diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrame.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrame.java index c5e854a54d..85f85bfbc9 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrame.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetStackFrame.java @@ -22,8 +22,7 @@ import java.util.concurrent.CompletableFuture; import agent.gdb.manager.GdbStackFrame; import agent.gdb.manager.impl.cmd.GdbStateChangeRecord; import ghidra.dbg.agent.DefaultTargetObject; -import ghidra.dbg.target.TargetObject; -import ghidra.dbg.target.TargetStackFrame; +import ghidra.dbg.target.*; import ghidra.dbg.target.schema.*; import ghidra.dbg.util.PathUtils; import ghidra.lifecycle.Internal; @@ -36,7 +35,7 @@ import ghidra.program.model.address.Address; attributes = { @TargetAttributeType(type = Void.class) }) public class GdbModelTargetStackFrame extends DefaultTargetObject - implements TargetStackFrame, GdbModelSelectableObject { + implements TargetStackFrame, TargetAggregate, GdbModelSelectableObject { public static final String FUNC_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "function"; public static final String FROM_ATTRIBUTE_NAME = PREFIX_INVISIBLE + "from"; // TODO diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java index 5237f1c1df..43b9b2d7b4 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/main/java/agent/gdb/model/impl/GdbModelTargetThread.java @@ -42,7 +42,8 @@ import ghidra.util.Msg; @TargetAttributeType(type = Void.class) }) public class GdbModelTargetThread extends DefaultTargetObject implements - TargetThread, TargetExecutionStateful, TargetSteppable, GdbModelSelectableObject { + TargetThread, TargetExecutionStateful, TargetSteppable, TargetAggregate, + GdbModelSelectableObject { protected static final TargetStepKindSet SUPPORTED_KINDS = TargetStepKindSet.of( // TargetStepKind.ADVANCE, // TargetStepKind.FINISH, // diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java index 916fa7567f..6ec34fa869 100644 --- a/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java +++ b/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java @@ -29,6 +29,7 @@ import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator; import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; +import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.script.GhidraScript; import ghidra.app.services.DebuggerTraceManagerService; @@ -46,6 +47,7 @@ import ghidra.program.model.listing.InstructionIterator; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.Memory; import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.TraceTimeManager; @@ -135,7 +137,9 @@ public class DebuggerEmuExampleScript extends GhidraScript { * library. This emulator will still know how to integrate with the UI, reading through to * open programs and writing state back into the trace. */ - BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(tool, trace, 0, null) { + TracePlatform host = trace.getPlatformManager().getHostPlatform(); + DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool, null, host, 0); + BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(access) { @Override protected PcodeUseropLibrary createUseropLibrary() { return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this); @@ -169,7 +173,7 @@ public class DebuggerEmuExampleScript extends GhidraScript { thread.stepInstruction(); snapshot = time.createSnapshot("Stepped to " + thread.getCounter()); - emulator.writeDown(trace, snapshot.getKey(), 0); + emulator.writeDown(host, snapshot.getKey(), 0); } printerr("We should not have completed 10 steps!"); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java index e9687151a1..2f9b60f594 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java @@ -23,6 +23,7 @@ import docking.action.DockingAction; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.DebuggerResources.GoToAction; import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.async.AsyncUtils; import ghidra.framework.plugintool.Plugin; import ghidra.framework.plugintool.PluginTool; import ghidra.pcode.exec.*; @@ -93,12 +94,13 @@ public abstract class DebuggerGoToTrait { } public CompletableFuture goToSleigh(AddressSpace space, PcodeExpression expression) { - AsyncPcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(current); - CompletableFuture result = expression.evaluate(executor); - return result.thenApply(offset -> { + PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, current); + CompletableFuture result = + CompletableFuture.supplyAsync(() -> expression.evaluate(executor)); + return result.thenApplyAsync(offset -> { Address address = space.getAddress( Utils.bytesToLong(offset, offset.length, expression.getLanguage().isBigEndian())); return goToAddress(address); - }); + }, AsyncUtils.SWING_EXECUTOR); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java index fbeea9e80c..5542c58c4f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java @@ -434,6 +434,10 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { return; } Register pc = curTrace.getBaseLanguage().getProgramCounter(); + if (pc == null) { + contextChanged(); + return; + } RegisterValue value = regs.getViewValue(current.getViewSnap(), pc); if (value == null) { contextChanged(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java index 17f4b566b4..f08cbb1544 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.gui.watch; import java.math.BigInteger; import java.util.*; +import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.tuple.Pair; @@ -30,9 +31,9 @@ import ghidra.app.services.DebuggerStateEditingService.StateEditor; import ghidra.docking.settings.Settings; import ghidra.docking.settings.SettingsImpl; import ghidra.framework.options.SaveState; +import ghidra.framework.plugintool.PluginTool; import ghidra.pcode.exec.*; -import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.TraceSleighUtils; +import ghidra.pcode.exec.trace.*; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.*; import ghidra.program.model.data.DataType; @@ -45,7 +46,7 @@ import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.symbol.*; import ghidra.program.util.ProgramLocation; import ghidra.trace.model.*; -import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.symbol.TraceLabelSymbol; import ghidra.trace.model.thread.TraceThread; @@ -63,7 +64,7 @@ public class WatchRow { private SleighLanguage language; private PcodeExecutor> executorWithState; private ReadDepsPcodeExecutor executorWithAddress; - private AsyncPcodeExecutor asyncExecutor; + private PcodeExecutor asyncExecutor; // name is reminder to use asynchronously private String expression; private String typePath; @@ -117,7 +118,9 @@ public class WatchRow { protected void doTargetReads() { if (compiled != null && asyncExecutor != null) { - compiled.evaluate(asyncExecutor).exceptionally(ex -> { + CompletableFuture asyncEvaluation = + CompletableFuture.supplyAsync(() -> compiled.evaluate(asyncExecutor)); + asyncEvaluation.exceptionally(ex -> { error = ex; Swing.runIfSwingOrRunLater(() -> { provider.watchTableModel.notifyUpdated(this); @@ -174,9 +177,10 @@ public class WatchRow { extends DirectBytesTracePcodeExecutorStatePiece { private AddressSet reads = new AddressSet(); - public ReadDepsTraceBytesPcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, - int frame) { - super(trace, snap, thread, frame); + public ReadDepsTraceBytesPcodeExecutorStatePiece(TracePlatform platform, long snap, + TraceThread thread, int frame) { + super(DirectBytesTracePcodeExecutorState.getDefaultThreadAccess(platform, snap, thread, + frame)); } @Override @@ -197,7 +201,7 @@ public class WatchRow { } @Override - protected void setInSpace(TraceMemorySpace space, long offset, int size, byte[] val) { + protected void setInSpace(AddressSpace space, long offset, int size, byte[] val) { throw new UnsupportedOperationException("Expression cannot write to trace"); } @@ -233,18 +237,31 @@ public class WatchRow { } } - protected static ReadDepsPcodeExecutor buildAddressDepsExecutor( + /** + * Build an executor that can compute three things simultaneously + * + *

+ * This computes the concrete value, its address, and the set of physical addresses involved in + * the computation. The resulting pair gives the value and its address. To get the addresses + * involved, invoke {@link ReadDepsPcodeExecutor#getReads()} after evaluation. + * + * @param tool the plugin tool + * @param coordinates the coordinates providing context for the evaluation + * @return an executor for evaluating the watch + */ + protected static ReadDepsPcodeExecutor buildAddressDepsExecutor(PluginTool tool, DebuggerCoordinates coordinates) { Trace trace = coordinates.getTrace(); + TracePlatform platform = DebuggerPcodeUtils.getCurrentPlatform(tool, coordinates); ReadDepsTraceBytesPcodeExecutorStatePiece piece = - new ReadDepsTraceBytesPcodeExecutorStatePiece(trace, coordinates.getViewSnap(), + new ReadDepsTraceBytesPcodeExecutorStatePiece(platform, coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame()); Language language = trace.getBaseLanguage(); if (!(language instanceof SleighLanguage)) { throw new IllegalArgumentException("Watch expressions require a SLEIGH language"); } PcodeExecutorState> paired = new DefaultPcodeExecutorState<>(piece) - .paired(new AddressOfPcodeExecutorStatePiece(language.isBigEndian())); + .paired(new AddressOfPcodeExecutorStatePiece(language)); PairedPcodeArithmetic arithmetic = new PairedPcodeArithmetic<>( BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE); return new ReadDepsPcodeExecutor(piece, (SleighLanguage) language, arithmetic, paired); @@ -270,11 +287,12 @@ public class WatchRow { recompile(); } if (coordinates.isAliveAndReadsPresent()) { - asyncExecutor = DebuggerPcodeUtils.executorForCoordinates(coordinates); + asyncExecutor = + DebuggerPcodeUtils.executorForCoordinates(provider.getTool(), coordinates); } executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace, coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame()); - executorWithAddress = buildAddressDepsExecutor(coordinates); + executorWithAddress = buildAddressDepsExecutor(provider.getTool(), coordinates); } public void setExpression(String expression) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerMemoryMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerMemoryMapper.java index d16d94ec27..9555526385 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerMemoryMapper.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerMemoryMapper.java @@ -38,6 +38,9 @@ public class DefaultDebuggerMemoryMapper implements DebuggerMemoryMapper { } protected static Address toSameNamedSpace(Address addr, AddressFactory factory) { + if (addr.isRegisterAddress()) { + throw new IllegalArgumentException("Memory mapper cannot handle register addresses"); + } return factory.getAddressSpace(addr.getAddressSpace().getName()) .getAddress(addr.getOffset()); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java index 3296e282bb..e0c2fd12fb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/DefaultDebuggerPlatformMapper.java @@ -23,6 +23,7 @@ import ghidra.trace.model.Trace; import ghidra.trace.model.guest.*; import ghidra.trace.model.target.TraceObject; import ghidra.util.MathUtilities; +import ghidra.util.Msg; import ghidra.util.database.UndoableTransaction; public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMapper { @@ -98,5 +99,14 @@ public class DefaultDebuggerPlatformMapper extends AbstractDebuggerPlatformMappe catch (AddressOverflowException e) { throw new AssertionError(e); } + + try { + platform.addMappedRegisterRange(); + } + catch (AddressOverflowException e) { + Msg.showError(this, null, "Map Registers", + "The host language cannot accomodate register storage for the" + + " guest platform (language: " + platform.getLanguage() + ")"); + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..b72fe72898 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractRWTargetPcodeExecutorStatePiece.java @@ -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 + * + *

+ * 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 waitTimeout(CompletableFuture 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 { + @Override + protected PcodeDebuggerDataAccess getBacking(AddressSpace space) { + return data; + } + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorStatePiece.java deleted file mode 100644 index a032a694c0..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorStatePiece.java +++ /dev/null @@ -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 - * - *

- * 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 waitTimeout(CompletableFuture 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. - * - *

- * 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 { - @Override - protected TraceMemorySpace getBacking(AddressSpace space) { - try (UndoableTransaction tid = - UndoableTransaction.start(trace, "Create space")) { - return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true); - } - } - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java index 320cf73163..52dcdbeb50 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java @@ -15,13 +15,11 @@ */ package ghidra.app.plugin.core.debug.service.emulation; -import ghidra.app.services.TraceRecorder; -import ghidra.framework.plugintool.PluginTool; -import ghidra.pcode.emu.*; +import ghidra.app.plugin.core.debug.service.emulation.data.*; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.trace.BytesTracePcodeEmulator; import ghidra.pcode.exec.trace.TracePcodeExecutorState; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; /** * A trace emulator that knows how to read target memory when necessary @@ -39,51 +37,27 @@ import ghidra.trace.model.thread.TraceThread; */ public class BytesDebuggerPcodeEmulator extends BytesTracePcodeEmulator implements DebuggerPcodeMachine { - protected final PluginTool tool; - protected final TraceRecorder recorder; + + protected final PcodeDebuggerAccess access; /** * Create the emulator * - * @param tool the tool creating the emulator - * @param trace the trace from which the emulator loads state - * @param snap the snap from which the emulator loads state - * @param recorder if applicable, the recorder for the trace's live target + * @param access the trace-and-debugger access shim */ - public BytesDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap, - TraceRecorder recorder) { - super(trace, snap); - this.tool = tool; - this.recorder = recorder; - } - - @Override - public PluginTool getTool() { - return tool; - } - - @Override - public TraceRecorder getRecorder() { - return recorder; - } - - @Override - protected BytesPcodeThread createThread(String name) { - BytesPcodeThread thread = super.createThread(name); - initializeThreadContext(thread); - return thread; + public BytesDebuggerPcodeEmulator(PcodeDebuggerAccess access) { + super(access); + this.access = access; } @Override public TracePcodeExecutorState createSharedState() { - return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder); + return new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RO); } @Override public TracePcodeExecutorState createLocalState(PcodeThread emuThread) { - TraceThread traceThread = - trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()); - return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0, - recorder); + return new RWTargetRegistersPcodeExecutorState(access.getDataForLocalState(emuThread, 0), + Mode.RO); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java index f910afc52e..c2b68ddcfd 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java @@ -15,9 +15,7 @@ */ package ghidra.app.plugin.core.debug.service.emulation; -import ghidra.app.services.TraceRecorder; -import ghidra.framework.plugintool.PluginTool; -import ghidra.trace.model.Trace; +import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess; /** * The Debugger's default emulator factory @@ -32,8 +30,7 @@ public class BytesDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorF } @Override - public DebuggerPcodeMachine create(PluginTool tool, Trace trace, long snap, - TraceRecorder recorder) { - return new BytesDebuggerPcodeEmulator(tool, trace, snap, recorder); + public DebuggerPcodeMachine create(PcodeDebuggerAccess access) { + return new BytesDebuggerPcodeEmulator(access); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index e163564557..cd655ced9c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -43,10 +43,12 @@ import ghidra.async.AsyncLazyMap; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; +import ghidra.pcode.exec.DebuggerPcodeUtils; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.trace.model.*; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; @@ -183,6 +185,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm @AutoServiceConsumed private DebuggerModelService modelService; @AutoServiceConsumed + private DebuggerPlatformService platformService; + @AutoServiceConsumed private DebuggerStaticMappingService staticMappings; @SuppressWarnings("unused") private AutoService.Wiring autoServiceWiring; @@ -456,7 +460,14 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm protected long doEmulate(CacheKey key, TaskMonitor monitor) throws CancelledException { Trace trace = key.trace; + /** + * TODO: object and/or platform should somehow be incorporated into the key, the schedule? + * something? + */ + TracePlatform platform = DebuggerPcodeUtils.getCurrentPlatform(platformService, + traceManager.getCurrentFor(trace).trace(trace)); TraceSchedule time = key.time; + CachedEmulator ce; DebuggerPcodeMachine emu; Map.Entry ancestor = findNearestPrefix(key); @@ -473,19 +484,23 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm ce = ancestor.getValue(); emu = ce.emulator; monitor.initialize(time.totalTickCount() - prevKey.time.totalTickCount()); + createRegisterSpaces(trace, time, monitor); + monitor.setMessage("Emulating"); time.finish(trace, prevKey.time, emu, monitor); } else { - emu = emulatorFactory.create(tool, trace, time.getSnap(), + emu = emulatorFactory.create(tool, platform, time.getSnap(), modelService == null ? null : modelService.getRecorder(trace)); ce = new CachedEmulator(emu); monitor.initialize(time.totalTickCount()); + createRegisterSpaces(trace, time, monitor); + monitor.setMessage("Emulating"); time.execute(trace, emu, monitor); } TraceSnapshot destSnap; try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) { destSnap = findScratch(trace, time); - emu.writeDown(trace, destSnap.getKey(), time.getSnap()); + emu.writeDown(platform, destSnap.getKey(), time.getSnap()); } synchronized (cache) { @@ -502,6 +517,19 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm return destSnap.getKey(); } + protected void createRegisterSpaces(Trace trace, TraceSchedule time, TaskMonitor monitor) { + if (trace.getObjectManager().getRootObject() == null) { + return; + } + // Cause object-register support to copy values into new register spaces + monitor.setMessage("Creating register spaces"); + try (UndoableTransaction tid = UndoableTransaction.start(trace, "Prepare emulation")) { + for (TraceThread thread : time.getThreads(trace)) { + trace.getMemoryManager().getMemoryRegisterSpace(thread, 0, true); + } + } + } + @Override public long emulate(Trace trace, TraceSchedule time, TaskMonitor monitor) throws CancelledException { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeEmulatorFactory.java index 8d99679f6f..5c9de69b42 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeEmulatorFactory.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeEmulatorFactory.java @@ -15,9 +15,11 @@ */ package ghidra.app.plugin.core.debug.service.emulation; +import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; +import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess; import ghidra.app.services.TraceRecorder; import ghidra.framework.plugintool.PluginTool; -import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.util.classfinder.ExtensionPoint; /** @@ -41,11 +43,21 @@ public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint { * Create the emulator * * @param tool the tool creating the emulator - * @param trace the user's current trace from which the emulator should load state + * @param platform the user's current trace platform from which the emulator should load state * @param snap the user's current snap from which the emulator should load state * @param recorder if applicable, the recorder for the trace's live target * @return the emulator */ - DebuggerPcodeMachine create(PluginTool tool, Trace trace, long snap, - TraceRecorder recorder); + default DebuggerPcodeMachine create(PluginTool tool, TracePlatform platform, long snap, + TraceRecorder recorder) { + return create(new DefaultPcodeDebuggerAccess(tool, recorder, platform, snap)); + } + + /** + * Create the emulator + * + * @param access the trace-and-debugger access shim + * @return the emulator + */ + DebuggerPcodeMachine create(PcodeDebuggerAccess access); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeMachine.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeMachine.java index 37d4da7270..bd8abc3090 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeMachine.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeMachine.java @@ -15,9 +15,6 @@ */ package ghidra.app.plugin.core.debug.service.emulation; -import ghidra.app.services.TraceRecorder; -import ghidra.framework.plugintool.PluginTool; -import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; import ghidra.pcode.exec.trace.TracePcodeMachine; @@ -26,26 +23,10 @@ import ghidra.pcode.exec.trace.TracePcodeMachine; * A Debugger-integrated emulator (or p-code machine) * *

- * This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator - * developers should use this interface, but emulator clients should not. Clients should use - * {@link PcodeMachine} instead. A common implementation is an emulator with concrete plus some - * auxiliary state. To realize such a machine, please see {@link AuxDebuggerPcodeEmulator} and - * {@link AuxDebuggerEmulatorPartsFactory}. + * A common implementation is an emulator with concrete plus some auxiliary state. To realize such a + * machine, please see {@link AuxDebuggerPcodeEmulator} and {@link AuxDebuggerEmulatorPartsFactory}. * * @param the type of values in the machine's memory and registers */ public interface DebuggerPcodeMachine extends TracePcodeMachine { - /** - * 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(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/Mode.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/Mode.java new file mode 100644 index 0000000000..9b19ac58f7 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/Mode.java @@ -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(); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java index 8278048cad..b09a483617 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ProgramEmulationUtils.java @@ -216,8 +216,8 @@ public enum ProgramEmulationUtils { */ public static void initializeRegisters(Trace trace, long snap, TraceThread thread, Program program, Address tracePc, Address programPc, TraceMemoryRegion stack) { - TraceMemorySpace space = - trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + TraceMemoryManager memory = trace.getMemoryManager(); + TraceMemorySpace regSpace = memory.getMemoryRegisterSpace(thread, true); if (program != null) { ProgramContext ctx = program.getProgramContext(); for (Register reg : Stream.of(ctx.getRegistersWithValues()) @@ -227,20 +227,30 @@ public enum ProgramEmulationUtils { if (rv == null || !rv.hasAnyValue()) { continue; } + TraceMemoryOperations space = + reg.getAddressSpace().isRegisterSpace() ? regSpace : memory; // Set all the mask bits space.setValue(snap, new RegisterValue(reg, BigInteger.ZERO).combineValues(rv)); } } - space.setValue(snap, new RegisterValue(trace.getBaseLanguage().getProgramCounter(), + Register regPC = trace.getBaseLanguage().getProgramCounter(); + TraceMemoryOperations spacePC = + regPC.getAddressSpace().isRegisterSpace() ? regSpace : memory; + spacePC.setValue(snap, new RegisterValue(regPC, NumericUtilities.unsignedLongToBigInteger(tracePc.getAddressableWordOffset()))); if (stack != null) { CompilerSpec cSpec = trace.getBaseCompilerSpec(); Address sp = cSpec.stackGrowsNegative() ? stack.getMaxAddress() : stack.getMinAddress(); - space.setValue(snap, - new RegisterValue(cSpec.getStackPointer(), - NumericUtilities.unsignedLongToBigInteger(sp.getAddressableWordOffset()))); + Register regSP = cSpec.getStackPointer(); + if (regSP != null) { + TraceMemoryOperations spaceSP = + regSP.getAddressSpace().isRegisterSpace() ? regSpace : memory; + spaceSP.setValue(snap, + new RegisterValue(regSP, + NumericUtilities.unsignedLongToBigInteger(sp.getAddressableWordOffset()))); + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java new file mode 100644 index 0000000000..8bf601a6f0 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorState.java @@ -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 { + /** + * 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)); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..a800872a14 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetMemoryPcodeExecutorStatePiece.java @@ -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 + * + *

+ * 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)}). + * + *

+ * This state will also attempt to fill unknown bytes with values from mapped static images. The + * order to retrieve state is: + *

    + *
  1. The cache, i.e., this state object
  2. + *
  3. The trace
  4. + *
  5. The live target, if applicable
  6. + *
  7. Mapped static images, if available
  8. + *
+ * + *

+ * 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 + * + *

+ * 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 newSpaceMap() { + return new TargetBackedSpaceMap() { + @Override + protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) { + return new RWTargetMemoryCachedSpace(language, space, + (PcodeDebuggerMemoryAccess) data); + } + }; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java new file mode 100644 index 0000000000..a9beec92aa --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorState.java @@ -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 { + /** + * 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)); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..2f26847bf1 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/RWTargetRegistersPcodeExecutorStatePiece.java @@ -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 + * + *

+ * 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)}). + * + *

    + *
  1. The cache, i.e., this state object
  2. + *
  3. The trace
  4. + *
  5. The live target, if applicable
  6. + *
+ * + *

+ * 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 + * + *

+ * 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 newSpaceMap() { + return new TargetBackedSpaceMap() { + @Override + protected CachedSpace newSpace(AddressSpace space, PcodeDebuggerDataAccess data) { + return new RWTargetRegistersCachedSpace(language, space, + (PcodeDebuggerRegistersAccess) data); + } + }; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java deleted file mode 100644 index 2cd2a15caf..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java +++ /dev/null @@ -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 { - /** - * 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)); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorStatePiece.java deleted file mode 100644 index 4735ff9bdb..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorStatePiece.java +++ /dev/null @@ -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 - * - *

- * 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)}). - * - *

- * This state will also attempt to fill unknown bytes with values from mapped static images. The - * order to retrieve state is: - *

    - *
  1. The cache, i.e., this state object
  2. - *
  3. The trace
  4. - *
  5. The live target, if applicable
  6. - *
  7. Mapped static images, if available
  8. - *
- * - *

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

- * 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> ent : mappingService - .getOpenMappedViews(trace, unknown, snap) - .entrySet()) { - Program program = ent.getKey(); - Memory memory = program.getMemory(); - AddressSetView initialized = memory.getLoadedAndInitializedAddressSet(); - - Collection 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 newSpaceMap() { - return new TargetBackedSpaceMap() { - @Override - protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { - return new ReadsTargetMemoryCachedSpace(language, space, backing, snap); - } - }; - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorState.java deleted file mode 100644 index 7f438c6358..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorState.java +++ /dev/null @@ -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 { - /** - * 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)); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorStatePiece.java deleted file mode 100644 index 564737b618..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorStatePiece.java +++ /dev/null @@ -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 - * - *

- * 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)}). - * - *

    - *
  1. The cache, i.e., this state object
  2. - *
  3. The trace
  4. - *
  5. The live target, if applicable
  6. - *
- * - *

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

- * 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 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 newSpaceMap() { - return new TargetBackedSpaceMap() { - @Override - protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { - return new ReadsTargetRegistersCachedSpace(language, space, backing, snap); - } - }; - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java new file mode 100644 index 0000000000..b2c4f6e0dd --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/AbstractPcodeDebuggerAccess.java @@ -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 the type of shared data-access shims provided + * @param the type of thread-local data-access shims provided + */ +public abstract class AbstractPcodeDebuggerAccess + extends AbstractPcodeTraceAccess + 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; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerAccess.java new file mode 100644 index 0000000000..c17ef1b270 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerAccess.java @@ -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 // + { + + /** + * 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); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java new file mode 100644 index 0000000000..083d27aea8 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerMemoryAccess.java @@ -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 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 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> ent : mappingService + .getOpenMappedViews(trace, hostView, snap) + .entrySet()) { + Program program = ent.getKey(); + Memory memory = program.getMemory(); + AddressSetView initialized = memory.getLoadedAndInitializedAddressSet(); + + Collection 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 PcodeTracePropertyAccess getPropertyAccess(String name, Class type) { + return new DefaultPcodeDebuggerPropertyAccess<>(this, name, type); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerPropertyAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerPropertyAccess.java new file mode 100644 index 0000000000..448c16c806 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerPropertyAccess.java @@ -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 + * + *

+ * This implementation defers to the same property of mapped static images when the property is not + * set in the trace. + * + * @param the type of the property's values + */ +public class DefaultPcodeDebuggerPropertyAccess + extends DefaultPcodeTracePropertyAccess { + + 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 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); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java new file mode 100644 index 0000000000..edc9b921fc --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/DefaultPcodeDebuggerRegistersAccess.java @@ -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 readFromTargetRegisters(AddressSetView guestView) { + if (!isLive()) { + return CompletableFuture.completedFuture(false); + } + Set 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 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. +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/InternalPcodeDebuggerDataAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/InternalPcodeDebuggerDataAccess.java new file mode 100644 index 0000000000..bfcd98d914 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/InternalPcodeDebuggerDataAccess.java @@ -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(); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerAccess.java new file mode 100644 index 0000000000..5c39380c4e --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerAccess.java @@ -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 + * + *

+ * 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); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerDataAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerDataAccess.java new file mode 100644 index 0000000000..4ea51634ed --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerDataAccess.java @@ -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 + * + *

+ * 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 + * + *

+ * 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(); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerMemoryAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerMemoryAccess.java new file mode 100644 index 0000000000..082ee7fddc --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerMemoryAccess.java @@ -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 + * + *

+ * 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 readFromTargetMemory(AddressSetView unknown); + + /** + * Use the Debugger's static mapping service to read bytes from relocated program images + * + *

+ * 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 + * + *

+ * 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 writeTargetMemory(Address address, byte[] data); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerRegistersAccess.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerRegistersAccess.java new file mode 100644 index 0000000000..66e43a42a4 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/data/PcodeDebuggerRegistersAccess.java @@ -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 readFromTargetRegisters(AddressSetView unknown); + + /** + * Instruct the associated recorder to write target registers + * + *

+ * 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 writeTargetRegister(Address address, byte[] data); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java index b5527c35f9..997ccc062f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerPlatformService.java @@ -16,7 +16,6 @@ package ghidra.app.services; import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper; -import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer; import ghidra.trace.model.Trace; import ghidra.trace.model.target.TraceObject; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java index 38367fc29f..0a1b05d5d6 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/TraceRecorder.java @@ -356,23 +356,37 @@ public interface TraceRecorder { return writeMemory(address, data); } if (address.isRegisterAddress()) { - Language lang = getTrace().getBaseLanguage(); - Register register = lang.getRegister(address, data.length); - if (register == null) { - throw new IllegalArgumentException( - "Cannot identify the (single) register to write: " + address); - } - - RegisterValue rv = new RegisterValue(register, - Utils.bytesToBigInteger(data, data.length, lang.isBigEndian(), false)); - TraceMemorySpace regs = - getTrace().getMemoryManager().getMemoryRegisterSpace(thread, frameLevel, false); - rv = TraceRegisterUtils.combineWithTraceBaseRegisterValue(rv, getSnap(), regs, true); - return writeThreadRegisters(thread, frameLevel, Map.of(rv.getRegister(), rv)); + return writeRegister(thread, frameLevel, address, data); } throw new IllegalArgumentException("Address is not in a recognized space: " + address); } + /** + * Write a register (by address) of the given thread + * + * @param thread the thread + * @param frameLevel the frame, usually 0. + * @param address the address of the register + * @param data the value to write + * @return a future which completes when the write is complete + */ + default CompletableFuture 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 * diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java deleted file mode 100644 index 2129911710..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java +++ /dev/null @@ -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 - * - *

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

- * 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 the type of values in the state - */ -public class AsyncPcodeExecutor extends PcodeExecutor> { - public AsyncPcodeExecutor(SleighLanguage language, - PcodeArithmetic> arithmetic, - PcodeExecutorState> state) { - super(language, arithmetic, state); - } - - public CompletableFuture stepOpAsync(PcodeOp op, PcodeFrame frame, - PcodeUseropLibrary> library) { - if (op.getOpcode() == PcodeOp.CBRANCH) { - return executeConditionalBranchAsync(op, frame); - } - stepOp(op, frame, library); - return AsyncUtils.NIL; - } - - public CompletableFuture stepAsync(PcodeFrame frame, - PcodeUseropLibrary> 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 executeConditionalBranchAsync(PcodeOp op, PcodeFrame frame) { - Varnode condVar = op.getInput(1); - CompletableFuture cond = state.getVar(condVar); - return cond.thenAccept(c -> { - if (arithmetic.isTrue(cond, Purpose.CONDITION)) { - executeBranch(op, frame); - } - }); - } - - public CompletableFuture executeAsync(PcodeProgram program, - PcodeUseropLibrary> library) { - return executeAsync(program.code, program.useropNames, library); - } - - protected CompletableFuture executeAsyncLoop(PcodeFrame frame, - PcodeUseropLibrary> library) { - if (frame.isFinished()) { - return AsyncUtils.NIL; - } - return stepAsync(frame, library) - .thenComposeAsync(__ -> executeAsyncLoop(frame, library)); - } - - public CompletableFuture executeAsync(List code, - Map useropNames, PcodeUseropLibrary> library) { - PcodeFrame frame = new PcodeFrame(language, code, useropNames); - return executeAsyncLoop(frame, library); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java deleted file mode 100644 index 0f1473d748..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java +++ /dev/null @@ -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 the type of values wrapped - */ -public class AsyncWrappedPcodeArithmetic implements PcodeArithmetic> { - public static final AsyncWrappedPcodeArithmetic BYTES_BE = - new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.BIG_ENDIAN); - public static final AsyncWrappedPcodeArithmetic BYTES_LE = - new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.LITTLE_ENDIAN); - - public static AsyncWrappedPcodeArithmetic forEndian(boolean isBigEndian) { - return isBigEndian ? BYTES_BE : BYTES_LE; - } - - public static AsyncWrappedPcodeArithmetic forLanguage(Language language) { - return forEndian(language.isBigEndian()); - } - - private final PcodeArithmetic arithmetic; - - public AsyncWrappedPcodeArithmetic(PcodeArithmetic 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 unaryOp(int opcode, int sizeout, int sizein1, - CompletableFuture in1) { - return in1.thenApply(t1 -> arithmetic.unaryOp(opcode, sizeout, sizein1, t1)); - } - - @Override - public CompletableFuture binaryOp(int opcode, int sizeout, int sizein1, - CompletableFuture in1, int sizein2, CompletableFuture in2) { - return in1.thenCombine(in2, - (t1, t2) -> arithmetic.binaryOp(opcode, sizeout, sizein1, t1, sizein2, t2)); - } - - @Override - public CompletableFuture modBeforeStore(int sizeout, int sizeinAddress, - CompletableFuture inAddress, int sizeinValue, CompletableFuture inValue) { - return inValue; - } - - @Override - public CompletableFuture modAfterLoad(int sizeout, int sizeinAddress, - CompletableFuture inAddress, int sizeinValue, CompletableFuture inValue) { - return inValue; - } - - @Override - public CompletableFuture fromConst(byte[] value) { - return CompletableFuture.completedFuture(arithmetic.fromConst(value)); - } - - @Override - public byte[] toConcrete(CompletableFuture 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 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 sizeOfAbstract(CompletableFuture value) { - return value.thenApply(v -> arithmetic.sizeOfAbstract(v)); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java deleted file mode 100644 index aadd997822..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java +++ /dev/null @@ -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 the type of values wrapped - */ -public class AsyncWrappedPcodeExecutorStatePiece - implements PcodeExecutorStatePiece, CompletableFuture> { - protected final PcodeExecutorStatePiece state; - protected final AsyncWrappedPcodeArithmetic addressArithmetic; - protected final AsyncWrappedPcodeArithmetic arithmetic; - private CompletableFuture lastWrite = AsyncUtils.NIL; - - public AsyncWrappedPcodeExecutorStatePiece(PcodeExecutorStatePiece state) { - this.state = state; - this.addressArithmetic = new AsyncWrappedPcodeArithmetic<>(state.getAddressArithmetic()); - this.arithmetic = new AsyncWrappedPcodeArithmetic<>(state.getArithmetic()); - } - - @Override - public AsyncWrappedPcodeArithmetic getAddressArithmetic() { - return addressArithmetic; - } - - @Override - public AsyncWrappedPcodeArithmetic getArithmetic() { - return arithmetic; - } - - protected boolean isWriteDone() { - return lastWrite.isDone(); - } - - protected CompletableFuture nextRead(Supplier> next) { - return lastWrite.thenCompose(__ -> next.get()).exceptionally(ex -> null); - } - - protected void nextWrite(Supplier> next) { - lastWrite = nextRead(next); - } - - protected CompletableFuture doSetVar(AddressSpace space, CompletableFuture offset, - int size, boolean quantize, CompletableFuture val) { - return offset.thenCompose(off -> val.thenAccept(v -> { - state.setVar(space, off, size, quantize, v); - })); - } - - @Override - public void setVar(AddressSpace space, CompletableFuture offset, int size, - boolean quantize, CompletableFuture val) { - nextWrite(() -> doSetVar(space, offset, size, quantize, val)); - } - - protected CompletableFuture doGetVar(AddressSpace space, CompletableFuture offset, - int size, boolean quantize) { - return offset.thenApply(off -> { - return state.getVar(space, off, size, quantize); - }); - } - - @Override - public CompletableFuture getVar(AddressSpace space, CompletableFuture 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); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java index 7dd480f8c1..fa41349cb8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java @@ -15,48 +15,118 @@ */ package ghidra.pcode.exec; -import java.util.concurrent.CompletableFuture; +import java.util.Objects; import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper; +import ghidra.app.plugin.core.debug.service.emulation.*; +import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece; +import ghidra.app.services.DebuggerPlatformService; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.emu.ThreadPcodeExecutorState; import ghidra.program.model.lang.Language; import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; +/** + * Utilities for evaluating or executing Sleigh/p-code in the Debugger + */ public enum DebuggerPcodeUtils { ; + /** - * Get an executor which can be used to evaluate Sleigh expressions at the given coordinates, - * asynchronously. + * Get the current platform * *

- * TODO: Change this to be synchronous and have clients evaluate expressions in another thread? + * TODO: This should be part of {@link DebuggerTraceManagerService}. * + * @param platformService the platform service * @param coordinates the coordinates - * @return the executor + * @return the "current platform" for the coordinates */ - public static AsyncPcodeExecutor executorForCoordinates( + public static TracePlatform getCurrentPlatform(DebuggerPlatformService platformService, DebuggerCoordinates coordinates) { Trace trace = coordinates.getTrace(); + if (platformService == null) { + return trace.getPlatformManager().getHostPlatform(); + } + DebuggerPlatformMapper mapper = platformService.getCurrentMapperFor(trace); + if (mapper == null) { + return trace.getPlatformManager().getHostPlatform(); + } + return Objects.requireNonNull(trace.getPlatformManager() + .getPlatform(mapper.getCompilerSpec(coordinates.getObject()))); + } + + /** + * Get the current platform + * + *

+ * 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 + * + *

+ * 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 executorStateForCoordinates(PluginTool tool, + DebuggerCoordinates coordinates) { + // TODO: Make platform part of coordinates + Trace trace = coordinates.getTrace(); if (trace == null) { throw new IllegalArgumentException("Coordinates have no trace"); } - Language language = trace.getBaseLanguage(); + TracePlatform platform = getCurrentPlatform(tool, coordinates); + Language language = platform.getLanguage(); if (!(language instanceof SleighLanguage)) { - throw new IllegalArgumentException("Given trace does not use a Sleigh language"); + throw new IllegalArgumentException( + "Given trace or platform does not use a Sleigh language"); } - SleighLanguage slang = (SleighLanguage) language; - PcodeExecutorState> state; - if (coordinates.getRecorder() == null) { - state = new AsyncWrappedPcodeExecutorState<>( - new DirectBytesTracePcodeExecutorStatePiece(trace, coordinates.getViewSnap(), - coordinates.getThread(), coordinates.getFrame())); + DefaultPcodeDebuggerAccess access = new DefaultPcodeDebuggerAccess(tool, + coordinates.getRecorder(), platform, coordinates.getSnap()); + PcodeExecutorState shared = + new RWTargetMemoryPcodeExecutorState(access.getDataForSharedState(), Mode.RW); + if (coordinates.getThread() == null) { + return shared; } - else { - state = new TraceRecorderAsyncPcodeExecutorState(coordinates.getRecorder(), - coordinates.getSnap(), coordinates.getThread(), coordinates.getFrame()); - } - return new AsyncPcodeExecutor<>(slang, AsyncWrappedPcodeArithmetic.forLanguage(slang), - state); + PcodeExecutorState local = new RWTargetRegistersPcodeExecutorState( + access.getDataForLocalState(coordinates.getThread(), coordinates.getFrame()), Mode.RW); + return new ThreadPcodeExecutorState<>(shared, local); + } + + /** + * Get an executor which can be used to evaluate Sleigh expressions at the given coordinates + * + *

+ * 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 executorForCoordinates(PluginTool tool, + DebuggerCoordinates coordinates) { + PcodeExecutorState state = executorStateForCoordinates(tool, coordinates); + + SleighLanguage slang = (SleighLanguage) state.getLanguage(); + return new PcodeExecutor<>(slang, BytesPcodeArithmetic.forLanguage(slang), state); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorState.java deleted file mode 100644 index 470bdac82b..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorState.java +++ /dev/null @@ -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> { - /** - * 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)); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorStatePiece.java deleted file mode 100644 index 649234ccc6..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorStatePiece.java +++ /dev/null @@ -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 - * - *

- * This is used for executing Sleigh code to manipulate trace history or a live target. - * - *

- * TODO: It might be easier to re-factor this to operate synchronously, executing Sleigh programs in - * a separate thread. - */ -public class TraceRecorderAsyncPcodeExecutorStatePiece - extends AsyncWrappedPcodeExecutorStatePiece { - 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 map, Address addr, int size) { - Address floor = map.floorKey(addr); - NavigableMap tail; - if (floor == null) { - tail = map; - } - else { - tail = map.tailMap(floor, true); - } - byte[] result = new byte[size]; - for (Map.Entry 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 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> 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> 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 offset, int size, boolean quantize, - CompletableFuture 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 doGetVar(AddressSpace space, - CompletableFuture 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); - }); - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java index 1144f0e8bb..9bf0148e45 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java @@ -21,8 +21,7 @@ import ghidra.app.plugin.core.debug.service.emulation.*; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; -import ghidra.pcode.exec.trace.PairedTracePcodeExecutorStatePiece; -import ghidra.pcode.exec.trace.TracePcodeExecutorState; +import ghidra.pcode.exec.trace.*; import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; @@ -70,7 +69,9 @@ public interface AuxDebuggerEmulatorPartsFactory extends AuxTraceEmulatorPart * given concrete piece is already capable of doing that for concrete values. The auxiliary * piece can, at its discretion, delegate to the concrete piece in order to derive its values. * It should be able to independently load its state from the trace and mapped static program, - * since this is one way a user expects to initialize the auxiliary values. + * since this is one way a user expects to initialize the auxiliary values. It ought to use the + * same data-access shim as the given concrete state. See + * {@link TracePcodeExecutorStatePiece#getData()}. * * @param emulator the emulator * @param concrete the concrete piece @@ -78,14 +79,14 @@ public interface AuxDebuggerEmulatorPartsFactory extends AuxTraceEmulatorPart */ TracePcodeExecutorState> createDebuggerSharedState( AuxDebuggerPcodeEmulator emulator, - ReadsTargetMemoryPcodeExecutorStatePiece concrete); + RWTargetMemoryPcodeExecutorStatePiece concrete); /** * Create the local (register) state of a new Debugger-integrated thread * *

* Like - * {@link #createDebuggerSharedState(AuxDebuggerPcodeEmulator, ReadsTargetMemoryPcodeExecutorStatePiece)} + * {@link #createDebuggerSharedState(AuxDebuggerPcodeEmulator, RWTargetMemoryPcodeExecutorStatePiece)} * this state must also be capable of lazily loading state from a trace and from a live target. * Static programs can't be mapped into register space, so they do not apply here. * @@ -96,5 +97,5 @@ public interface AuxDebuggerEmulatorPartsFactory extends AuxTraceEmulatorPart */ TracePcodeExecutorState> createDebuggerLocalState( AuxDebuggerPcodeEmulator emulator, PcodeThread> thread, - ReadsTargetRegistersPcodeExecutorStatePiece concrete); + RWTargetRegistersPcodeExecutorStatePiece concrete); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java index 75bd0e7267..cc8dc9aa26 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java @@ -18,14 +18,12 @@ package ghidra.pcode.exec.debug.auxiliary; import org.apache.commons.lang3.tuple.Pair; import ghidra.app.plugin.core.debug.service.emulation.*; -import ghidra.app.services.TraceRecorder; -import ghidra.framework.plugintool.PluginTool; +import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.exec.trace.TracePcodeExecutorState; import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; -import ghidra.trace.model.Trace; /** * An Debugger-integrated emulator whose parts are manufactured by a @@ -43,49 +41,33 @@ import ghidra.trace.model.Trace; */ public abstract class AuxDebuggerPcodeEmulator extends AuxTracePcodeEmulator implements DebuggerPcodeMachine> { - protected final PluginTool tool; - protected final TraceRecorder recorder; + + protected final PcodeDebuggerAccess access; /** * Create a new emulator * - * @param tool the user's tool where the emulator is integrated - * @param trace the user's current trace from which the emulator loads state - * @param snap the user's current snapshot from which the emulator loads state - * @param recorder if applicable, the trace's recorder for its live target + * @param access the trace-and-debugger access shim */ - public AuxDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap, - TraceRecorder recorder) { - super(trace, snap); - this.tool = tool; - this.recorder = recorder; + public AuxDebuggerPcodeEmulator(PcodeDebuggerAccess access) { + super(access); + this.access = access; } @Override protected abstract AuxDebuggerEmulatorPartsFactory getPartsFactory(); - @Override - public PluginTool getTool() { - return tool; - } - - @Override - public TraceRecorder getRecorder() { - return recorder; - } - @Override public TracePcodeExecutorState> createSharedState() { return getPartsFactory().createDebuggerSharedState(this, - new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, null, 0, recorder)); + new RWTargetMemoryPcodeExecutorStatePiece(access.getDataForSharedState(), Mode.RO)); } @Override public TracePcodeExecutorState> createLocalState( PcodeThread> thread) { return getPartsFactory().createDebuggerLocalState(this, thread, - new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, - getTraceThread(thread), 0, - recorder)); + new RWTargetRegistersPcodeExecutorStatePiece(access.getDataForLocalState(thread, 0), + Mode.RO)); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java index e0d1442df9..ed1c0ebf10 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlanTests.java @@ -284,7 +284,7 @@ public class DebuggerCopyPlanTests extends AbstractGhidraHeadedDebuggerGUITest { Register contextReg = tb.language.getContextBaseRegister(); Register longMode = tb.language.getRegister("longMode"); RegisterValue rv = tb.trace.getRegisterContextManager() - .getValueWithDefault(tb.language, contextReg, 0, tb.addr(0x55550000)); + .getValueWithDefault(tb.host, contextReg, 0, tb.addr(0x55550000)); rv = rv.assign(longMode, BigInteger.ZERO); Instruction checkCtx; try (UndoableTransaction tid = tb.startTransaction()) { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index 941b6ab668..2174d77590 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -835,17 +835,20 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI assertTrue(listingProvider.actionGoTo.isEnabled()); performAction(listingProvider.actionGoTo, false); DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class); - - dialog1.setExpression("r0"); - runSwing(() -> dialog1.okCallback()); + runSwing(() -> { + dialog1.setExpression("r0"); + dialog1.okCallback(); + }); waitForPass( () -> assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress())); performAction(listingProvider.actionGoTo, false); DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class); - dialog2.setExpression("*:4 r0"); - runSwing(() -> dialog2.okCallback()); + runSwing(() -> { + dialog2.setExpression("*:4 r0"); + dialog2.okCallback(); + }); waitForPass( () -> assertEquals(tb.addr(0x00404321), listingProvider.getLocation().getAddress())); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index cb94df21ad..d5209338f7 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -627,18 +627,21 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge assertTrue(memBytesProvider.actionGoTo.isEnabled()); performAction(memBytesProvider.actionGoTo, false); - DebuggerGoToDialog dialog = waitForDialogComponent(DebuggerGoToDialog.class); - - dialog.setExpression("r0"); - dialog.okCallback(); + DebuggerGoToDialog dialog1 = waitForDialogComponent(DebuggerGoToDialog.class); + runSwing(() -> { + dialog1.setExpression("r0"); + dialog1.okCallback(); + }); waitForPass( () -> assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress())); performAction(memBytesProvider.actionGoTo, false); - dialog = waitForDialogComponent(DebuggerGoToDialog.class); - dialog.setExpression("*:4 r0"); - dialog.okCallback(); + DebuggerGoToDialog dialog2 = waitForDialogComponent(DebuggerGoToDialog.class); + runSwing(() -> { + dialog2.setExpression("*:4 r0"); + dialog2.okCallback(); + }); waitForPass( () -> assertEquals(tb.addr(0x00404321), memBytesProvider.getLocation().getAddress())); @@ -1093,16 +1096,20 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); waitFor(() -> !trace.getMemoryManager().getAllRegions().isEmpty()); - goToDyn(addr(trace, 0x55550800)); performAction(actionEdit); - triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42"); - performAction(actionEdit); - waitForSwing(); - waitRecorder(recorder); + waitForPass(noExc(() -> { + traceManager.activateTrace(trace); + goToDyn(addr(trace, 0x55550800)); + triggerText(memBytesProvider.getByteViewerPanel().getCurrentComponent(), "42"); + waitForSwing(); + waitRecorder(recorder); - byte[] data = new byte[4]; - mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data); - assertArrayEquals(mb.arr(0x42, 0, 0, 0), data); + byte[] data = new byte[4]; + mb.testProcess1.memory.getMemory(mb.addr(0x55550800), data); + assertArrayEquals(mb.arr(0x42, 0, 0, 0), data); + })); + + performAction(actionEdit); } @Test diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderObjectTest.java index b9f4c9be73..b067a16757 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderObjectTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderObjectTest.java @@ -18,10 +18,15 @@ package ghidra.app.plugin.core.debug.gui.stack; import java.io.IOException; import ghidra.dbg.target.schema.SchemaContext; -import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.database.UndoableTransaction; +import ghidra.util.exception.DuplicateNameException; public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest { @@ -51,46 +56,69 @@ public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest { public void activateObjectsMode() throws Exception { // NOTE the use of index='1' allowing object-based managers to ID unique path - ctx = XmlSchemaContext.deserialize("" + // - "" + // - " " + // - " " + // - " " + // - " " + // - " " + // <---- NOTE HERE - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - ""); + ctx = XmlSchemaContext.deserialize(""" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """); try (UndoableTransaction tid = tb.startTransaction()) { tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); } } + + @Override + protected TraceThread addThread(String n) throws DuplicateNameException { + try (UndoableTransaction tid = tb.startTransaction()) { + TraceObjectThread thread = (TraceObjectThread) super.addThread(n); + TraceObjectKeyPath regsPath = thread.getObject().getCanonicalPath().extend("Registers"); + tb.trace.getObjectManager() + .createObject(regsPath) + .insert(thread.getLifespan(), ConflictResolution.DENY); + return thread; + } + } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java index c40a2e6b8d..cd7920221a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java @@ -405,7 +405,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemorySpace regVals = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false); - row.setRawValueString("0x1234"); + runSwing(() -> row.setRawValueString("0x1234")); waitForPass(() -> { long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); @@ -414,7 +414,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI assertEquals("0x1234", row.getRawValueString()); }); - row.setRawValueString("1234"); // Decimal this time + runSwing(() -> row.setRawValueString("1234")); // Decimal this time waitForPass(() -> { long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); @@ -436,7 +436,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemorySpace regVals = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, false); - row.setValueString("1234"); + runSwing(() -> row.setValueString("1234")); waitForPass(() -> { long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); @@ -456,7 +456,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI // Wait for row to settle. TODO: Why is this necessary? waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress())); - row.setRawValueString("0x1234"); + runSwing(() -> row.setRawValueString("0x1234")); waitForPass(() -> { long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); @@ -468,7 +468,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI // Wait for row to settle. TODO: Why is this necessary? waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress())); - row.setRawValueString("{ 12 34 56 78 9a bc de f0 }"); + runSwing(() -> row.setRawValueString("{ 12 34 56 78 9a bc de f0 }")); waitForPass(() -> { long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); @@ -491,7 +491,9 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryOperations mem = tb.trace.getMemoryManager(); ByteBuffer buf = ByteBuffer.allocate(8); - row.setValueString("1234"); + // Wait for row to settle. TODO: Why is this necessary? + waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress())); + runSwing(() -> row.setValueString("1234")); waitForPass(() -> { long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); @@ -516,7 +518,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryOperations mem = tb.trace.getMemoryManager(); ByteBuffer buf = ByteBuffer.allocate(14); - row.setValueString("\"Hello, World!\""); + runSwing(() -> row.setValueString("\"Hello, World!\"")); waitForPass(() -> { long viewSnap = traceManager.getCurrent().getViewSnap(); assertTrue(DBTraceUtils.isScratch(viewSnap)); @@ -561,7 +563,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI public void testEditRegisterTarget() throws Throwable { WatchRow row = prepareTestEditTarget("r0"); - row.setRawValueString("0x1234"); + runSwing(() -> row.setRawValueString("0x1234")); retryVoid(() -> { assertArrayEquals(mb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), bank.regVals.get("r0")); }, List.of(AssertionError.class)); @@ -573,7 +575,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI // Wait for the async reads to settle. waitForPass(() -> assertEquals(tb.addr(0x00400000), row.getAddress())); - row.setRawValueString("0x1234"); + runSwing(() -> row.setRawValueString("0x1234")); retryVoid(() -> { assertArrayEquals(tb.arr(0, 0, 0, 0, 0, 0, 0x12, 0x34), waitOn(mb.testProcess1.memory.readMemory(mb.addr(0x00400000), 8))); @@ -588,7 +590,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI assertFalse(recorder.isRegisterOnTarget(thread, r1)); assertFalse(row.isRawValueEditable()); - row.setRawValueString("0x1234"); + runSwingWithException(() -> row.setRawValueString("0x1234")); } protected void setupUnmappedDataSection() throws Throwable { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java index 92d086de3d..e86e5c4aa4 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java @@ -32,8 +32,8 @@ import ghidra.app.services.DebuggerStateEditingService.StateEditingMode; import ghidra.app.services.DebuggerStateEditingService.StateEditor; import ghidra.app.services.TraceRecorder; import ghidra.dbg.target.TargetRegisterBank; -import ghidra.pcode.exec.AsyncPcodeExecutor; import ghidra.pcode.exec.DebuggerPcodeUtils; +import ghidra.pcode.exec.PcodeExecutor; import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemoryAccessException; import ghidra.trace.database.DBTraceUtils; @@ -143,8 +143,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge try (UndoableTransaction tid = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread TraceThread thread = tb.getOrAddThread("Threads[0]", 0); - AsyncPcodeExecutor executor = DebuggerPcodeUtils - .executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); + PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates( + env.getTool(), DebuggerCoordinates.NOWHERE.thread(thread)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); asm.assemble(tb.addr(0x00400000), "imm r0,#123"); @@ -181,8 +181,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge try (UndoableTransaction tid = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread thread = tb.getOrAddThread("Threads[0]", 0); - AsyncPcodeExecutor executor = DebuggerPcodeUtils - .executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); + PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates( + env.getTool(), DebuggerCoordinates.NOWHERE.thread(thread)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); asm.assemble(tb.addr(0x00400000), "imm r0,#123"); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java index 977dbb3860..956e351005 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServiceTest.java @@ -18,24 +18,29 @@ package ghidra.app.plugin.core.debug.service.emulation; import static org.junit.Assert.*; import java.math.BigInteger; +import java.util.concurrent.CompletableFuture; import org.junit.Before; import org.junit.Test; import org.junit.experimental.categories.Category; +import com.google.common.collect.Range; + import generic.Unique; import generic.test.category.NightlyCategory; import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.services.DebuggerStaticMappingService; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.*; import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryBlock; import ghidra.program.util.ProgramLocation; -import ghidra.trace.model.Trace; +import ghidra.trace.model.*; +import ghidra.trace.model.memory.TraceMemoryManager; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.schedule.TraceSchedule; @@ -202,22 +207,75 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU Trace trace = traceManager.getCurrentTrace(); assertNotNull(trace); - TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads()); - TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false); + TraceMemoryManager mem = trace.getMemoryManager(); assertEquals(new BigInteger("000100", 16), - regs.getViewValue(0, regPC).getUnsignedValue()); - assertEquals(new BigInteger("0000", 16), regs.getViewValue(0, regW0).getUnsignedValue()); + mem.getViewValue(0, regPC).getUnsignedValue()); + assertEquals(new BigInteger("0000", 16), + mem.getViewValue(0, regW0).getUnsignedValue()); assertEquals(new BigInteger("0800", 16), - regs.getViewValue(0, regW1).getUnsignedValue()); + mem.getViewValue(0, regW1).getUnsignedValue()); long scratch = emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); assertEquals(new BigInteger("000102", 16), - regs.getViewValue(scratch, regPC).getUnsignedValue()); + mem.getViewValue(scratch, regPC).getUnsignedValue()); assertEquals(new BigInteger("1234", 16), - regs.getViewValue(scratch, regW0).getUnsignedValue()); + mem.getViewValue(scratch, regW0).getUnsignedValue()); assertEquals(new BigInteger("0800", 16), - regs.getViewValue(scratch, regW1).getUnsignedValue()); + mem.getViewValue(scratch, regW1).getUnsignedValue()); + } + + @Test + public void testPureEmulationRelocated() throws Throwable { + createAndOpenTrace("x86:LE:64:default"); + createProgramFromTrace(); + + intoProject(program); + intoProject(tb.trace); + + Assembler asm = Assemblers.getAssembler(program); + Memory memory = program.getMemory(); + Address addrText = addr(program, 0x00400000); + Address addrData = addr(program, 0x00600000); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { + MemoryBlock blockText = memory.createInitializedBlock("text", addrText, 0x1000, + (byte) 0, TaskMonitor.DUMMY, false); + blockText.setExecute(true); + memory.createInitializedBlock(".data", addrData, 0x1000, (byte) 0, TaskMonitor.DUMMY, + false); + + // NOTE: qword ptr [0x00600800] is RIP-relative + asm.assemble(addrText, "MOV RAX, qword ptr [0x00600800]"); + memory.setLong(addr(program, 0x00600800), 0xdeadbeefcafebabeL); + } + + programManager.openProgram(program); + waitForSwing(); + + DebuggerStaticMappingService mappings = + tool.getService(DebuggerStaticMappingService.class); + CompletableFuture settled; + TraceThread thread; + TraceMemorySpace regs; + try (UndoableTransaction tid = tb.startTransaction()) { + thread = tb.getOrAddThread("Threads[0]", 0); + regs = tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); + regs.setValue(0, new RegisterValue(program.getLanguage().getProgramCounter(), + BigInteger.valueOf(0x55550000))); + settled = mappings.changesSettled(); + mappings.addMapping(new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), + tb.addr(0x55550000)), new ProgramLocation(program, addrText), 0x1000, false); + mappings.addMapping(new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), + tb.addr(0x55750000)), new ProgramLocation(program, addrData), 0x1000, false); + } + waitForSwing(); + waitOn(settled); + + long scratch = + emulationPlugin.emulate(tb.trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); + + assertEquals("deadbeefcafebabe", + regs.getViewValue(scratch, tb.reg("RAX")).getUnsignedValue().toString(16)); } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java index 643ab26953..8c8e8c2373 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java @@ -448,6 +448,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); + waitRecorder(recorder); Trace trace = recorder.getTrace(); traceManager.openTrace(trace); @@ -464,6 +465,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge waitOn(mb.testModel.session.requestFocus(mb.testThread1)); TraceThread thread1 = recorder.getTraceThread(mb.testThread1); + assertNotNull(thread1); waitForPass(() -> assertEquals(thread1, traceManager.getCurrentThread())); TestTargetStack stack = mb.testThread1.addStack(); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java similarity index 79% rename from Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java rename to Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java index c74c232d0e..bea813317f 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderPcodeExecTest.java @@ -22,18 +22,25 @@ import java.util.Map; import org.junit.Test; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.mapping.DebuggerRegisterMapper; +import ghidra.app.plugin.core.debug.service.emulation.data.PcodeDebuggerAccess; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.services.ActionSource; import ghidra.app.services.TraceRecorder; import ghidra.dbg.model.TestTargetRegisterBankInThread; +import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorState; import ghidra.pcode.utils.Utils; import ghidra.program.model.lang.Register; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; -public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebuggerGUITest { +/** + * Test the {@link DirectBytesTracePcodeExecutorState} in combination with + * {@link PcodeDebuggerAccess} to ensure it read and writes the target when appropriate. + */ +public class TraceRecorderPcodeExecTest extends AbstractGhidraHeadedDebuggerGUITest { @Test public void testExecutorEval() throws Throwable { @@ -70,11 +77,11 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge assertTrue(rm.getRegistersOnTarget().contains(r1)); }); - AsyncPcodeExecutor executor = - new AsyncPcodeExecutor<>(language, AsyncWrappedPcodeArithmetic.forLanguage(language), - new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0)); + PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, + DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread)); - byte[] result = waitOn(expr.evaluate(executor)); + // In practice, this should be backgrounded, but we're in a test thread + byte[] result = expr.evaluate(executor); assertEquals(11, Utils.bytesToLong(result, result.length, language.isBigEndian())); } @@ -92,6 +99,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, createTargetTraceMapper(mb.testProcess1), ActionSource.AUTOMATIC); + waitRecorder(recorder); TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); Trace trace = recorder.getTrace(); @@ -113,13 +121,12 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge assertTrue(rm.getRegistersOnTarget().contains(r1)); }); - TraceRecorderAsyncPcodeExecutorState asyncState = - new TraceRecorderAsyncPcodeExecutorState(recorder, recorder.getSnap(), thread, 0); - AsyncPcodeExecutor executor = new AsyncPcodeExecutor<>( - language, AsyncWrappedPcodeArithmetic.forLanguage(language), asyncState); + PcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(tool, + DebuggerCoordinates.NOWHERE.recorder(recorder).thread(thread)); - waitOn(executor.executeAsync(prog, PcodeUseropLibrary.nil())); - waitOn(asyncState.getVar(language.getRegister("r2"))); + executor.execute(prog, PcodeUseropLibrary.nil()); + // Ignore return value. We'll assert that it got written to the trace + executor.state.getVar(language.getRegister("r2")); assertEquals(BigInteger.valueOf(11), new BigInteger(1, regs.regVals.get("r2"))); } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java index 8b97c8fd5c..9b78f6a297 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java @@ -21,8 +21,7 @@ import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import ghidra.dbg.agent.DefaultTargetObject; -import ghidra.dbg.target.TargetAggregate; -import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.*; import ghidra.dbg.target.schema.DefaultTargetObjectSchema.DefaultAttributeSchema; import ghidra.dbg.util.*; import ghidra.dbg.util.CollectionUtils.Delta; @@ -906,4 +905,82 @@ public interface TargetObjectSchema { schema.validateTypeAndInterfaces(element, parentPath, ent.getKey(), strict); } } + + /** + * Search for a suitable register container + * + *

+ * This will try with and without considerations for frames. If the schema indicates that + * register containers are not contained within frames, then frameLevel must be 0, otherwise + * this will return empty. If dependent on frameLevel, this will return two singleton paths: one + * for a decimal index and another for a hexadecimal index. If not, this will return a singleton + * path. If it fails to find a unique container, this will return empty. + * + *

+ * NOTE: This must be used at the top of the search scope, probably the root schema. For + * example, to search the entire model for a register container related to {@code myObject}: + * + *

+	 * for (PathPattern regPattern : myObject.getModel()
+	 * 		.getSchema()
+	 * 		.searchForRegisterContainer(0, myObject.getPath())) {
+	 * 	TargetObject objRegs = myObject.getModel().getModelObject(regPattern.getSingletonPath());
+	 * 	if (objRegs != null) {
+	 * 		// found it
+	 * 	}
+	 * }
+	 * 
+ * + *

+ * This places some conventional restrictions / expectations on models where registers are given + * on a frame-by-frame basis. The schema should present the {@link TargetRegisterContainer} as + * the same object or a successor to {@link TargetStackFrame}, which must in turn be a successor + * to {@link TargetThread}. The frame level (usually an index) must be in the path from thread + * to stack frame. There can be no wild cards between the frame and the register container. For + * example, the container for {@code Threads[1]} may be {@code Threads[1].Stack[n].Registers}, + * where {@code n} is the frame level. {@code Threads[1]} would have the {@link TargetThread} + * interface, {@code Threads[1].Stack[0]} would have the {@link TargetStackFrame} interface, and + * {@code Threads[1].Stack[0].Registers} would have the {@link TargetRegisterContainer} + * interface. Note it is not sufficient for {@link TargetRegisterContainer} to be a successor of + * {@link TargetThread} with a single index between. There must be an intervening + * {@link TargetStackFrame}, and the frame level (index) must precede it. + * + * @param frameLevel the frameLevel, must be 0 if not applicable + * @path the path of the seed object relative to the root + * @return the predicates where the register container should be found, possibly empty + */ + default PathPredicates searchForRegisterContainer(int frameLevel, List path) { + List simple = searchForSuitable(TargetRegisterContainer.class, path); + if (simple != null) { + return frameLevel == 0 ? PathPredicates.pattern(simple) : PathPredicates.EMPTY; + } + List threadPath = searchForAncestor(TargetThread.class, path); + if (threadPath == null) { + return PathPredicates.EMPTY; + } + PathPattern framePatternRelThread = + getSuccessorSchema(threadPath).searchFor(TargetStackFrame.class, false) + .getSingletonPattern(); + if (framePatternRelThread == null) { + return PathPredicates.EMPTY; + } + + if (framePatternRelThread.countWildcards() != 1) { + return null; + } + + PathMatcher result = new PathMatcher(); + for (String index : List.of(Integer.toString(frameLevel), + "0x" + Integer.toHexString(frameLevel))) { + List framePathRelThread = + framePatternRelThread.applyKeys(index).getSingletonPath(); + List framePath = PathUtils.extend(threadPath, framePathRelThread); + List regsPath = + searchForSuitable(TargetRegisterContainer.class, framePath); + if (regsPath != null) { + result.addPattern(regsPath); + } + } + return result; + } } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathMatcher.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathMatcher.java index abcc33a7d1..6e57476d70 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathMatcher.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathMatcher.java @@ -118,6 +118,11 @@ public class PathMatcher implements PathPredicates { return patterns.iterator().next(); } + @Override + public Collection getPatterns() { + return patterns; + } + protected void coalesceWilds(Set result) { if (result.contains("")) { result.removeIf(PathUtils::isName); @@ -179,10 +184,10 @@ public class PathMatcher implements PathPredicates { } @Override - public PathMatcher applyKeys(List indices) { + public PathMatcher applyKeys(Align align, List indices) { PathMatcher result = new PathMatcher(); for (PathPattern pat : patterns) { - result.addPattern(pat.applyKeys(indices)); + result.addPattern(pat.applyKeys(align, indices)); } return result; } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPattern.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPattern.java index 49c14723bf..d0c7d906fa 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPattern.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPattern.java @@ -199,6 +199,11 @@ public class PathPattern implements PathPredicates { return this; } + @Override + public Collection getPatterns() { + return List.of(this); + } + @Override public Set getNextKeys(List path) { if (path.size() >= pattern.size()) { @@ -257,22 +262,26 @@ public class PathPattern implements PathPredicates { } @Override - public PathPattern applyKeys(List indices) { - List result = new ArrayList<>(pattern.size()); - Iterator it = indices.iterator(); - for (String pat : pattern) { - if (it.hasNext() && isWildcard(pat)) { - String index = it.next(); + public PathPattern applyKeys(Align align, List indices) { + List result = Arrays.asList(new String[pattern.size()]); + ListIterator iit = align.iterator(indices); + ListIterator pit = align.iterator(pattern); + + while (pit.hasNext()) { + int i = pit.nextIndex(); + String pat = pit.next(); + if (iit.hasNext() && isWildcard(pat)) { + String index = iit.next(); if (PathUtils.isIndex(pat)) { - result.add(PathUtils.makeKey(index)); + result.set(i, PathUtils.makeKey(index)); } else { // NB. Rare for attribute wildcards, but just in case - result.add(index); + result.set(i, index); } } else { - result.add(pat); + result.set(i, pat); } } return new PathPattern(result); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java index 29f4e63bbf..1a968a8c8f 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java @@ -17,13 +17,108 @@ package ghidra.dbg.util; import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.stream.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; import ghidra.async.AsyncFence; import ghidra.dbg.target.TargetObject; import ghidra.dbg.util.PathUtils.PathComparator; +import ghidra.util.ReversedListIterator; public interface PathPredicates { + PathPredicates EMPTY = new PathPredicates() { + @Override + public PathPredicates or(PathPredicates that) { + return that; + } + + @Override + public boolean matches(List path) { + return false; + } + + @Override + public boolean successorCouldMatch(List path, boolean strict) { + return false; + } + + @Override + public boolean ancestorMatches(List path, boolean strict) { + return false; + } + + @Override + public boolean ancestorCouldMatchRight(List path, boolean strict) { + return false; + } + + @Override + public Set getNextKeys(List path) { + return Set.of(); + } + + @Override + public Set getNextNames(List path) { + return Set.of(); + } + + @Override + public Set getNextIndices(List path) { + return Set.of(); + } + + @Override + public Set getPrevKeys(List path) { + return Set.of(); + } + + @Override + public List getSingletonPath() { + return null; + } + + @Override + public PathPattern getSingletonPattern() { + return null; + } + + @Override + public Collection getPatterns() { + return List.of(); + } + + @Override + public PathPredicates removeRight(int count) { + return this; + } + + @Override + public PathPredicates applyKeys(Align align, List keys) { + return this; + } + + @Override + public boolean isEmpty() { + return true; + } + }; + + enum Align { + LEFT { + @Override + ListIterator iterator(List list) { + return list.listIterator(); + } + }, + RIGHT { + @Override + ListIterator iterator(List list) { + return new ReversedListIterator<>(list.listIterator(list.size())); + } + }; + + abstract ListIterator iterator(List list); + } static boolean keyMatches(String pat, String key) { if (key.equals(pat)) { @@ -163,6 +258,13 @@ public interface PathPredicates { */ PathPattern getSingletonPattern(); + /** + * Get the patterns of this predicate + * + * @return the patterns + */ + Collection getPatterns(); + /** * Remove count elements from the right * @@ -313,33 +415,44 @@ public interface PathPredicates { * Substitute wildcards from left to right for the given list of keys * *

- * Takes each pattern and substitutes its wildcards for the given indices, starting from the - * left and working right. This object is unmodified, and the result is returned. + * Takes each pattern and substitutes its wildcards for the given indices, according to the + * given alignment. This object is unmodified, and the result is returned. * *

- * If there are fewer wildcards in a pattern than given, only the left-most keys are taken. If - * there are fewer keys than wildcards in a pattern, then the right-most wildcards are left in - * the resulting pattern. + * If there are fewer wildcards in a pattern than given, only the first keys are taken. If there + * are fewer keys than wildcards in a pattern, then the remaining wildcards are left in the + * resulting pattern. In this manner, the left-most wildcards are substituted for the left-most + * indices, or the right-most wildcards are substituted for the right-most indices, depending on + * the alignment. * + * @param align the end to align * @param keys the keys to substitute * @return the pattern or matcher with the applied substitutions */ - PathPredicates applyKeys(List keys); + PathPredicates applyKeys(Align align, List keys); - default PathPredicates applyKeys(Stream keys) { - return applyKeys(keys.collect(Collectors.toList())); + default PathPredicates applyKeys(Align align, String... keys) { + return applyKeys(align, List.of(keys)); } default PathPredicates applyKeys(String... keys) { - return applyKeys(List.of(keys)); + return applyKeys(Align.LEFT, keys); } - default PathPredicates applyIntKeys(int radix, List keys) { - return applyKeys(keys.stream().map(k -> Integer.toString(k, radix))); + default PathPredicates applyIntKeys(int radix, Align align, List keys) { + return applyKeys(align, + keys.stream().map(k -> Integer.toString(k, radix)).collect(Collectors.toList())); + } + + default PathPredicates applyIntKeys(int radix, Align align, int... keys) { + return applyKeys(align, + IntStream.of(keys) + .mapToObj(k -> Integer.toString(k, radix)) + .collect(Collectors.toList())); } default PathPredicates applyIntKeys(int... keys) { - return applyKeys(IntStream.of(keys).mapToObj(k -> Integer.toString(k))); + return applyIntKeys(10, Align.LEFT, keys); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java index 98704c9c0a..97a43898b9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -18,11 +18,9 @@ package ghidra.pcode.exec.trace; import com.google.common.collect.RangeSet; import com.google.common.primitives.UnsignedLong; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; -import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; -import ghidra.trace.model.thread.TraceThread; /** * A state piece which can check for uninitialized reads @@ -30,16 +28,16 @@ import ghidra.trace.model.thread.TraceThread; *

* Depending on the use case, it may be desirable to ensure all reads through the course of * emulation are from initialized parts of memory. For traces, there's an additional consideration - * as to whether the values are present, but state. Again, depending on the use case, that may be + * as to whether the values are present, but stale. Again, depending on the use case, that may be * acceptable. See the extensions of this class for "stock" implementations. */ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece extends BytesTracePcodeExecutorStatePiece { protected class CheckedCachedSpace extends CachedSpace { - public CheckedCachedSpace(Language language, AddressSpace space, TraceMemorySpace source, - long snap) { - super(language, space, source, snap); + public CheckedCachedSpace(Language language, AddressSpace space, + PcodeTraceDataAccess backing) { + super(language, space, backing); } @Override @@ -54,31 +52,35 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiec } } - public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap, - TraceThread thread, int frame) { - super(trace, snap, thread, frame); + /** + * Construct a piece + * + * @param data the trace-data access shim + */ + public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) { + super(data); } @Override protected AbstractSpaceMap newSpaceMap() { return new TraceBackedSpaceMap() { @Override - protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { - return new CheckedCachedSpace(language, space, backing, snap); + protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) { + return new CheckedCachedSpace(language, space, backing); } }; } /** - * Decide what to do, give that a portion of a read is uninitialized + * Decide what to do, given that a portion of a read is uninitialized * - * @param backing the object backing the address space that was read + * @param backing the shim backing the address space that was read * @param start the starting address of the requested read * @param size the size of the requested read * @param uninitialized the portion of the read that is uninitialized * @return the adjusted size of the read * @throws Exception to interrupt the emulator */ - protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size, - AddressSet uninitialized); + protected abstract int checkUninitialized(PcodeTraceDataAccess backing, Address start, + int size, AddressSet uninitialized); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java index eb9d79cd2e..933988ec9a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java @@ -15,51 +15,54 @@ */ package ghidra.pcode.exec.trace; -import ghidra.pcode.emu.PcodeEmulator; -import ghidra.pcode.emu.PcodeThread; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.emu.*; +import ghidra.pcode.exec.trace.data.*; +import ghidra.trace.model.guest.TracePlatform; /** * An emulator that can read initial state from a trace and record its state back into it */ public class BytesTracePcodeEmulator extends PcodeEmulator implements TracePcodeMachine { - protected final Trace trace; - protected final long snap; + protected final PcodeTraceAccess access; /** * Create a trace-bound emulator * - * @param trace the trace - * @param snap the snap from which it lazily reads its state + * @param access the trace access shim */ - public BytesTracePcodeEmulator(Trace trace, long snap) { - super(trace.getBaseLanguage()); - this.trace = trace; - this.snap = snap; + public BytesTracePcodeEmulator(PcodeTraceAccess access) { + super(access.getLanguage()); + this.access = access; + } + + /** + * Create a trace-bound emulator + * + * @param platform the platform to emulate + * @param snap the source snap + */ + public BytesTracePcodeEmulator(TracePlatform platform, long snap) { + this(new DefaultPcodeTraceAccess(platform, snap)); } @Override - public Trace getTrace() { - return trace; + protected BytesPcodeThread createThread(String name) { + BytesPcodeThread thread = super.createThread(name); + access.getDataForLocalState(thread, 0).initializeThreadContext(thread); + return thread; } - @Override - public long getSnap() { - return snap; - } - - protected TracePcodeExecutorState newState(TraceThread thread) { - return new BytesTracePcodeExecutorState(trace, snap, thread, 0); + protected TracePcodeExecutorState newState(PcodeTraceDataAccess data) { + return new BytesTracePcodeExecutorState(data); } @Override public TracePcodeExecutorState createSharedState() { - return newState(null); + return newState(access.getDataForSharedState()); } @Override public TracePcodeExecutorState createLocalState(PcodeThread thread) { - return newState(getTraceThread(thread)); + return newState(access.getDataForLocalState(thread, 0)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java index 0c34dab9c3..ecb33504be 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java @@ -15,22 +15,18 @@ */ package ghidra.pcode.exec.trace; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; /** * A state composing a single {@link BytesTracePcodeExecutorStatePiece} */ -class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState { +public class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState { /** * Create the state * - * @param trace the trace from which bytes are loaded - * @param snap the snap from which bytes are loaded - * @param thread if applicable, the thread identifying the register space - * @param frame if applicable, the frame identifying the register space + * @param data the trace-data access shim */ - public BytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) { - super(new BytesTracePcodeExecutorStatePiece(trace, snap, thread, frame)); + public BytesTracePcodeExecutorState(PcodeTraceDataAccess data) { + super(new BytesTracePcodeExecutorStatePiece(data)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java index 7563b23aba..4a1bd40fee 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java @@ -17,17 +17,16 @@ package ghidra.pcode.exec.trace; import java.nio.ByteBuffer; -import com.google.common.collect.*; +import com.google.common.collect.Range; +import com.google.common.collect.RangeSet; import com.google.common.primitives.UnsignedLong; import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece; import ghidra.pcode.exec.BytesPcodeExecutorStateSpace; import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; -import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; -import ghidra.trace.model.thread.TraceThread; import ghidra.util.MathUtilities; /** @@ -42,35 +41,20 @@ public class BytesTracePcodeExecutorStatePiece extends AbstractBytesPcodeExecutorStatePiece implements TracePcodeExecutorStatePiece { - protected final Trace trace; - protected final long snap; - protected final TraceThread thread; - protected final int frame; - - public BytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, - int frame) { - super(trace.getBaseLanguage()); - this.trace = trace; - this.snap = snap; - this.thread = thread; - this.frame = frame; - } - - protected static class CachedSpace extends BytesPcodeExecutorStateSpace { + protected static class CachedSpace + extends BytesPcodeExecutorStateSpace { protected final AddressSet written = new AddressSet(); - protected final long snap; - public CachedSpace(Language language, AddressSpace space, TraceMemorySpace backing, - long snap) { + public CachedSpace(Language language, AddressSpace space, PcodeTraceDataAccess backing) { + // Backing could be null, so we need language parameter super(language, space, backing); - this.snap = snap; } @Override public void write(long offset, byte[] val, int srcOffset, int length) { super.write(offset, val, srcOffset, length); Address loc = space.getAddress(offset); - Address end = loc.addWrap(length); + Address end = loc.addWrap(length - 1); if (loc.compareTo(end) <= 0) { written.add(loc, end); } @@ -91,7 +75,7 @@ public class BytesTracePcodeExecutorStatePiece long lower = lower(toRead); long upper = upper(toRead); ByteBuffer buf = ByteBuffer.allocate((int) (upper - lower + 1)); - backing.getBytes(snap, space.getAddress(lower), buf); + backing.getBytes(space.getAddress(lower), buf); for (Range rng : uninitialized.asRanges()) { long l = lower(rng); long u = upper(rng); @@ -100,19 +84,17 @@ public class BytesTracePcodeExecutorStatePiece } } - protected void warnUnknown(AddressSet unknown) { + protected void warnUnknown(AddressSetView unknown) { warnAddressSet("Emulator state initialized from UNKNOWN", unknown); } // Must already have started a transaction - protected void writeDown(Trace trace, long snap, TraceThread thread, int frame) { + protected void writeDown(PcodeTraceDataAccess into) { if (space.isUniqueSpace()) { return; } byte[] data = new byte[4096]; ByteBuffer buf = ByteBuffer.wrap(data); - TraceMemorySpace mem = - TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true); for (AddressRange range : written) { long lower = range.getMinAddress().getOffset(); long fullLen = range.getLength(); @@ -121,7 +103,7 @@ public class BytesTracePcodeExecutorStatePiece bytes.getData(lower, data, 0, len); buf.position(0); buf.limit(len); - mem.putBytes(snap, space.getAddress(lower), buf); + into.putBytes(space.getAddress(lower), buf); lower += len; fullLen -= len; @@ -130,64 +112,47 @@ public class BytesTracePcodeExecutorStatePiece } } - /** - * Get the state's source trace - * - * @return the trace - */ - public Trace getTrace() { - return trace; - } + protected final PcodeTraceDataAccess data; /** - * Get the source snap + * Create a concrete state piece backed by a trace * - * @return the snap + * @param data the trace-data access shim */ - public long getSnap() { - return snap; - } - - /** - * Get the source thread, if a local state - * - * @return the thread - */ - public TraceThread getThread() { - return thread; - } - - /** - * Get the source frame, if a local state - * - * @return the frame, probably 0 - */ - public int getFrame() { - return frame; + public BytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { + super(data.getLanguage()); + this.data = data; } @Override - public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { - if (trace.getBaseLanguage() != language) { - throw new IllegalArgumentException("Destination trace must be same language as source"); + public PcodeTraceDataAccess getData() { + return data; + } + + @Override + public void writeDown(PcodeTraceDataAccess into) { + if (into.getLanguage() != language) { + throw new IllegalArgumentException( + "Destination platform must be same language as source"); } for (CachedSpace cached : spaceMap.values()) { - cached.writeDown(trace, snap, thread, frame); + cached.writeDown(into); } } /** * A space map which binds spaces to corresponding spaces in the trace */ - protected class TraceBackedSpaceMap extends CacheingSpaceMap { + protected class TraceBackedSpaceMap + extends CacheingSpaceMap { @Override - protected TraceMemorySpace getBacking(AddressSpace space) { - return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false); + protected PcodeTraceDataAccess getBacking(AddressSpace space) { + return data; } @Override - protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { - return new CachedSpace(language, space, backing, snap); + protected CachedSpace newSpace(AddressSpace space, PcodeTraceDataAccess backing) { + return new CachedSpace(language, space, backing); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java index ee664b416c..1636d8ff68 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java @@ -16,8 +16,7 @@ package ghidra.pcode.exec.trace; import ghidra.pcode.exec.DefaultPcodeExecutorState; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; /** * An adapter that implements {@link TracePcodeExecutorState} given a @@ -41,7 +40,12 @@ public class DefaultTracePcodeExecutorState extends DefaultPcodeExecutorState } @Override - public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { - piece.writeDown(trace, snap, thread, frame); + public PcodeTraceDataAccess getData() { + return piece.getData(); + } + + @Override + public void writeDown(PcodeTraceDataAccess into) { + piece.writeDown(into); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java index 5fb30d4e50..276b349cb0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java @@ -17,42 +17,60 @@ package ghidra.pcode.exec.trace; import org.apache.commons.lang3.tuple.Pair; -import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PairedPcodeExecutorState; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.pcode.exec.trace.data.DefaultPcodeTraceAccess; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; /** * A state composing a single {@link DirectBytesTracePcodeExecutorStatePiece} * - *

- * Note this does not implement {@link DefaultTracePcodeExecutorState} because it treats the trace - * as if it were a stand-alone state. The interface expects implementations to lazily load into a - * cache and write it back down later. This does not do that. - * * @see TraceSleighUtils */ -public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorState { - private final Trace trace; - private final long snap; - private final TraceThread thread; - private final int frame; +public class DirectBytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState { + + /** + * Get a trace-data access shim suitable for evaluating Sleigh expressions with thread context + * + *

+ * Do not use the returned shim for emulation, but only for one-off p-code execution, e.g., + * Sleigh expression evaluation. + * + * @param platform the platform whose language and address mappings to use + * @param snap the source snap + * @param thread the thread for register context + * @param frame the frame for register context, 0 if not applicable + * @return the trace-data access shim + */ + public static PcodeTraceDataAccess getDefaultThreadAccess(TracePlatform platform, long snap, + TraceThread thread, int frame) { + return new DefaultPcodeTraceAccess(platform, snap).getDataForThreadState(thread, frame); + } /** * Create the state * - * @param trace the trace the executor will access + * @param data the trace-data access shim + */ + public DirectBytesTracePcodeExecutorState(PcodeTraceDataAccess data) { + super(new DirectBytesTracePcodeExecutorStatePiece(data)); + } + + /** + * Create the state + * + * @param platform the platform whose language and address mappings to use * @param snap the snap the executor will access * @param thread the thread for reading and writing registers * @param frame the frame for reading and writing registers */ - public DirectBytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread, + public DirectBytesTracePcodeExecutorState(TracePlatform platform, long snap, TraceThread thread, int frame) { - super(new DirectBytesTracePcodeExecutorStatePiece(trace, snap, thread, frame)); - this.trace = trace; - this.snap = snap; - this.thread = thread; - this.frame = frame; + this(getDefaultThreadAccess(platform, snap, thread, frame)); } /** @@ -63,6 +81,6 @@ public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorStat */ public PcodeExecutorState> withMemoryState() { return new PairedPcodeExecutorState<>(this, - new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)); + new TraceMemoryStatePcodeExecutorStatePiece(getData())); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java index 1dd8aa363e..bac6441d59 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java @@ -17,20 +17,18 @@ package ghidra.pcode.exec.trace; import java.nio.ByteBuffer; +import javax.help.UnsupportedOperationException; + import org.apache.commons.lang3.tuple.Pair; import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.lang.Language; import ghidra.program.model.mem.MemBuffer; -import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemoryState; -import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.util.DefaultTraceTimeViewport; /** * An executor state piece that operates directly on trace memory and registers @@ -45,37 +43,37 @@ import ghidra.trace.util.DefaultTraceTimeViewport; * @see TraceSleighUtils */ public class DirectBytesTracePcodeExecutorStatePiece - extends AbstractLongOffsetPcodeExecutorStatePiece { + extends AbstractLongOffsetPcodeExecutorStatePiece + implements TracePcodeExecutorStatePiece { + + protected final PcodeTraceDataAccess data; protected final SemisparseByteArray unique = new SemisparseByteArray(); - private final Trace trace; - private long snap; - private TraceThread thread; - private int frame; - private final DefaultTraceTimeViewport viewport; - - protected DirectBytesTracePcodeExecutorStatePiece(Language language, - PcodeArithmetic arithmetic, Trace trace, long snap, TraceThread thread, - int frame) { - super(language, arithmetic, arithmetic); - this.trace = trace; - this.snap = snap; - this.thread = thread; - this.frame = frame; - - this.viewport = new DefaultTraceTimeViewport(trace); - this.viewport.setSnap(snap); + /** + * Construct a piece + * + * @param arithmetic the arithmetic for byte arrays + * @param data the trace-data access shim + */ + protected DirectBytesTracePcodeExecutorStatePiece(PcodeArithmetic arithmetic, + PcodeTraceDataAccess data) { + super(data.getLanguage(), arithmetic, arithmetic); + this.data = data; } - protected DirectBytesTracePcodeExecutorStatePiece(Language language, Trace trace, long snap, - TraceThread thread, int frame) { - this(language, BytesPcodeArithmetic.forLanguage(language), trace, snap, thread, frame); + /** + * Construct a piece + * + * @param data the trace-data access shim + */ + protected DirectBytesTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { + this(BytesPcodeArithmetic.forLanguage(data.getLanguage()), data); } - public DirectBytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, - int frame) { - this(trace.getBaseLanguage(), trace, snap, thread, frame); + @Override + public PcodeTraceDataAccess getData() { + return data; } /** @@ -91,74 +89,7 @@ public class DirectBytesTracePcodeExecutorStatePiece */ public PcodeExecutorStatePiece> withMemoryState() { return new PairedPcodeExecutorStatePiece<>(this, - new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)); - } - - /** - * Get the trace - * - * @return the trace - */ - public Trace getTrace() { - return trace; - } - - /** - * Re-bind this state to another snap - * - * @param snap the new snap - */ - public void setSnap(long snap) { - this.snap = snap; - this.viewport.setSnap(snap); - } - - /** - * Get the current snap - * - * @return the snap - */ - public long getSnap() { - return snap; - } - - /** - * Re-bind this state to another thread - * - * @param thread the new thread - */ - public void setThread(TraceThread thread) { - if (thread != null & thread.getTrace() != trace) { - throw new IllegalArgumentException("Thread, if given, must be part of the same trace"); - } - this.thread = thread; - } - - /** - * Get the current thread - * - * @return the thread - */ - public TraceThread getThread() { - return thread; - } - - /** - * Re-bind this state to another frame - * - * @param frame the new frame - */ - public void setFrame(int frame) { - this.frame = frame; - } - - /** - * Get the current frame - * - * @return the frame - */ - public int getFrame() { - return frame; + new TraceMemoryStatePcodeExecutorStatePiece(data)); } @Override @@ -175,24 +106,23 @@ public class DirectBytesTracePcodeExecutorStatePiece } @Override - protected TraceMemorySpace getForSpace(AddressSpace space, boolean toWrite) { - return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, toWrite); + protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) { + return space; } @Override - protected void setInSpace(TraceMemorySpace space, long offset, int size, byte[] val) { + protected void setInSpace(AddressSpace space, long offset, int size, byte[] val) { assert size == val.length; - int wrote = - space.putBytes(snap, space.getAddressSpace().getAddress(offset), ByteBuffer.wrap(val)); + int wrote = data.putBytes(space.getAddress(offset), ByteBuffer.wrap(val)); if (wrote != size) { throw new RuntimeException("Could not write full value to trace"); } } @Override - protected byte[] getFromSpace(TraceMemorySpace space, long offset, int size) { + protected byte[] getFromSpace(AddressSpace space, long offset, int size) { ByteBuffer buf = ByteBuffer.allocate(size); - int read = space.getViewBytes(snap, space.getAddressSpace().getAddress(offset), buf); + int read = data.getBytes(space.getAddress(offset), buf); if (read != size) { throw new RuntimeException("Could not read full value from trace"); } @@ -201,6 +131,11 @@ public class DirectBytesTracePcodeExecutorStatePiece @Override public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { - return trace.getMemoryManager().getBufferAt(snap, address); + throw new UnsupportedOperationException(); + } + + @Override + public void writeDown(PcodeTraceDataAccess into) { + // Writes directly, so just ignore } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java index a20d2018d9..dad5e0b46b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java @@ -18,8 +18,7 @@ package ghidra.pcode.exec.trace; import org.apache.commons.lang3.tuple.Pair; import ghidra.pcode.exec.PairedPcodeExecutorState; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; /** * A trace-bound state composed of another trace-bound state and a piece @@ -48,8 +47,13 @@ public class PairedTracePcodeExecutorState extends PairedPcodeExecutorStat } @Override - public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { - left.writeDown(trace, snap, thread, frame); - right.writeDown(trace, snap, thread, frame); + public PcodeTraceDataAccess getData() { + return left.getData(); + } + + @Override + public void writeDown(PcodeTraceDataAccess into) { + left.writeDown(into); + right.writeDown(into); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java index 8e6f96ec96..9b5c82b0eb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java @@ -19,8 +19,7 @@ import org.apache.commons.lang3.tuple.Pair; import ghidra.pcode.exec.PairedPcodeExecutorStatePiece; import ghidra.pcode.exec.PcodeArithmetic; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; /** * A trace-bound state piece composed of two other trace-bound pieces sharing the same address type @@ -53,9 +52,14 @@ public class PairedTracePcodeExecutorStatePiece } @Override - public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { - left.writeDown(trace, snap, thread, frame); - right.writeDown(trace, snap, thread, frame); + public PcodeTraceDataAccess getData() { + return left.getData(); + } + + @Override + public void writeDown(PcodeTraceDataAccess into) { + left.writeDown(into); + right.writeDown(into); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java index 9aff7932ad..67d94d9972 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java @@ -15,8 +15,7 @@ */ package ghidra.pcode.exec.trace; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; /** * A state composing a single {@link RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece} @@ -27,14 +26,9 @@ public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState /** * Create the state * - * @param trace the trace from which to load state - * @param snap the snap from which to load state - * @param thread if applicable, the thread identifying the register space - * @param frame if applicable, the frame identifying the register space + * @param data the trace-data access shim */ - public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, - TraceThread thread, int frame) { - super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread, - frame)); + public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(PcodeTraceDataAccess data) { + super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java index 48785910e7..5b974be400 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -15,14 +15,10 @@ */ package ghidra.pcode.exec.trace; -import com.google.common.collect.Range; - import ghidra.pcode.exec.AccessPcodeExecutionException; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.AddressSetView; -import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemoryState; -import ghidra.trace.model.thread.TraceThread; /** * A relaxation of {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece} that permits @@ -35,15 +31,18 @@ import ghidra.trace.model.thread.TraceThread; public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece { - public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap, - TraceThread thread, int frame) { - super(trace, snap, thread, frame); + /** + * Construct a piece + * + * @param data the trace-data access shim + */ + public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) { + super(data); } @Override - protected AddressSetView getKnown(TraceMemorySpace source) { - return source.getAddressesWithState(Range.closed(0L, snap), - s -> s == TraceMemoryState.KNOWN); + protected AddressSetView getKnown(PcodeTraceDataAccess backing) { + return backing.getKnownBefore(); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java index 9dc7ed770f..78c5e19731 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java @@ -15,8 +15,7 @@ */ package ghidra.pcode.exec.trace; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; /** * A state composing a single {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorState} @@ -27,14 +26,9 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState /** * Create the state * - * @param trace the trace from which to load state - * @param snap the snap from which to load state - * @param thread if applicable, the thread identifying the register space - * @param frame if applicable, the frame identifying the register space + * @param data the trace-data access shim */ - public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, - TraceThread thread, int frame) { - super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread, - frame)); + public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(PcodeTraceDataAccess data) { + super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(data)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java index 3cf120eac4..24b7a17c9b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -16,11 +16,9 @@ package ghidra.pcode.exec.trace; import ghidra.pcode.exec.AccessPcodeExecutionException; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; -import ghidra.trace.model.Trace; import ghidra.trace.model.memory.TraceMemorySpace; -import ghidra.trace.model.memory.TraceMemoryState; -import ghidra.trace.model.thread.TraceThread; /** * A space which requires reads to be completely {@link TraceMemorySpace#KNOWN} memory. @@ -32,13 +30,17 @@ import ghidra.trace.model.thread.TraceThread; public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece { - public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap, - TraceThread thread, int frame) { - super(trace, snap, thread, frame); + public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(PcodeTraceDataAccess data) { + super(data); } - protected AddressSetView getKnown(TraceMemorySpace source) { - return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN); + /** + * Construct a piece + * + * @param data the trace-data access shim + */ + protected AddressSetView getKnown(PcodeTraceDataAccess backing) { + return backing.getKnownNow(); } protected AccessPcodeExecutionException excFor(AddressSetView unknown) { @@ -46,7 +48,7 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece } @Override - protected int checkUninitialized(TraceMemorySpace backing, Address start, int size, + protected int checkUninitialized(PcodeTraceDataAccess backing, Address start, int size, AddressSet uninitialized) { if (backing == null) { if (!uninitialized.contains(start)) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java index 2823d12df5..acb3bac696 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeExecutorStatePiece.java @@ -22,13 +22,11 @@ import com.google.common.primitives.UnsignedLong; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.*; import ghidra.program.model.mem.MemBuffer; -import ghidra.trace.model.Trace; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemoryState; -import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.util.DefaultTraceTimeViewport; /** * The p-code execute state piece for {@link TraceMemoryState} @@ -43,60 +41,21 @@ import ghidra.trace.util.DefaultTraceTimeViewport; * though it's also exemplified in {@link #getFromSpace(TraceMemorySpace, long, int)}. */ public class TraceMemoryStatePcodeExecutorStatePiece extends - AbstractLongOffsetPcodeExecutorStatePiece { + AbstractLongOffsetPcodeExecutorStatePiece { - private final RangeMap unique = TreeRangeMap.create(); - private final Trace trace; - private long snap; - private TraceThread thread; - private int frame; + protected final RangeMap unique = TreeRangeMap.create(); + protected final PcodeTraceDataAccess data; - private final DefaultTraceTimeViewport viewport; - - public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, - int frame) { - super(trace.getBaseLanguage(), - BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()), + /** + * Construct a piece + * + * @param data the trace-data access shim + */ + public TraceMemoryStatePcodeExecutorStatePiece(PcodeTraceDataAccess data) { + super(data.getLanguage(), + BytesPcodeArithmetic.forLanguage(data.getLanguage()), TraceMemoryStatePcodeArithmetic.INSTANCE); - this.trace = trace; - this.snap = snap; - this.thread = thread; - this.frame = frame; - - this.viewport = new DefaultTraceTimeViewport(trace); - this.viewport.setSnap(snap); - } - - public Trace getTrace() { - return trace; - } - - public void setSnap(long snap) { - this.snap = snap; - this.viewport.setSnap(snap); - } - - public long getSnap() { - return snap; - } - - public void setThread(TraceThread thread) { - if (thread != null & thread.getTrace() != trace) { - throw new IllegalArgumentException("Thread, if given, must be part of the same trace"); - } - this.thread = thread; - } - - public TraceThread getThread() { - return thread; - } - - public void setFrame(int frame) { - this.frame = frame; - } - - public int getFrame() { - return frame; + this.data = data; } protected Range range(long offset, int size) { @@ -136,24 +95,19 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends } @Override - protected TraceMemorySpace getForSpace(AddressSpace space, boolean toWrite) { - return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, toWrite); + protected AddressSpace getForSpace(AddressSpace space, boolean toWrite) { + return space; } @Override - protected void setInSpace(TraceMemorySpace space, long offset, int size, TraceMemoryState val) { + protected void setInSpace(AddressSpace space, long offset, int size, TraceMemoryState val) { // NB. Will ensure writes with unknown state are still marked unknown - space.setState(size, space.getAddressSpace().getAddress(offset), val); + data.setState(range(space, offset, size), val); } @Override - protected TraceMemoryState getFromSpace(TraceMemorySpace space, long offset, int size) { - AddressSet set = new AddressSet(range(space.getAddressSpace(), offset, size)); - for (long snap : viewport.getOrderedSnaps()) { - set.delete( - space.getAddressesWithState(snap, set, state -> state == TraceMemoryState.KNOWN)); - } - return set.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN; + protected TraceMemoryState getFromSpace(AddressSpace space, long offset, int size) { + return data.getViewportState(range(space, offset, size)); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java index c66fecd6c2..02ac75f706 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java @@ -16,15 +16,14 @@ package ghidra.pcode.exec.trace; import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; /** * An interface for trace-bound states * *

* In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are - * required to implement {@link #writeDown(Trace, long, TraceThread, int)}. This interface also + * required to implement {@link #writeDown(PcodeTraceDataAccess)}. This interface also * derives from {@link PcodeExecutorState} so that, as the name implies, they can be used where a * state is required. * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java index 67371bf960..80a74e44ea 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java @@ -16,8 +16,8 @@ package ghidra.pcode.exec.trace; import ghidra.pcode.exec.PcodeExecutorStatePiece; -import ghidra.trace.model.Trace; -import ghidra.trace.model.thread.TraceThread; +import ghidra.pcode.exec.trace.data.PcodeTraceAccess; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; /** * A state piece which knows how to write its values back into a trace @@ -26,6 +26,18 @@ import ghidra.trace.model.thread.TraceThread; * @param the type of values */ public interface TracePcodeExecutorStatePiece extends PcodeExecutorStatePiece { + + /** + * Get the state's trace-data access shim + * + *

+ * This method is meant for auxiliary state pieces, so that it can access the same trace data as + * this piece. + * + * @return the trace-data access shim + */ + PcodeTraceDataAccess getData(); + /** * Write the accumulated values (cache) into the given trace * @@ -33,11 +45,8 @@ public interface TracePcodeExecutorStatePiece extends PcodeExecutorStatePi * NOTE: This method requires a transaction to have already been started on the * destination trace. * - * @param trace the trace to modify - * @param snap the snap within the trace - * @param thread the thread to take register writes - * @param frame the frame for register writes - * @see TracePcodeMachine#writeDown(Trace, long, long) + * @param into the destination data-access shim + * @see TracePcodeMachine#writeDown(PcodeTraceAccess) */ - void writeDown(Trace trace, long snap, TraceThread thread, int frame); + void writeDown(PcodeTraceDataAccess into); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java index 9d93749cd9..868c651eb7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java @@ -15,51 +15,17 @@ */ package ghidra.pcode.exec.trace; -import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.PcodeThread; -import ghidra.program.model.lang.Register; -import ghidra.program.model.lang.RegisterValue; -import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; -import ghidra.trace.model.memory.TraceMemoryState; -import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.model.thread.TraceThreadManager; +import ghidra.pcode.exec.trace.data.*; +import ghidra.trace.model.guest.TracePlatform; /** * A p-code machine which sources its state from a trace and can record back into it * - *

- * This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator - * developers should use this interface, but emulator clients should not. Clients should use - * {@link PcodeMachine} instead. - * * @param the type of values manipulated by the machine */ public interface TracePcodeMachine extends PcodeMachine { - /** - * Get the trace from which this emulator reads its initial state - * - * @return the trace - */ - Trace getTrace(); - - /** - * Get the snapshot from which this emulator reads its initial state - * - * @return the snapshot key - */ - long getSnap(); - - /** - * Get the trace thread corresponding to the given p-code thread - * - * @param thread the p-code thread - * @return the trace thread - */ - default TraceThread getTraceThread(PcodeThread thread) { - return getTrace().getThreadManager().getLiveThreadByPath(getSnap(), thread.getName()); - } /** * Create a shared state @@ -77,44 +43,7 @@ public interface TracePcodeMachine extends PcodeMachine { TracePcodeExecutorState createLocalState(PcodeThread thread); /** - * Check if a register has a {@link TraceMemoryState#KNOWN} value for the given thread - * - * @param thread the thread - * @param register the register - * @return true if known - */ - default boolean isRegisterKnown(PcodeThread thread, Register register) { - Trace trace = getTrace(); - long snap = getSnap(); - TraceThread traceThread = - trace.getThreadManager().getLiveThreadByPath(snap, thread.getName()); - TraceMemorySpace space = - trace.getMemoryManager().getMemoryRegisterSpace(traceThread, false); - if (space == null) { - return false; - } - return space.getState(snap, register) == TraceMemoryState.KNOWN; - } - - /** - * Initialize the given thread using context from the trace at its program counter - * - * @param thread the thread to initialize - */ - default void initializeThreadContext(PcodeThread thread) { - SleighLanguage language = getLanguage(); - Register contextreg = language.getContextBaseRegister(); - if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(thread, contextreg)) { - RegisterValue context = getTrace().getRegisterContextManager() - .getValueWithDefault(language, contextreg, getSnap(), thread.getCounter()); - if (context != null) { // TODO: Why does this happen? - thread.overrideContext(context); - } - } - } - - /** - * Write the accumulated emulator state into the given trace at the given snap + * Write the accumulated emulator state via the given trace access shim * *

* NOTE: This method requires a transaction to have already been started on the @@ -122,26 +51,32 @@ public interface TracePcodeMachine extends PcodeMachine { * threadsSnap. When using scratch space, threadsSnap should be the source snap. If populating a * new trace, threadsSnap should probably be the destination snap. * - * @param trace the trace to modify + * @param into the destination trace-data access shim + */ + default void writeDown(PcodeTraceAccess into) { + TracePcodeExecutorState sharedState = (TracePcodeExecutorState) getSharedState(); + sharedState.writeDown(into.getDataForSharedState()); + for (PcodeThread emuThread : getAllThreads()) { + PcodeTraceDataAccess localInto = into.getDataForLocalState(emuThread, 0); + if (localInto == null) { + throw new IllegalArgumentException( + "Given trace does not have thread with name/path '" + emuThread.getName() + + "' at source snap"); + } + TracePcodeExecutorState localState = + (TracePcodeExecutorState) emuThread.getState().getLocalState(); + localState.writeDown(localInto); + } + } + + /** + * @see #writeDown(PcodeTraceAccess) + * @param platform the platform whose trace to modify * @param destSnap the destination snap within the trace * @param threadsSnap the snap at which to find corresponding threads, usually the same as * {@link #getSnap()} */ - default void writeDown(Trace trace, long destSnap, long threadsSnap) { - TracePcodeExecutorState ss = (TracePcodeExecutorState) getSharedState(); - ss.writeDown(trace, destSnap, null, 0); - TraceThreadManager threadManager = trace.getThreadManager(); - for (PcodeThread emuThread : getAllThreads()) { - TracePcodeExecutorState ls = - (TracePcodeExecutorState) emuThread.getState().getLocalState(); - TraceThread traceThread = - threadManager.getLiveThreadByPath(threadsSnap, emuThread.getName()); - if (traceThread == null) { - throw new IllegalArgumentException( - "Given trace does not have thread with name/path '" + emuThread.getName() + - "' at snap " + destSnap); - } - ls.writeDown(trace, destSnap, traceThread, 0); - } + default void writeDown(TracePlatform platform, long destSnap, long threadsSnap) { + writeDown(new DefaultPcodeTraceAccess(platform, destSnap, threadsSnap)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java index ef1f3afb62..0274880034 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java @@ -28,7 +28,7 @@ import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; @@ -38,32 +38,6 @@ import ghidra.trace.model.thread.TraceThread; public enum TraceSleighUtils { ; - /** - * Get the trace memory space for the given "coordinates" - * - *

- * This is used to find "backing" objects for a p-code executor state bound to a trace, whether - * direct or cached. - * - * @param space the address space - * @param trace the trace - * @param thread the thread, if a register space - * @param frame the frame, if a register space - * @param toWrite true if the state intends to write to the space, i.e., the space must exist - * @return the space, or null if it doesn't exist - */ - public static TraceMemorySpace getSpaceForExecution(AddressSpace space, Trace trace, - TraceThread thread, int frame, boolean toWrite) { - if (space.isRegisterSpace()) { - if (thread == null) { - throw new IllegalArgumentException( - "Cannot access register unless a thread is given."); - } - return trace.getMemoryManager().getMemoryRegisterSpace(thread, frame, toWrite); - } - return trace.getMemoryManager().getMemorySpace(space, toWrite); - } - /** * Build a p-code executor that operates directly on bytes of the given trace * @@ -72,7 +46,27 @@ public enum TraceSleighUtils { * for manipulating or initializing variables using Sleigh code. It is generally not suitable * for use in an emulator. For that, consider {@link BytesTracePcodeEmulator}. * - * @param trace the trace + * @param platform the platform + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the executor + */ + public static PcodeExecutor buildByteExecutor(TracePlatform platform, long snap, + TraceThread thread, int frame) { + DirectBytesTracePcodeExecutorState state = + new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame); + Language language = platform.getLanguage(); + if (!(language instanceof SleighLanguage)) { + throw new IllegalArgumentException("TracePlatform must use a SLEIGH language"); + } + return new PcodeExecutor<>((SleighLanguage) language, + BytesPcodeArithmetic.forLanguage(language), state); + } + + /** + * @see #buildByteExecutor(TracePlatform, long, TraceThread, int) + * @param trace the trace whose host platform to use * @param snap the snap * @param thread the thread, required if register space is used * @param frame the frame, for when register space is used @@ -80,14 +74,7 @@ public enum TraceSleighUtils { */ public static PcodeExecutor buildByteExecutor(Trace trace, long snap, TraceThread thread, int frame) { - DirectBytesTracePcodeExecutorState state = - new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame); - Language language = trace.getBaseLanguage(); - if (!(language instanceof SleighLanguage)) { - throw new IllegalArgumentException("Trace must use a SLEIGH language"); - } - return new PcodeExecutor<>((SleighLanguage) language, - BytesPcodeArithmetic.forLanguage(language), state); + return buildByteExecutor(trace.getPlatformManager().getHostPlatform(), snap, thread, frame); } /** @@ -98,7 +85,29 @@ public enum TraceSleighUtils { * when the client would also like to know if all variables involved are * {@link TraceMemoryState#KNOWN}. * - * @param trace the trace + * @param platform the platform + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the executor + */ + public static PcodeExecutor> buildByteWithStateExecutor( + TracePlatform platform, long snap, TraceThread thread, int frame) { + DirectBytesTracePcodeExecutorState state = + new DirectBytesTracePcodeExecutorState(platform, snap, thread, frame); + PcodeExecutorState> paired = state.withMemoryState(); + Language language = platform.getLanguage(); + if (!(language instanceof SleighLanguage)) { + throw new IllegalArgumentException("TracePlatform must use a SLEIGH language"); + } + return new PcodeExecutor<>((SleighLanguage) language, new PairedPcodeArithmetic<>( + BytesPcodeArithmetic.forLanguage(language), TraceMemoryStatePcodeArithmetic.INSTANCE), + paired); + } + + /** + * @see #buildByteWithStateExecutor(TracePlatform, long, TraceThread, int) + * @param trace the trace whose host platform to use * @param snap the snap * @param thread the thread, required if register space is used * @param frame the frame, for when register space is used @@ -106,16 +115,8 @@ public enum TraceSleighUtils { */ public static PcodeExecutor> buildByteWithStateExecutor( Trace trace, long snap, TraceThread thread, int frame) { - DirectBytesTracePcodeExecutorState state = - new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame); - PcodeExecutorState> paired = state.withMemoryState(); - Language language = trace.getBaseLanguage(); - if (!(language instanceof SleighLanguage)) { - throw new IllegalArgumentException("Trace must use a SLEIGH language"); - } - return new PcodeExecutor<>((SleighLanguage) language, new PairedPcodeArithmetic<>( - BytesPcodeArithmetic.forLanguage(language), TraceMemoryStatePcodeArithmetic.INSTANCE), - paired); + return buildByteWithStateExecutor(trace.getPlatformManager().getHostPlatform(), snap, + thread, frame); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java index 46d0af3ff8..9d5909a846 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java @@ -42,7 +42,9 @@ public interface AuxTraceEmulatorPartsFactory extends AuxEmulatorPartsFactory * capable of lazily loading state from a trace and later writing its cache back into the trace * at another snapshot. The given concrete piece is already capable of doing that for concrete * values. The auxiliary piece should be able to independently load its state from the trace, - * since this is one way a user expects to initialize the auxiliary values. + * since this is one way a user expects to initialize the auxiliary values. It ought to use the + * same data-access shim as the given concrete state. See + * {@link TracePcodeExecutorStatePiece#getData()}. * * @param emulator the emulator * @param concrete the concrete piece diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java index c9ee332266..63e8a1b6be 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java @@ -21,7 +21,8 @@ import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; import ghidra.pcode.exec.trace.*; -import ghidra.trace.model.Trace; +import ghidra.pcode.exec.trace.data.*; +import ghidra.trace.model.guest.TracePlatform; /** * An trace-integrated emulator whose parts are manufactured by a @@ -39,51 +40,48 @@ import ghidra.trace.model.Trace; public abstract class AuxTracePcodeEmulator extends AuxPcodeEmulator implements TracePcodeMachine> { - protected final Trace trace; - protected final long snap; + protected final PcodeTraceAccess access; /** * Create a new emulator * - * @param trace the trace from which the emulator loads state - * @param snap the snap from which the emulator loads state + * @param access the trace access shim */ - public AuxTracePcodeEmulator(Trace trace, long snap) { - super(trace.getBaseLanguage()); - this.trace = trace; - this.snap = snap; + public AuxTracePcodeEmulator(PcodeTraceAccess access) { + super(access.getLanguage()); + this.access = access; + } + + /** + * Create a new emulator + * + * @param platform the platform to emulate + * @param snap the source snap + */ + public AuxTracePcodeEmulator(TracePlatform platform, long snap) { + this(new DefaultPcodeTraceAccess(platform, snap)); } @Override protected abstract AuxTraceEmulatorPartsFactory getPartsFactory(); - @Override - public Trace getTrace() { - return trace; - } - - @Override - public long getSnap() { - return snap; - } - @Override protected PcodeThread> createThread(String name) { PcodeThread> thread = super.createThread(name); - initializeThreadContext(thread); + access.getDataForLocalState(thread, 0).initializeThreadContext(thread); return thread; } @Override public TracePcodeExecutorState> createSharedState() { return getPartsFactory().createTraceSharedState(this, - new BytesTracePcodeExecutorStatePiece(trace, snap, null, 0)); + new BytesTracePcodeExecutorStatePiece(access.getDataForSharedState())); } @Override public TracePcodeExecutorState> createLocalState( PcodeThread> thread) { return getPartsFactory().createTraceLocalState(this, thread, - new BytesTracePcodeExecutorStatePiece(trace, snap, getTraceThread(thread), 0)); + new BytesTracePcodeExecutorStatePiece(access.getDataForLocalState(thread, 0))); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceAccess.java new file mode 100644 index 0000000000..6eb24bfe28 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceAccess.java @@ -0,0 +1,146 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.PcodeThread; +import ghidra.program.model.lang.Language; +import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.DefaultTraceTimeViewport; +import ghidra.trace.util.TraceTimeViewport; + +/** + * An abstract implementation of {@link PcodeTraceAccess} + * + * @param the type of shared data-access shims provided + * @param the type of thread-local data-access shims provided + */ +public abstract class AbstractPcodeTraceAccess + implements PcodeTraceAccess { + + protected final TracePlatform platform; + protected final long threadsSnap; + protected final long snap; + + protected final TraceTimeViewport viewport; + protected S dataForSharedState; + protected final Map, L> dataForLocalStateByThreadAndFrame = + new HashMap<>(); + + /** + * Construct a shim + * + * @param platform the associated platform + * @param snap the associated snap + * @param threadsSnap the snap to use when finding associated threads between trace and emulator + */ + public AbstractPcodeTraceAccess(TracePlatform platform, long snap, long threadsSnap) { + this.platform = platform; + this.snap = snap; + this.threadsSnap = threadsSnap; + + DefaultTraceTimeViewport viewport = new DefaultTraceTimeViewport(getTrace()); + viewport.setSnap(snap); + this.viewport = viewport; + } + + /** + * Construct a shim + * + * @param platform the associated platform + * @param snap the associated snap + */ + public AbstractPcodeTraceAccess(TracePlatform platform, long snap) { + this(platform, snap, snap); + } + + /** + * Get the associated trace + * + * @return the trace + */ + protected Trace getTrace() { + return platform.getTrace(); + } + + /** + * Get the trace thread conventionally associated with the given p-code thread + * + *

+ * A p-code thread is conventionally associated with the trace thread whose path matches the + * p-code thread's name. + * + * @param thread the p-code thread + * @return the trace thread + */ + protected TraceThread getTraceThread(PcodeThread thread) { + return getTrace().getThreadManager().getLiveThreadByPath(threadsSnap, thread.getName()); + } + + @Override + public Language getLanguage() { + return platform.getLanguage(); + } + + /** + * Factory method for the shared data-access shim + * + * @return the new data-access shim + */ + protected abstract S newDataForSharedState(); + + @Override + public S getDataForSharedState() { + synchronized (dataForLocalStateByThreadAndFrame) { + if (dataForSharedState == null) { + dataForSharedState = newDataForSharedState(); + } + return dataForSharedState; + } + } + + /** + * Factory method for a thread's local data-access shim + * + * @param thread the associated trace thread + * @param frame the frame, usually 0 + * @return the new data-access shim + */ + protected abstract L newDataForLocalState(TraceThread thread, int frame); + + @Override + public L getDataForLocalState(TraceThread thread, int frame) { + if (thread == null) { + return null; + } + synchronized (dataForLocalStateByThreadAndFrame) { + return dataForLocalStateByThreadAndFrame.computeIfAbsent(Pair.of(thread, frame), p -> { + return newDataForLocalState(p.getLeft(), p.getRight()); + }); + } + } + + @Override + public L getDataForLocalState(PcodeThread thread, int frame) { + return getDataForLocalState(getTraceThread(thread), frame); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java new file mode 100644 index 0000000000..4e3de427c1 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/AbstractPcodeTraceDataAccess.java @@ -0,0 +1,193 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import java.nio.ByteBuffer; + +import com.google.common.collect.Range; + +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.memory.*; +import ghidra.trace.util.TraceTimeViewport; + +/** + * An abstract data-access shim, for either memory or registers + */ +public abstract class AbstractPcodeTraceDataAccess implements InternalPcodeTraceDataAccess { + protected final TracePlatform platform; + protected final long snap; + protected final TraceTimeViewport viewport; + + protected final TraceMemoryManager mm; + + /** + * Construct a shim + * + * @param platform the associated platform + * @param snap the associated snap + * @param viewport the viewport, set to the same snapshot + */ + public AbstractPcodeTraceDataAccess(TracePlatform platform, long snap, + TraceTimeViewport viewport) { + this.platform = platform; + this.snap = snap; + this.viewport = viewport; + + this.mm = platform.getTrace().getMemoryManager(); + } + + @Override + public Language getLanguage() { + return platform.getLanguage(); + } + + @Override + public TracePlatform getPlatform() { + return platform; + } + + @Override + public long getSnap() { + return snap; + } + + /** + * Get the interface for accessing trace memory or registers + * + * @param createIfAbsent in the case of registers, whether to create the missing space + * @return the operations, or null + */ + protected abstract TraceMemoryOperations getMemoryOps(boolean createIfAbsent); + + /** + * If this shim is associated with a (register) overlay space, translate the given address into + * it + * + * @param address the physical (register) address + * @return the overlay address + */ + protected abstract Address toOverlay(Address address); + + /** + * @see #toOverlay(Address) + * @param range the physical range + * @return the overlay range + */ + protected abstract AddressRange toOverlay(AddressRange range); + + /** + * @see #toOverlay(Address) + * @param set + * @return + */ + protected abstract AddressSetView toOverlay(AddressSetView set); + + @Override + public void setState(AddressRange guestRange, TraceMemoryState state) { + AddressRange hostRange = platform.mapGuestToHost(guestRange); + if (hostRange == null) { + return; + } + getMemoryOps(true).setState(snap, toOverlay(hostRange), state); + } + + @Override + public TraceMemoryState getViewportState(AddressRange guestRange) { + TraceMemoryOperations ops = getMemoryOps(false); + if (ops == null) { + return TraceMemoryState.UNKNOWN; + } + + AddressRange hostRange = platform.mapGuestToHost(guestRange); + if (hostRange == null) { + return TraceMemoryState.UNKNOWN; + } + + AddressSet hostSet = new AddressSet(toOverlay(hostRange)); + for (long snap : viewport.getOrderedSnaps()) { + hostSet.delete( + ops.getAddressesWithState(snap, hostSet, s -> s == TraceMemoryState.KNOWN)); + } + return hostSet.isEmpty() ? TraceMemoryState.KNOWN : TraceMemoryState.UNKNOWN; + } + + protected AddressSetView doGetKnown(Range span) { + TraceMemoryOperations ops = getMemoryOps(false); + if (ops == null) { + return new AddressSet(); + } + return platform.mapHostToGuest(ops.getAddressesWithState(span, + s -> s == TraceMemoryState.KNOWN)); + } + + @Override + public AddressSetView getKnownNow() { + return doGetKnown(Range.singleton(snap)); + } + + @Override + public AddressSetView getKnownBefore() { + return doGetKnown(Range.closed(0L, snap)); + } + + @Override + public AddressSetView intersectUnknown(AddressSetView guestView) { + TraceMemoryOperations ops = getMemoryOps(false); + if (ops == null) { + return guestView; + } + + AddressSetView hostView = toOverlay(platform.mapGuestToHost(guestView)); + AddressSetView hostKnown = ops.getAddressesWithState(snap, hostView, + s -> s != null && s != TraceMemoryState.UNKNOWN); + AddressSetView hostResult = hostView.subtract(hostKnown); + return platform.mapHostToGuest(hostResult); + } + + @Override + public int putBytes(Address start, ByteBuffer buf) { + // TODO: Truncate or verify range? + Address hostStart = platform.mapGuestToHost(start); + if (hostStart == null) { + return 0; + } + return getMemoryOps(true).putBytes(snap, toOverlay(hostStart), buf); + } + + @Override + public int getBytes(Address start, ByteBuffer buf) { + // TODO: Truncate or verify range? + Address hostStart = platform.mapGuestToHost(start); + if (hostStart == null) { + return 0; + } + TraceMemoryOperations ops = getMemoryOps(false); + if (ops == null) { + // TODO: Write 0s? + int length = buf.remaining(); + buf.position(buf.position() + length); + return length; + } + return ops.getViewBytes(snap, toOverlay(hostStart), buf); + } + + @Override + public PcodeTracePropertyAccess getPropertyAccess(String name, Class type) { + return new DefaultPcodeTracePropertyAccess<>(this, name, type); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java new file mode 100644 index 0000000000..15133d25c1 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceAccess.java @@ -0,0 +1,57 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.thread.TraceThread; + +/** + * The default trace access shim for a session + */ +public class DefaultPcodeTraceAccess extends AbstractPcodeTraceAccess // + { + + /** + * Construct a shim + * + * @param platform the associated platform + * @param snap the associated snap + * @param threadsSnap the snap to use when finding associated threads between trace and emulator + */ + public DefaultPcodeTraceAccess(TracePlatform platform, long snap, long threadsSnap) { + super(platform, snap, threadsSnap); + } + + /** + * Construct a shim + * + * @param platform the associated platform + * @param snap the associated snap + */ + public DefaultPcodeTraceAccess(TracePlatform platform, long snap) { + super(platform, snap); + } + + @Override + public DefaultPcodeTraceMemoryAccess newDataForSharedState() { + return new DefaultPcodeTraceMemoryAccess(platform, snap, viewport); + } + + @Override + public DefaultPcodeTraceRegistersAccess newDataForLocalState(TraceThread thread, int frame) { + return new DefaultPcodeTraceRegistersAccess(platform, snap, thread, frame, viewport); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceMemoryAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceMemoryAccess.java new file mode 100644 index 0000000000..ac3a08d0f5 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceMemoryAccess.java @@ -0,0 +1,72 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import ghidra.program.model.address.*; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.memory.TraceMemoryOperations; +import ghidra.trace.model.property.TracePropertyMapOperations; +import ghidra.trace.util.TraceTimeViewport; + +/** + * The default data-access shim for trace memory + */ +public class DefaultPcodeTraceMemoryAccess extends AbstractPcodeTraceDataAccess + implements PcodeTraceMemoryAccess { + + /** + * Construct a shim + * + * @param platform the associated platform + * @param snap the associated snap + * @param viewport the viewport, set to the same snapshot + */ + protected DefaultPcodeTraceMemoryAccess(TracePlatform platform, long snap, + TraceTimeViewport viewport) { + super(platform, snap, viewport); + } + + @Override + protected TraceMemoryOperations getMemoryOps(boolean createIfAbsent) { + return mm; + } + + @Override + public TracePropertyMapOperations getPropertyOps(String name, Class type, + boolean createIfAbsent) { + if (createIfAbsent) { + return platform.getTrace() + .getAddressPropertyManager() + .getOrCreatePropertyMap(name, type); + } + return platform.getTrace().getAddressPropertyManager().getPropertyMap(name, type); + } + + @Override + protected Address toOverlay(Address address) { + return address; + } + + @Override + protected AddressRange toOverlay(AddressRange range) { + return range; + } + + @Override + protected AddressSetView toOverlay(AddressSetView set) { + return set; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTracePropertyAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTracePropertyAccess.java new file mode 100644 index 0000000000..23c1d12c3c --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTracePropertyAccess.java @@ -0,0 +1,155 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import com.google.common.collect.Range; + +import ghidra.program.model.address.*; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.model.property.*; + +/** + * The default trace-property access shim + * + * @param the type of the property's values + */ +public class DefaultPcodeTracePropertyAccess + implements PcodeTracePropertyAccess { + + protected final InternalPcodeTraceDataAccess data; + protected final String name; + protected final Class type; + + protected TracePropertyMapOperations po; + + /** + * Construct the shim + * + * @param data the trace-data access shim providing this property access shim + * @param name the name of the property + * @param type the type of the property + */ + protected DefaultPcodeTracePropertyAccess(InternalPcodeTraceDataAccess data, String name, + Class type) { + this.data = data; + this.name = name; + this.type = type; + + this.po = data.getPropertyOps(name, type, false); + } + + /** + * Get the interface for accessing the trace property on memory or registers + * + * @param createIfAbsent whether to create the missing property (and space in the case of a + * register property) + * @return the operations, or null + */ + protected TracePropertyMapOperations getPropertyOperations(boolean createIfAbsent) { + if (po == null) { + return po = data.getPropertyOps(name, type, createIfAbsent); + } + return po; + } + + /** + * Extension point: Alternative logic when the trace property is null + * + * @param hostAddress the trace address (in the host platform) + * @return the alternative value, or null + */ + protected T whenNull(Address hostAddress) { + return null; + } + + @Override + public T get(Address address) { + Address hostAddr = data.getPlatform().mapGuestToHost(address); + if (hostAddr == null) { + return null; + } + TracePropertyMapOperations ops = getPropertyOperations(false); + if (ops == null) { + return whenNull(hostAddr); + } + Address overlayAddr = toOverlay(ops, hostAddr); + return ops.get(data.getSnap(), overlayAddr); + } + + @Override + public void put(Address address, T value) { + Address hostAddr = data.getPlatform().mapGuestToHost(address); + if (hostAddr == null) { + // TODO: Warn? + return; + } + Range span = DBTraceUtils.atLeastMaybeScratch(data.getSnap()); + TracePropertyMapOperations ops = getPropertyOperations(true); + ops.set(span, toOverlay(ops, hostAddr), value); + } + + @Override + public void clear(AddressRange range) { + AddressRange hostRange = data.getPlatform().mapGuestToHost(range); + if (hostRange == null) { + // TODO: Warn? + return; + } + Range span = DBTraceUtils.atLeastMaybeScratch(data.getSnap()); + TracePropertyMapOperations ops = getPropertyOperations(false); + if (ops == null) { + return; + } + ops.clear(span, toOverlay(ops, hostRange)); + } + + /** + * If this provides access to an overlay space, translate the physical address to it + * + * @param ops the property operations + * @param address the physical address + * @return the overlay address, or the same address + */ + protected Address toOverlay(TracePropertyMapOperations ops, Address address) { + if (ops instanceof TracePropertyMap) { + return address; + } + if (ops instanceof TracePropertyMapSpace mapSpace) { + return mapSpace.getAddressSpace().getOverlayAddress(address); + } + throw new AssertionError(); + } + + /** + * If this provides access to an overlay space, translate the physical range to it + * + * @param ops the property operations + * @param range the physical range + * @return the overlay range, or the same range + */ + protected AddressRange toOverlay(TracePropertyMapOperations ops, AddressRange range) { + if (ops instanceof TracePropertyMap) { + return range; + } + if (ops instanceof TracePropertyMapSpace mapSpace) { + AddressSpace space = mapSpace.getAddressSpace(); + return new AddressRangeImpl( + space.getOverlayAddress(range.getMinAddress()), + space.getOverlayAddress(range.getMaxAddress())); + } + throw new AssertionError(); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceRegistersAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceRegistersAccess.java new file mode 100644 index 0000000000..0d25c4d43b --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceRegistersAccess.java @@ -0,0 +1,151 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import ghidra.pcode.emu.PcodeThread; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.property.TracePropertyMap; +import ghidra.trace.model.property.TracePropertyMapSpace; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceTimeViewport; + +/** + * The default data-access shim for trace registers + */ +public class DefaultPcodeTraceRegistersAccess extends AbstractPcodeTraceDataAccess + implements PcodeTraceRegistersAccess { + + protected final TraceThread thread; + protected final int frame; + + protected TraceMemorySpace ms; + + /** + * Construct a shim + * + * @param platform the associated platform + * @param snap the associated snap + * @param thread the associated thread whose registers to access + * @param frame the associated frame, or 0 if not applicable + * @param viewport the viewport, set to the same snapshot + */ + protected DefaultPcodeTraceRegistersAccess(TracePlatform platform, long snap, + TraceThread thread, int frame, TraceTimeViewport viewport) { + super(platform, snap, viewport); + this.thread = thread; + this.frame = frame; + + this.ms = mm.getMemoryRegisterSpace(thread, frame, false); + } + + @Override + protected TraceMemorySpace getMemoryOps(boolean createIfAbsent) { + if (ms == null) { + return ms = mm.getMemoryRegisterSpace(thread, frame, createIfAbsent); + } + return ms; + } + + @Override + public TracePropertyMapSpace getPropertyOps(String name, Class type, + boolean createIfAbsent) { + if (createIfAbsent) { + return platform.getTrace() + .getAddressPropertyManager() + .getOrCreatePropertyMap(name, type) + .getPropertyMapRegisterSpace(thread, frame, createIfAbsent); + } + TracePropertyMap map = platform.getTrace() + .getAddressPropertyManager() + .getPropertyMap(name, type); + return map == null ? null : map.getPropertyMapRegisterSpace(thread, frame, createIfAbsent); + } + + /** + * Check if a register has a {@link TraceMemoryState#KNOWN} value for the given thread + * + * @param thread the thread + * @param register the register + * @return true if known + */ + protected boolean isRegisterKnown(PcodeThread thread, Register register) { + Trace trace = platform.getTrace(); + TraceThread traceThread = + trace.getThreadManager().getLiveThreadByPath(snap, thread.getName()); + TraceMemorySpace space = + trace.getMemoryManager().getMemoryRegisterSpace(traceThread, false); + if (space == null) { + return false; + } + return space.getState(platform, snap, register) == TraceMemoryState.KNOWN; + } + + @Override + public void initializeThreadContext(PcodeThread thread) { + Trace trace = platform.getTrace(); + Language language = platform.getLanguage(); + Register contextreg = language.getContextBaseRegister(); + if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(thread, contextreg)) { + RegisterValue context = trace.getRegisterContextManager() + .getValueWithDefault(platform, contextreg, snap, thread.getCounter()); + if (context != null) { // TODO: Why does this happen? + thread.overrideContext(context); + } + } + } + + @Override + protected Address toOverlay(Address address) { + TraceMemorySpace ops = getMemoryOps(false); + if (ops == null) { + return null; // client should bail anyway + } + return ops.getAddressSpace().getOverlayAddress(address); + } + + @Override + protected AddressRange toOverlay(AddressRange range) { + TraceMemorySpace ops = getMemoryOps(false); + if (ops == null) { + return null; // client should bail anyway + } + AddressSpace space = ops.getAddressSpace(); + return new AddressRangeImpl( + space.getOverlayAddress(range.getMinAddress()), + space.getOverlayAddress(range.getMaxAddress())); + } + + @Override + protected AddressSetView toOverlay(AddressSetView set) { + TraceMemorySpace ops = getMemoryOps(false); + if (ops == null) { + return null; // client should bail anyway + } + AddressSpace space = ops.getAddressSpace(); + AddressSet result = new AddressSet(); + for (AddressRange rng : set) { + result.add( + space.getOverlayAddress(rng.getMinAddress()), + space.getOverlayAddress(rng.getMaxAddress())); + } + return result; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java new file mode 100644 index 0000000000..31ab70302b --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/DefaultPcodeTraceThreadAccess.java @@ -0,0 +1,113 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import java.nio.ByteBuffer; + +import ghidra.pcode.emu.PcodeThread; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.trace.model.memory.TraceMemoryState; + +/** + * The default data-access shim, for both memory and registers + * + *

+ * This is not designed for use with the emulator, but rather with stand-alone p-code executors, + * e.g., to evaluate a Sleigh expression. It multiplexes a given memory access shim and another + * register access shim into a single shim for use in one state piece. + */ +public class DefaultPcodeTraceThreadAccess + implements PcodeTraceMemoryAccess, PcodeTraceRegistersAccess { + + protected final PcodeTraceMemoryAccess memory; + protected final PcodeTraceRegistersAccess registers; + + /** + * Construct a shim + * + * @param memory the memory access shim + * @param registers the regsiter access shim + */ + protected DefaultPcodeTraceThreadAccess(PcodeTraceMemoryAccess memory, + PcodeTraceRegistersAccess registers) { + this.memory = memory; + this.registers = registers; + } + + @Override + public Language getLanguage() { + return memory.getLanguage(); + } + + @Override + public void setState(AddressRange range, TraceMemoryState state) { + if (range.getAddressSpace().isRegisterSpace()) { + registers.setState(range, state); + return; + } + memory.setState(range, state); + } + + @Override + public TraceMemoryState getViewportState(AddressRange range) { + if (range.getAddressSpace().isRegisterSpace()) { + return registers.getViewportState(range); + } + return memory.getViewportState(range); + } + + @Override + public AddressSetView getKnownNow() { + return memory.getKnownNow().union(registers.getKnownNow()); + } + + @Override + public AddressSetView getKnownBefore() { + return memory.getKnownBefore().union(registers.getKnownBefore()); + } + + @Override + public AddressSetView intersectUnknown(AddressSetView view) { + return memory.intersectUnknown(view).union(registers.intersectUnknown(view)); + } + + @Override + public int putBytes(Address start, ByteBuffer buf) { + if (start.isRegisterAddress()) { + return registers.putBytes(start, buf); + } + return memory.putBytes(start, buf); + } + + @Override + public int getBytes(Address start, ByteBuffer buf) { + if (start.isRegisterAddress()) { + return registers.getBytes(start, buf); + } + return memory.getBytes(start, buf); + } + + @Override + public PcodeTracePropertyAccess getPropertyAccess(String name, Class type) { + throw new UnsupportedOperationException("This is meant for p-code executor use"); + } + + @Override + public void initializeThreadContext(PcodeThread thread) { + registers.initializeThreadContext(thread); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/InternalPcodeTraceDataAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/InternalPcodeTraceDataAccess.java new file mode 100644 index 0000000000..ad349ff26a --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/InternalPcodeTraceDataAccess.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import ghidra.lifecycle.Internal; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.property.TracePropertyMapOperations; + +@Internal +public interface InternalPcodeTraceDataAccess extends PcodeTraceDataAccess { + TracePlatform getPlatform(); + + long getSnap(); + + TracePropertyMapOperations getPropertyOps(String name, Class type, + boolean createIfAbsent); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java new file mode 100644 index 0000000000..3bd560bec7 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceAccess.java @@ -0,0 +1,129 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.trace.TracePcodeMachine; +import ghidra.program.model.lang.Language; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.thread.TraceThread; + +/** + * A trace access shim + * + *

+ * This encapsulates the source or destination "coordinates" of a trace to simplify access to that + * trace by p-code operations. This is also meant to encapsulate certain conventions, e.g., writes + * are effective from the destination snapshot into the indefinite future, and meant to protect + * p-code executor/emulator states from future re-factorings of the Trace API. + * + *

+ * While, technically anything can be behind the shim, the default implementations are backed by a + * trace. The shim is associated with a chosen platform and snapshot. All methods are with respect + * to that platform. In particular the addresses must all be in spaces of the platform's language. + * Note that the platform may be the trace's host platform. + * + *

+ * Typically, each component of an emulator and/or its state will accept a corresponding access + * shim. Thus, each method in the chain of obtaining the shim is invoked during that piece's + * construction or invoked and passed into the constructor by a factory method. A complete chain + * starts with {@link DefaultPcodeTraceAccess}. Each method is listed with notes about where it is + * typically invoked below: + * + *

    + *
  • Typically invoked by an overloaded constructor, which then passes it to {@code this(...)}. + * Clients can also construct the shim and pass it to the shim-accepting constructor manually. + * Similarly, {@link TracePcodeMachine#writeDown(TracePlatform, long, long)} will construct one and + * pass it to the overloaded method, which can instead be done by the client. + * + *
    + * PcodeTraceAccess access =
    + * 	new DefaultPcodeTraceAccess(trace.getPlatformManager().getHostPlatform(), 0, 0);
    + * 
    + * + *
  • Typically invoked by a factory method for an emulator's shared executor state + * + *
    + * PcodeTraceMemoryAccess sharedData = access.getDataForSharedState();
    + * 
    + * + *
  • Typically invoked by a factory method for an emulator thread's local executor state + * + *
    + * PcodeTraceRegisterAccess localData = access.getDataForLocalState(thread, 0);
    + * 
    + * + *
  • Typically invoked by an auxiliary emulator state piece + * + *
    + * PcodeTracePropertyAccess property = data.getPropertyAccess("MyProperty", String.class);
    + * 
    + *
+ */ +public interface PcodeTraceAccess { + /** + * Get the language of the associated platform + * + * @return the langauge + */ + Language getLanguage(); + + /** + * Get the data-access shim for use in an emulator's shared state + * + * @return the shim + */ + PcodeTraceMemoryAccess getDataForSharedState(); + + /** + * Get the data-access shim for use in an emulator thread's local state + * + * @param thread the emulator's thread + * @param frame the frame, usually 0 + * @return the shim + */ + PcodeTraceRegistersAccess getDataForLocalState(PcodeThread thread, int frame); + + /** + * Get the data-access shim for use in an emulator thread's local state + * + * @param thread the trace thread associated with the emulator's thread + * @param frame the frame, usually 0 + * @return the shim + */ + PcodeTraceRegistersAccess getDataForLocalState(TraceThread thread, int frame); + + /** + * Get the data-access shim for use in an executor having thread context + * + *

+ * NOTE: Do not use this shim for an emulator thread's local state. Use + * {@link #getDataForLocalState(PcodeThread, int)} instead. This shim is meant for use in + * stand-alone executors, e.g., for evaluating Sleigh expressions. Most likely, the thread is + * the active thread in the UI. + * + * @param thread the trace thread for context, if applicable, or null + * @param frame the frame + * @return the shim + */ + default PcodeTraceDataAccess getDataForThreadState(TraceThread thread, int frame) { + if (thread == null) { + return getDataForSharedState(); + } + return new DefaultPcodeTraceThreadAccess(getDataForSharedState(), + getDataForLocalState(thread, frame)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java new file mode 100644 index 0000000000..41171e4030 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceDataAccess.java @@ -0,0 +1,135 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import java.nio.ByteBuffer; + +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.trace.model.memory.TraceMemoryState; + +/** + * A data-access shim for a trace + * + * @see PcodeTraceAccess + */ +public interface PcodeTraceDataAccess { + /** + * Get the language of the associated platform + * + * @return the language + */ + Language getLanguage(); + + /** + * Set the memory state of an address range + * + *

+ * The state is set only for the destination snapshot. It is not effective for the + * indefinite future. + * + * @param range the range + * @param state the desired state + */ + void setState(AddressRange range, TraceMemoryState state); + + /** + * Get the composite state of an address range, using the snapshot's viewport + * + *

+ * Typically, the viewport is at most 2 snapshots deep. When reading from a captured snapshot, + * the viewport includes only the source snapshot. When reading from scratch snapshot (usually + * generated by emulation), the viewport includes that scratch snapshot and the original source + * snapshot. The {@link TraceMemoryState#KNOWN} address set is the union of known address sets + * among all snapshots in the viewport. If all addresses in the given range are + * {@link TraceMemoryState#KNOWN}, then the composite state is known. Otherwise, the composite + * state is {@link TraceMemoryState#UNKNOWN}. + * + * @param range the range to check + * @return the composite state of the range + */ + TraceMemoryState getViewportState(AddressRange range); + + /** + * Get the address set of {@link TraceMemoryState#KNOWN} memory in the source snapshot + * + *

+ * Note, this does not consider the snapshot's viewport. + * + * @implNote This can be an expensive operation when the platform is a guest, since what would + * ordinarily be a lazy address set must be computed and translated to the guest + * address spaces. + * + * @return the address set + */ + AddressSetView getKnownNow(); + + /** + * Get the address set of {@link TraceMemoryState#KNOWN} memory among all snapshots from 0 to + * the source snapshot + * + *

+ * Note, this does not consider the snapshot's viewport. + * + * @implNote This can be an expensive operation when the platform is a guest, since what would + * ordinarily be a lazy address set must be computed and translated to the guest + * address spaces. + * + * @return the address set + */ + AddressSetView getKnownBefore(); + + /** + * Compute the intersection of the given address set and the set of + * {@link TraceMemoryState#UNKNOWN} memory + * + * @param view the address set + * @return the intersection + */ + AddressSetView intersectUnknown(AddressSetView view); + + /** + * Write bytes into the trace + * + *

+ * Each written byte is effective for future snapshots up to but excluding the next snapshot + * where another byte is written at the same address. + * + * @param start the address of the first byte to write + * @param buf a buffer of bytes to write + * @return the number of bytes written + */ + int putBytes(Address start, ByteBuffer buf); + + /** + * Read bytes from the trace + * + * @param start the address of the first byte to read + * @param buf a buffer to receive the bytes + * @return the number of bytes read + */ + int getBytes(Address start, ByteBuffer buf); + + /** + * Get a property-access shim for the named property + * + * @param the type of the property's values + * @param name the name of the property + * @param type the class of the property's values + * @return the access shim + */ + PcodeTracePropertyAccess getPropertyAccess(String name, Class type); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceMemoryAccess.java similarity index 58% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorState.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceMemoryAccess.java index c77ebbf2f4..4ffbf450d0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceMemoryAccess.java @@ -13,18 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.exec; - -import java.util.concurrent.CompletableFuture; +package ghidra.pcode.exec.trace.data; /** - * The state for a {@link AsyncWrappedPcodeExecutorStatePiece} - * - * @param the type of wrapped values + * A data-access shim for a trace's memory */ -public class AsyncWrappedPcodeExecutorState - extends DefaultPcodeExecutorState> { - public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece piece) { - super(new AsyncWrappedPcodeExecutorStatePiece<>(piece)); - } +public interface PcodeTraceMemoryAccess extends PcodeTraceDataAccess { + // Nothing to add } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTracePropertyAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTracePropertyAccess.java new file mode 100644 index 0000000000..2a185f3982 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTracePropertyAccess.java @@ -0,0 +1,60 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; + +/** + * A trace-property access shim for a specific property + * + * @see PcodeTraceAccess + * @see PcodeTraceDataAccess + * + * @param the type of the property's values + */ +public interface PcodeTracePropertyAccess { + /** + * Get the property's value at the given address + * + *

+ * This may search for the same property from other related data sources, e.g., from mapped + * static images. + * + * @param address the address + * @return the value, or null if not set + */ + T get(Address address); + + /** + * Set the property's value at the given address + * + *

+ * The value is affective for future snapshots up to but excluding the next snapshot where + * another value is set at the same address. + * + * @param address the address + * @param value the value to set + */ + void put(Address address, T value); + + /** + * Clear the property's value across a range + * + * @param range the range + */ + void clear(AddressRange range); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceRegistersAccess.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceRegistersAccess.java new file mode 100644 index 0000000000..9569c7ed76 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/data/PcodeTraceRegistersAccess.java @@ -0,0 +1,37 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.data; + +import ghidra.pcode.emu.PcodeThread; + +/** + * A data-access shim for a trace's registers + */ +public interface PcodeTraceRegistersAccess extends PcodeTraceDataAccess { + + /** + * Initialize the given p-code thread's context register using register context from the trace + * at the thread's program counter + * + *

+ * This is called during thread construction, after the program counter is initialized from the + * same trace thread. This will ensure that the instruction decoder starts in the same mode as + * the disassembler was for the trace. + * + * @param thread the thread to initialize + */ + void initializeThreadContext(PcodeThread thread); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java index fa4110452d..260a3018cc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTrace.java @@ -38,6 +38,7 @@ import ghidra.trace.database.breakpoint.DBTraceBreakpointManager; import ghidra.trace.database.context.DBTraceRegisterContextManager; import ghidra.trace.database.data.DBTraceDataSettingsAdapter; import ghidra.trace.database.data.DBTraceDataTypeManager; +import ghidra.trace.database.guest.DBTraceObjectRegisterSupport; import ghidra.trace.database.guest.DBTracePlatformManager; import ghidra.trace.database.listing.DBTraceCodeManager; import ghidra.trace.database.listing.DBTraceCommentAdapter; @@ -567,6 +568,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace @Override public void setChanged(TraceChangeRecord event) { changed = true; + DBTraceObjectRegisterSupport.INSTANCE.processEvent(event); fireEvent(event); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java index bdc7b7801c..30a4d9f81b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java @@ -26,6 +26,8 @@ import db.*; import ghidra.program.model.address.*; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceManager; +import ghidra.trace.model.Trace.TraceOverlaySpaceChangeType; +import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; import ghidra.util.database.*; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; @@ -241,6 +243,8 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager { DBTraceOverlaySpaceEntry ent = overlayStore.create(); ent.set(space.getName(), base.getName()); trace.updateViewsAddSpaceBlock(space); + trace.setChanged(new TraceChangeRecord<>(TraceOverlaySpaceChangeType.ADDED, null, + trace, null, space)); return space; } } @@ -258,6 +262,8 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager { assert space != null; factory.removeOverlaySpace(name); trace.updateViewsDeleteSpaceBlock(space); + trace.setChanged(new TraceChangeRecord<>(TraceOverlaySpaceChangeType.DELETED, null, + trace, space, null)); } } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/TraceAddressFactory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/TraceAddressFactory.java index 1d510a2180..ffc44b7b91 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/TraceAddressFactory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/TraceAddressFactory.java @@ -28,6 +28,12 @@ public class TraceAddressFactory extends ProgramAddressFactory { super(language, compilerSpec); } + @Override + protected boolean validateOriginalSpace(AddressSpace originalSpace) { + return (originalSpace.isMemorySpace() || originalSpace.isRegisterSpace()) && + !originalSpace.isOverlaySpace(); + } + @Override // for peer access protected OverlayAddressSpace addOverlayAddressSpace(String name, boolean preserveName, AddressSpace originalSpace, long minOffset, long maxOffset) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextManager.java index 5e2d8c2e02..437fc816d0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextManager.java @@ -39,6 +39,7 @@ import ghidra.trace.database.space.DBTraceDelegatingManager; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.context.TraceRegisterContextManager; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.thread.TraceThread; import ghidra.util.database.*; import ghidra.util.database.annot.*; @@ -181,10 +182,15 @@ public class DBTraceRegisterContextManager } @Override - public RegisterValue getValueWithDefault(Language language, Register register, long snap, + public RegisterValue getValueWithDefault(TracePlatform platform, Register register, long snap, Address address) { - return delegateReadOr(address.getAddressSpace(), - m -> m.getValueWithDefault(language, register, snap, address), + Address hostAddress = platform.mapGuestToHost(address); + Language language = platform.getLanguage(); + if (hostAddress == null) { + return getDefaultValue(language, register, address); + } + return delegateReadOr(hostAddress.getAddressSpace(), + m -> m.getValueWithDefault(language, register, snap, hostAddress, address), () -> getDefaultValue(language, register, address)); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextSpace.java index 021452aef1..a5aedde077 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextSpace.java @@ -41,6 +41,7 @@ import ghidra.trace.database.space.DBTraceSpaceBased; import ghidra.trace.model.ImmutableTraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.context.TraceRegisterContextSpace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.thread.TraceThread; import ghidra.util.LockHold; import ghidra.util.database.*; @@ -369,20 +370,30 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D } } + protected RegisterValue getValueWithDefault(Language language, Register register, + long snap, Address hostAddress, Address langAddress) { + Register base = register.getBaseRegister(); + RegisterValue baseValue = doGetBaseValue(language, base, snap, hostAddress); + if (baseValue == null) { + return getDefaultValue(language, register, langAddress); + } + RegisterValue defaultBaseValue = getDefaultValue(language, base, langAddress); + if (defaultBaseValue == null) { + return baseValue.getRegisterValue(register); + } + return defaultBaseValue.combineValues(baseValue).getRegisterValue(register); + } + @Override - public RegisterValue getValueWithDefault(Language language, Register register, long snap, - Address address) { + public RegisterValue getValueWithDefault(TracePlatform platform, Register register, long snap, + Address guestAddress) { + Language language = platform.getLanguage(); try (LockHold hold = LockHold.lock(lock.readLock())) { - Register base = register.getBaseRegister(); - RegisterValue baseValue = doGetBaseValue(language, base, snap, address); - if (baseValue == null) { - return getDefaultValue(language, register, address); + Address hostAddress = platform.mapGuestToHost(guestAddress); + if (hostAddress == null) { + return getDefaultValue(language, register, guestAddress); } - RegisterValue defaultBaseValue = getDefaultValue(language, base, address); - if (defaultBaseValue == null) { - return baseValue.getRegisterValue(register); - } - return defaultBaseValue.combineValues(baseValue).getRegisterValue(register); + return getValueWithDefault(language, register, snap, hostAddress, guestAddress); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java index 3f3eb0b025..dac58093b6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatform.java @@ -19,6 +19,8 @@ import java.io.IOException; import java.util.*; import java.util.Map.Entry; +import org.apache.commons.lang3.tuple.Pair; + import com.google.common.collect.Range; import db.DBRecord; @@ -35,6 +37,9 @@ import ghidra.trace.database.DBTraceUtils.LanguageIDDBFieldCodec; import ghidra.trace.model.Trace; import ghidra.trace.model.Trace.TracePlatformChangeType; import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.guest.TraceGuestPlatformMappedRange; +import ghidra.trace.util.OverlappingObjectIterator; +import ghidra.trace.util.OverlappingObjectIterator.Ranger; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; import ghidra.util.database.*; @@ -48,6 +53,33 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject implements TraceGuestPlatform, InternalTracePlatform { public static final String TABLE_NAME = "Platforms"; + private static enum MappedRangeRanger implements Ranger { + HOST { + @Override + AddressRange getRange(DBTraceGuestPlatformMappedRange t) { + return t.getHostRange(); + } + }, + GUEST { + @Override + AddressRange getRange(DBTraceGuestPlatformMappedRange t) { + return t.getGuestRange(); + } + }; + + abstract AddressRange getRange(DBTraceGuestPlatformMappedRange t); + + @Override + public Address getMinAddress(DBTraceGuestPlatformMappedRange t) { + return getRange(t).getMinAddress(); + } + + @Override + public Address getMaxAddress(DBTraceGuestPlatformMappedRange t) { + return getRange(t).getMaxAddress(); + } + } + @DBAnnotatedObjectInfo(version = 0) public static class DBTraceGuestLanguage extends DBAnnotatedObject { public static final String TABLE_NAME = "Languages"; @@ -239,6 +271,36 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject return mappedRange; } + protected Address computeNextRegisterMin() { + Address regMax = manager.hostPlatform.getLanguage() + .getAddressFactory() + .getRegisterSpace() + .getMaxAddress(); + AddressRangeIterator rit = hostAddressSet.getAddressRanges(regMax, false); + if (!rit.hasNext()) { + return null; + } + AddressRange next = rit.next(); + if (!next.getAddressSpace().isRegisterSpace()) { + return null; + } + return next.getMaxAddress().add(1); + } + + @Override + public TraceGuestPlatformMappedRange addMappedRegisterRange() + throws AddressOverflowException { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + AddressRange guestRange = getRegistersRange(); + if (guestRange == null) { + return null; // No registers, so we're mapped! + } + Address hostMin = manager.computeNextRegisterMin(); + long size = guestRange.getLength(); + return addMappedRange(hostMin, guestRange.getMinAddress(), size); + } + } + @Override public AddressSetView getHostAddressSet() { return new AddressSet(hostAddressSet); @@ -261,6 +323,34 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject } } + @Override + public AddressRange mapHostToGuest(AddressRange hostRange) { + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + Entry floorEntry = + rangesByHostAddress.floorEntry(hostRange.getMinAddress()); + if (floorEntry == null) { + return null; + } + return floorEntry.getValue().mapHostToGuest(hostRange); + } + } + + @Override + public AddressSetView mapHostToGuest(AddressSetView hostSet) { + Iterator> it = + new OverlappingObjectIterator( + rangesByHostAddress.values().iterator(), MappedRangeRanger.HOST, + hostSet.iterator(), OverlappingObjectIterator.ADDRESS_RANGE); + AddressSet result = new AddressSet(); + while (it.hasNext()) { + Pair next = it.next(); + DBTraceGuestPlatformMappedRange entry = next.getLeft(); + AddressRange hostRange = next.getRight(); + result.add(entry.mapHostToGuest(hostRange)); + } + return result; + } + @Override public Address mapGuestToHost(Address guestAddress) { try (LockHold hold = LockHold.lock(manager.lock.readLock())) { @@ -273,6 +363,34 @@ public class DBTraceGuestPlatform extends DBAnnotatedObject } } + @Override + public AddressRange mapGuestToHost(AddressRange guestRange) { + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + Entry floorEntry = + rangesByGuestAddress.floorEntry(guestRange.getMinAddress()); + if (floorEntry == null) { + return null; + } + return floorEntry.getValue().mapGuestToHost(guestRange); + } + } + + @Override + public AddressSetView mapGuestToHost(AddressSetView guestSet) { + Iterator> it = + new OverlappingObjectIterator( + rangesByGuestAddress.values().iterator(), MappedRangeRanger.GUEST, + guestSet.iterator(), OverlappingObjectIterator.ADDRESS_RANGE); + AddressSet result = new AddressSet(); + while (it.hasNext()) { + Pair next = it.next(); + DBTraceGuestPlatformMappedRange entry = next.getLeft(); + AddressRange hostRange = next.getRight(); + result.add(entry.mapGuestToHost(hostRange)); + } + return result; + } + /** * Map the an address only if the entire range is contained in a single mapped range * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java index 29d17c8df5..c1d6fc6f3c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceGuestPlatformMappedRange.java @@ -19,8 +19,6 @@ import java.io.IOException; import db.DBRecord; import ghidra.program.model.address.*; -import ghidra.program.model.lang.CompilerSpec; -import ghidra.program.model.lang.Language; import ghidra.trace.model.guest.TraceGuestPlatformMappedRange; import ghidra.util.database.*; import ghidra.util.database.annot.*; @@ -53,13 +51,13 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject static DBObjectColumn LENGTH_COLUMN; @DBAnnotatedField(column = HOST_SPACE_COLUMN_NAME) - private int hostSpace; + private int hostSpaceID; @DBAnnotatedField(column = HOST_OFFSET_COLUMN_NAME) private long hostOffset; @DBAnnotatedField(column = GUEST_LANGUAGE_COLUMN_NAME) int guestPlatformKey; @DBAnnotatedField(column = GUEST_SPACE_COLUMN_NAME) - private int guestSpace; + private int guestSpaceID; @DBAnnotatedField(column = GUEST_OFFSET_COLUMN_NAME) private long guestOffset; @DBAnnotatedField(column = LENGTH_COLUMN_NAME) @@ -70,6 +68,9 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject private AddressRangeImpl hostRange; private DBTraceGuestPlatform platform; private AddressRangeImpl guestRange; + private AddressSpace hostSpace; + private AddressSpace guestSpace; + private long shiftHostToGuest; public DBTraceGuestPlatformMappedRange(DBTracePlatformManager manager, DBCachedObjectStore s, DBRecord r) { @@ -83,10 +84,8 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject if (created) { return; } - Address hostStart = - manager.trace.getBaseLanguage() - .getAddressFactory() - .getAddress(hostSpace, hostOffset); + this.hostSpace = manager.trace.getBaseAddressFactory().getAddressSpace(hostSpaceID); + Address hostStart = hostSpace.getAddress(hostOffset); Address hostEnd = hostStart.addWrap(length - 1); this.hostRange = new AddressRangeImpl(hostStart, hostEnd); @@ -95,16 +94,21 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject throw new IOException("Table is corrupt. Got host platform in guest mapping."); } this.platform = (DBTraceGuestPlatform) platform; - Address guestStart = platform.getAddressFactory().getAddress(guestSpace, guestOffset); + this.guestSpace = platform.getAddressFactory().getAddressSpace(guestSpaceID); + Address guestStart = guestSpace.getAddress(guestOffset); Address guestEnd = guestStart.addWrap(length - 1); this.guestRange = new AddressRangeImpl(guestStart, guestEnd); + + this.shiftHostToGuest = guestStart.getOffset() - hostStart.getOffset(); } void set(Address hostStart, DBTraceGuestPlatform platform, Address guestStart, long length) { - this.hostSpace = hostStart.getAddressSpace().getSpaceID(); + this.hostSpace = hostStart.getAddressSpace(); + this.hostSpaceID = hostSpace.getSpaceID(); this.hostOffset = hostStart.getOffset(); this.guestPlatformKey = (int) platform.getKey(); - this.guestSpace = guestStart.getAddressSpace().getSpaceID(); + this.guestSpace = guestStart.getAddressSpace(); + this.guestSpaceID = guestSpace.getSpaceID(); this.guestOffset = guestStart.getOffset(); this.length = length; update(HOST_SPACE_COLUMN, HOST_OFFSET_COLUMN, GUEST_LANGUAGE_COLUMN, GUEST_SPACE_COLUMN, @@ -114,16 +118,12 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject this.platform = platform; this.guestRange = new AddressRangeImpl(guestStart, guestStart.addWrap(length - 1)); + this.shiftHostToGuest = guestStart.getOffset() - hostStart.getOffset(); } @Override - public Language getHostLanguage() { - return manager.trace.getBaseLanguage(); - } - - @Override - public CompilerSpec getHostCompilerSpec() { - return manager.trace.getBaseCompilerSpec(); + public InternalTracePlatform getHostPlatform() { + return manager.hostPlatform; } @Override @@ -141,13 +141,31 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject return guestRange; } + protected static Address doMapTo(Address address, AddressSpace space, long shift) { + return space.getAddress(address.getOffset() + shift); + } + + protected static AddressRange doMapTo(AddressRange range, AddressSpace space, long shift) { + return new AddressRangeImpl( + doMapTo(range.getMinAddress(), space, shift), + doMapTo(range.getMaxAddress(), space, shift)); + } + @Override public Address mapHostToGuest(Address hostAddress) { if (!hostRange.contains(hostAddress)) { return null; } - long offset = hostAddress.subtract(hostRange.getMinAddress()); - return guestRange.getMinAddress().add(offset); + return doMapTo(hostAddress, guestSpace, shiftHostToGuest); + } + + @Override + public AddressRange mapHostToGuest(AddressRange hostRange) { + if (!this.hostRange.contains(hostRange.getMinAddress()) || + !this.hostRange.contains(hostRange.getMaxAddress())) { + return null; + } + return doMapTo(hostRange, guestSpace, shiftHostToGuest); } @Override @@ -155,8 +173,16 @@ public class DBTraceGuestPlatformMappedRange extends DBAnnotatedObject if (!guestRange.contains(guestAddress)) { return null; } - long offset = guestAddress.subtract(guestRange.getMinAddress()); - return hostRange.getMinAddress().add(offset); + return doMapTo(guestAddress, hostSpace, -shiftHostToGuest); + } + + @Override + public AddressRange mapGuestToHost(AddressRange guestRange) { + if (!this.guestRange.contains(guestRange.getMinAddress()) || + !this.guestRange.contains(guestRange.getMaxAddress())) { + return null; + } + return doMapTo(guestRange, hostSpace, -shiftHostToGuest); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java new file mode 100644 index 0000000000..7fd7207356 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupport.java @@ -0,0 +1,492 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.guest; + +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.stream.Stream; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetRegister; +import ghidra.dbg.target.TargetRegisterContainer; +import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.dbg.util.PathMatcher; +import ghidra.dbg.util.PathPredicates.Align; +import ghidra.dbg.util.PathUtils; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.*; +import ghidra.trace.model.TraceDomainObjectListener; +import ghidra.trace.model.guest.*; +import ghidra.trace.model.memory.TraceMemoryManager; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.symbol.*; +import ghidra.trace.model.target.*; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.util.Msg; + +public enum DBTraceObjectRegisterSupport { + // TODO: Could/should this be done by an analyzer, instead of being internal trace logic? + INSTANCE; + + private static final TraceDomainObjectListener HANDLER = new TraceDomainObjectListener() { + { + listenFor(TraceObjectChangeType.VALUE_CREATED, INSTANCE::objectValueCreated); + listenFor(TraceSymbolChangeType.ADDED, INSTANCE::symbolAdded); + listenFor(TraceOverlaySpaceChangeType.ADDED, INSTANCE::spaceAdded); + listenFor(TracePlatformChangeType.MAPPING_ADDED, INSTANCE::guestMappingAdded); + } + }; + + static class RegisterValueException extends Exception { + public RegisterValueException(String message) { + super(message); + } + } + + static class LazyValues { + private final TraceObjectValue registerValue; + private BigInteger value; + private int length = -1; + private byte[] be; + private byte[] le; + + public LazyValues(TraceObjectValue registerValue) { + this.registerValue = registerValue; + } + + BigInteger convertRegisterValueToBigInteger() throws RegisterValueException { + Object val = registerValue.getValue(); + if (val instanceof String s) { + try { + return new BigInteger(s, 16); + } + catch (NumberFormatException e) { + throw new RegisterValueException( + "Invalid register value " + s + ". Must be hex digits only."); + } + } + if (val instanceof Byte b) { + return BigInteger.valueOf(b); + } + else if (val instanceof Short s) { + return BigInteger.valueOf(s); + } + else if (val instanceof Integer i) { + return BigInteger.valueOf(i); + } + else if (val instanceof Long l) { + return BigInteger.valueOf(l); + } + else if (val instanceof Address a) { + return a.getOffsetAsBigInteger(); + } + throw new RegisterValueException( + "Cannot convert register value: (" + registerValue.getValue().getClass() + ") '" + + registerValue.getValue() + + "'"); + } + + int getRegisterValueLength() throws RegisterValueException { + Object objLength = registerValue.getParent() + .getValue(registerValue.getMinSnap(), TargetRegister.LENGTH_ATTRIBUTE_NAME) + .getValue(); + if (!(objLength instanceof Number)) { + throw new RegisterValueException( + "Register length is not numeric: (" + objLength.getClass() + ") '" + objLength + + "'"); + } + return ((Number) objLength).intValue(); + } + + BigInteger getValue() throws RegisterValueException { + if (value != null) { + return value; + } + return value = convertRegisterValueToBigInteger(); + } + + int getLength() throws RegisterValueException { + if (length != -1) { + return length; + } + return length = getRegisterValueLength(); + } + + byte[] getBytesBigEndian() throws RegisterValueException { + if (be != null) { + return be; + } + return be = Utils.bigIntegerToBytes(getValue(), getLength(), true); + } + + byte[] getBytesLittleEndian() throws RegisterValueException { + if (le != null) { + return le; + } + return le = Utils.bigIntegerToBytes(getValue(), getLength(), false); + } + + public byte[] getBytes(boolean isBigEndian) throws RegisterValueException { + return isBigEndian ? getBytesBigEndian() : getBytesLittleEndian(); + } + } + + protected AddressSpace findRegisterOverlay(TraceObject object) { + TraceObject container = object + .queryCanonicalAncestorsTargetInterface(TargetRegisterContainer.class) + .findFirst() + .orElse(null); + if (container == null) { + return null; + } + String pathStr = container.getCanonicalPath().toString(); + AddressSpace space = object.getTrace().getBaseAddressFactory().getAddressSpace(pathStr); + if (space == null) { + return null; + } + if (!space.isRegisterSpace()) { + return null; + } + return space; + } + + protected AddressSpace findRegisterOverlay(TraceObjectValue objectValue) { + return findRegisterOverlay(objectValue.getParent()); + } + + protected void onValueCreatedTransferToPlatformRegister(TraceObjectValue registerValue, + TracePlatform platform, String name, LazyValues lazy) throws RegisterValueException { + Register register = platform.getLanguage().getRegister(name); + if (register == null) { + return; + } + + Address hostAddr = platform.mapGuestToHost(register.getAddress()); + if (hostAddr == null) { + return; + } + AddressSpace hostSpace = hostAddr.getAddressSpace(); + TraceMemoryManager mem = registerValue.getTrace().getMemoryManager(); + long minSnap = registerValue.getMinSnap(); + if (hostSpace.isMemorySpace()) { + mem.getMemorySpace(hostSpace, true) + .setValue(platform, minSnap, new RegisterValue(register, lazy.getValue())); + } + else if (hostSpace.isRegisterSpace()) { + AddressSpace overlay = findRegisterOverlay(registerValue); + if (overlay == null) { + return; + } + mem.getMemorySpace(overlay, true) + .setValue(platform, minSnap, new RegisterValue(register, lazy.getValue())); + } + else { + throw new AssertionError(); + } + } + + protected void transferValueToPlatformRegister(TraceObjectValue registerValue, + TracePlatform platform, TraceMemorySpace mem, Register register) { + LazyValues lazy = new LazyValues(registerValue); + try { + mem.setValue(platform, registerValue.getMinSnap(), + new RegisterValue(register, lazy.getValue())); + } + catch (RegisterValueException e) { + Msg.error(this, e.getMessage()); + } + } + + protected String getRegisterName(TraceObject registerObject) { + String name = registerObject.getCanonicalPath().key(); + if (PathUtils.isIndex(name)) { + return PathUtils.parseIndex(name); + } + return name; + } + + protected void onSpaceAddedCheckTransferObjectToPlatformRegister(TraceObject registerObject, + TracePlatform platform, TraceMemorySpace mem) { + String name = getRegisterName(registerObject); + Register register = platform.getLanguage().getRegister(name); + if (register == null || !register.getAddressSpace().isRegisterSpace()) { + return; + } + for (TraceObjectValue registerValue : it(registerObject.getOrderedValues(Range.all(), + TargetRegister.VALUE_ATTRIBUTE_NAME, true))) { + transferValueToPlatformRegister(registerValue, platform, mem, register); + } + } + + protected void onSpaceAddedCheckTransferToPlatformRegisters(TracePlatform platform, + TraceObject regContainer, TraceMemorySpace mem) { + for (TraceObjectValPath path : it( + regContainer.querySuccessorsTargetInterface(Range.all(), TargetRegister.class))) { + TraceObject registerObject = + path.getDestination(platform.getTrace().getObjectManager().getRootObject()); + onSpaceAddedCheckTransferObjectToPlatformRegister(registerObject, platform, mem); + } + } + + protected TraceMemorySpace getMemorySpace(TraceObject object, TraceLabelSymbol label) { + Address hostAddr = label.getAddress(); + AddressSpace hostSpace = hostAddr.getAddressSpace(); + TraceMemoryManager mem = label.getTrace().getMemoryManager(); + if (hostSpace.isMemorySpace()) { + return mem.getMemorySpace(hostSpace, true); + } + else if (hostSpace.isRegisterSpace()) { + AddressSpace overlay = findRegisterOverlay(object); + return mem.getMemorySpace(overlay, true); + } + else { + throw new AssertionError(); + } + } + + protected void transferRegisterValueToLabel(TraceObjectValue registerValue, + TraceLabelSymbol label, byte[] value) { + TraceMemorySpace mem = getMemorySpace(registerValue.getParent(), label); + Address hostAddr = label.getAddress(); + long minSnap = registerValue.getMinSnap(); + Address address = mem.getAddressSpace().getOverlayAddress(hostAddr); + mem.putBytes(minSnap, address, ByteBuffer.wrap(value)); + } + + protected static Iterable it(Stream stream) { + return () -> stream.iterator(); + } + + protected void transferRegisterObjectToLabel(TraceObject registerObject, TraceLabelSymbol label, + boolean isBigEndian) { + TraceMemorySpace mem = getMemorySpace(registerObject, label); + Address address = mem.getAddressSpace().getOverlayAddress(label.getAddress()); + for (TraceObjectValue registerValue : it(registerObject.getOrderedValues( + label.getLifespan(), TargetRegister.VALUE_ATTRIBUTE_NAME, true))) { + LazyValues lazy = new LazyValues(registerValue); + try { + long minSnap = registerValue.getMinSnap(); + mem.putBytes(minSnap, address, ByteBuffer.wrap(lazy.getBytes(isBigEndian))); + } + catch (RegisterValueException e) { + Msg.error(this, e.getMessage()); + } + } + } + + public void onValueCreatedTransfer(TraceObjectValue registerValue) + throws RegisterValueException { + TraceObject registerObject = registerValue.getParent(); + Trace trace = registerValue.getTrace(); + LazyValues lazy = new LazyValues(registerValue); + + String name = getRegisterName(registerObject); + + TracePlatformManager platformManager = trace.getPlatformManager(); + onValueCreatedTransferToPlatformRegister(registerValue, platformManager.getHostPlatform(), + name, lazy); + for (TracePlatform platform : platformManager.getGuestPlatforms()) { + onValueCreatedTransferToPlatformRegister(registerValue, platform, name, lazy); + } + + TraceNamespaceSymbolView namespaces = trace.getSymbolManager().namespaces(); + TraceNamespaceSymbol nsRegMapBE = + namespaces.getGlobalNamed(InternalTracePlatform.REG_MAP_BE); + if (nsRegMapBE != null) { + for (TraceLabelSymbol label : trace.getSymbolManager() + .labels() + .getChildrenNamed(name, nsRegMapBE)) { + transferRegisterValueToLabel(registerValue, label, lazy.getBytesBigEndian()); + } + } + TraceNamespaceSymbol nsRegMapLE = + namespaces.getGlobalNamed(InternalTracePlatform.REG_MAP_LE); + if (nsRegMapLE != null) { + for (TraceLabelSymbol label : trace.getSymbolManager() + .labels() + .getChildrenNamed(name, nsRegMapLE)) { + transferRegisterValueToLabel(registerValue, label, lazy.getBytesLittleEndian()); + } + } + } + + protected boolean isRegisterValue(TraceObjectValue objectValue) { + TraceObject parent = objectValue.getParent(); + return parent != null && + parent.getTargetSchema().getInterfaces().contains(TargetRegister.class) && + TargetRegister.VALUE_ATTRIBUTE_NAME.equals(objectValue.getEntryKey()); + } + + public void onValueCreatedCheckTransfer(TraceObjectValue objectValue) { + if (isRegisterValue(objectValue)) { + try { + onValueCreatedTransfer(objectValue); + } + catch (RegisterValueException e) { + Msg.error(this, e.getMessage()); + } + } + } + + public void onSymbolAddedCheckTransferToLabel(TraceLabelSymbol label, boolean isBigEndian) { + TraceObjectManager objectManager = label.getTrace().getObjectManager(); + TargetObjectSchema schema = objectManager.getRootSchema(); + if (schema == null) { + return; + } + PathMatcher matcher = schema.searchFor(TargetRegister.class, true); + matcher = matcher.applyKeys(Align.RIGHT, List.of(label.getName())); + for (TraceObjectValPath path : it( + objectManager.getValuePaths(label.getLifespan(), matcher))) { + Object regRaw = path.getDestinationValue(objectManager.getRootObject()); + if (regRaw instanceof TraceObject regObj) { + transferRegisterObjectToLabel(regObj, label, isBigEndian); + } + } + } + + public void onSymbolAddedCheckTransfer(TraceSymbol symbol) { + TraceObject root = symbol.getTrace().getObjectManager().getRootObject(); + if (root == null) { + return; + } + if (symbol instanceof TraceLabelSymbol label) { + TraceNamespaceSymbolView namespaces = label.getTrace().getSymbolManager().namespaces(); + TraceNamespaceSymbol regMapBE = + namespaces.getGlobalNamed(InternalTracePlatform.REG_MAP_BE); + TraceNamespaceSymbol regMapLE = + namespaces.getGlobalNamed(InternalTracePlatform.REG_MAP_LE); + if (label.getParentNamespace() == regMapBE) { + onSymbolAddedCheckTransferToLabel(label, true); + } + else if (label.getParentNamespace() == regMapLE) { + onSymbolAddedCheckTransferToLabel(label, false); + } + } + } + + public void onSpaceAddedCheckTransfer(Trace trace, AddressSpace space) { + TraceObject root = trace.getObjectManager().getRootObject(); + if (root == null) { + return; + } + assert space.isOverlaySpace(); + if (!space.isRegisterSpace()) { + return; + } + TraceMemorySpace mem = trace.getMemoryManager().getMemorySpace(space, true); + TraceObject regContainer = trace.getObjectManager() + .getObjectByCanonicalPath( + TraceObjectKeyPath.parse(mem.getAddressSpace().getName())); + if (regContainer == null || !regContainer.getTargetSchema() + .getInterfaces() + .contains(TargetRegisterContainer.class)) { + return; + } + TracePlatformManager platformManager = trace.getPlatformManager(); + onSpaceAddedCheckTransferToPlatformRegisters(platformManager.getHostPlatform(), + regContainer, mem); + for (TraceGuestPlatform platform : platformManager.getGuestPlatforms()) { + onSpaceAddedCheckTransferToPlatformRegisters(platform, regContainer, mem); + } + } + + protected void onMappingAddedCheckTransferRegisterObjectMemoryMapped(TraceObject registerObject, + TraceGuestPlatformMappedRange mapped) { + String name = getRegisterName(registerObject); + TraceGuestPlatform guest = mapped.getGuestPlatform(); + Register register = guest.getLanguage().getRegister(name); + // TODO: Permit overlay spaces? + if (register == null || mapped.getGuestRange().contains(register.getAddress())) { + return; + } + Address hostAddr = mapped.mapGuestToHost(register.getAddress()); + if (hostAddr == null) { + return; + } + TraceMemorySpace mem = registerObject.getTrace() + .getMemoryManager() + .getMemorySpace(hostAddr.getAddressSpace(), true); + for (TraceObjectValue registerValue : it(registerObject.getOrderedValues(Range.all(), + TargetRegister.VALUE_ATTRIBUTE_NAME, true))) { + transferValueToPlatformRegister(registerValue, guest, mem, register); + } + } + + public void onMappingAddedCheckTransferMemoryMapped(TraceObject root, + TraceGuestPlatformMappedRange mapped) { + for (TraceObjectValPath path : it( + root.querySuccessorsTargetInterface(Range.all(), TargetRegister.class))) { + TraceObject registerObject = path.getDestination(root); + onMappingAddedCheckTransferRegisterObjectMemoryMapped(registerObject, mapped); + } + } + + public void onMappingAddedCheckTransfer(TraceGuestPlatformMappedRange mapped) { + Trace trace = mapped.getHostPlatform().getTrace(); + TraceObject root = trace.getObjectManager().getRootObject(); + if (root == null) { + return; + } + AddressSpace guestSpace = mapped.getGuestRange().getAddressSpace(); + if (guestSpace.isRegisterSpace()) { + // TODO: Optimize: create/use TraceAddressFactory.getOverlaySpaces() + for (AddressSpace space : trace.getBaseAddressFactory().getAllAddressSpaces()) { + if (!space.isOverlaySpace()) { + continue; + } + onSpaceAddedCheckTransfer(trace, space); + } + } + else if (guestSpace.isMemorySpace()) { + if (guestSpace.isOverlaySpace()) { + return; + } + onMappingAddedCheckTransferMemoryMapped(root, mapped); + } + else { + throw new AssertionError(); + } + } + + public void processEvent(TraceChangeRecord event) { + HANDLER.handleTraceChangeRecord(event); + } + + private void objectValueCreated(TraceObjectValue objectValue) { + onValueCreatedCheckTransfer(objectValue); + } + + private void symbolAdded(TraceSymbol symbol) { + onSymbolAddedCheckTransfer(symbol); + } + + private void spaceAdded(Trace trace, AddressSpace isNull, AddressSpace space) { + onSpaceAddedCheckTransfer(trace, space); + } + + private void guestMappingAdded(TraceGuestPlatform guest, TraceGuestPlatformMappedRange isNull, + TraceGuestPlatformMappedRange mapped) { + onMappingAddedCheckTransfer(mapped); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java index f9a05376be..d69b2011c0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/DBTracePlatformManager.java @@ -21,8 +21,7 @@ import java.util.concurrent.locks.ReadWriteLock; import db.DBHandle; import ghidra.lifecycle.Internal; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.database.DBTrace; @@ -106,11 +105,31 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana return hostAddress; } + @Override + public AddressRange mapHostToGuest(AddressRange hostRange) { + return hostRange; + } + + @Override + public AddressSetView mapHostToGuest(AddressSetView hostSet) { + return hostSet; + } + @Override public Address mapGuestToHost(Address guestAddress) { return guestAddress; } + @Override + public AddressRange mapGuestToHost(AddressRange guestRange) { + return guestRange; + } + + @Override + public AddressSetView mapGuestToHost(AddressSetView guestSet) { + return guestSet; + } + @Override public MemBuffer getMappedMemBuffer(long snap, Address guestAddress) { return trace.getMemoryManager().getBufferAt(snap, guestAddress); @@ -342,4 +361,18 @@ public class DBTracePlatformManager implements DBTraceManager, TracePlatformMana } return dbPlatform; } + + protected Address computeNextRegisterMin() { + AddressRange hostRegsRange = hostPlatform.getRegistersRange(); + Address next = hostRegsRange == null + ? hostPlatform.getAddressFactory().getRegisterSpace().getAddress(0) + : hostRegsRange.getMaxAddress().add(1); + for (DBTraceGuestPlatform guest : platformStore.asMap().values()) { + Address candidateNext = guest.computeNextRegisterMin(); + if (candidateNext != null && next.compareTo(candidateNext) < 0) { + next = candidateNext; + } + } + return next; + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java index 02c2b3b960..5d85c1194e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/guest/InternalTracePlatform.java @@ -15,10 +15,34 @@ */ package ghidra.trace.database.guest; +import java.util.Collection; +import java.util.List; + +import ghidra.dbg.target.TargetRegister; +import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.dbg.util.PathMatcher; +import ghidra.dbg.util.PathPredicates.Align; +import ghidra.dbg.util.PathUtils; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.program.model.symbol.SourceType; import ghidra.trace.database.guest.DBTraceGuestPlatform.DBTraceGuestLanguage; import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.symbol.*; +import ghidra.trace.util.TraceRegisterUtils; +import ghidra.util.LockHold; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.InvalidInputException; public interface InternalTracePlatform extends TracePlatform { + String REG_MAP_BE = "__reg_map_be__"; + String REG_MAP_LE = "__reg_map_le__"; + + static String regMap(Register register) { + return register.isBigEndian() ? REG_MAP_BE : REG_MAP_LE; + } + /** * Get the entry's key in the table as an integer * @@ -27,4 +51,93 @@ public interface InternalTracePlatform extends TracePlatform { int getIntKey(); DBTraceGuestLanguage getLanguageEntry(); + + @Override + default AddressRange getConventionalRegisterRange(AddressSpace space, Register register) { + AddressRange result = mapGuestToHost(TraceRegisterUtils.rangeForRegister(register)); + if (result == null) { + throw new IllegalArgumentException("Register " + register + " is not mapped"); + } + if (space == null) { + return result; + } + if (register.getAddressSpace().isRegisterSpace()) { + if (result.getAddressSpace() != space.getPhysicalSpace()) { + throw new IllegalArgumentException( + "Register " + register + " does not map to space " + space + + "'s physical space (" + space.getPhysicalSpace() + ")"); + } + return new AddressRangeImpl( + space.getOverlayAddress(result.getMinAddress()), + space.getOverlayAddress(result.getMaxAddress())); + } + if (result.getAddressSpace() != space) { + throw new IllegalArgumentException( + "Memory-mapped register " + register + " does not map to space " + space); + } + return result; + } + + @Override + default PathMatcher getConventionalRegisterPath(AddressSpace space, Register register) { + List path = PathUtils.parse(space.getName()); + TargetObjectSchema rootSchema = getTrace().getObjectManager().getRootSchema(); + if (rootSchema == null) { + return null; + } + TargetObjectSchema schema = rootSchema + .getSuccessorSchema(path); + PathMatcher matcher = schema.searchFor(TargetRegister.class, path, true); + if (matcher.isEmpty()) { + return matcher; + } + Address pmin = mapGuestToHost(register.getAddress()); + TraceSymbolManager symbolManager = getTrace().getSymbolManager(); + TraceNamespaceSymbol nsRegMap = symbolManager.namespaces().getGlobalNamed(regMap(register)); + Collection labels = symbolManager.labels() + .getAt(0, null, pmin, false) + .stream() + .filter(s -> s.getParentNamespace() == nsRegMap) + .toList(); + String name = labels.isEmpty() ? register.getName() : labels.iterator().next().getName(); + return matcher.applyKeys(Align.RIGHT, List.of(name)); + } + + @Override + default TraceLabelSymbol addRegisterMapOverride(Register register, String objectName) { + Address hostAddr = mapGuestToHost(register.getAddress()); + if (hostAddr == null) { + throw new IllegalStateException( + "Given register is not mapped to the host, or it's not in the guest language"); + } + try (LockHold hold = getTrace().lockWrite()) { + TraceSymbolManager symbolManager = getTrace().getSymbolManager(); + TraceNamespaceSymbol globals = symbolManager.getGlobalNamespace(); + TraceNamespaceSymbolView namespaces = symbolManager.namespaces(); + String regMap = regMap(register); + TraceNamespaceSymbol nsRegMap = namespaces.getGlobalNamed(regMap); + if (nsRegMap == null) { + nsRegMap = namespaces.add(regMap, globals, SourceType.USER_DEFINED); + } + return symbolManager.labels() + .create(0, null, hostAddr, objectName, nsRegMap, SourceType.USER_DEFINED); + } + catch (DuplicateNameException | InvalidInputException e) { + // I checked for the namespace first and with a write lock + // The input is REG_MAP, which had better be valid + throw new AssertionError(); + } + } + + default AddressRange getRegistersRange() { + Language language = getLanguage(); + AddressSpace regSpace = language.getAddressFactory().getRegisterSpace(); + AddressSetView regAddrs = language.getRegisterAddresses(); + AddressIterator minIt = regAddrs.getAddresses(regSpace.getMinAddress(), true); + if (!minIt.hasNext()) { + return null; + } + AddressIterator maxIt = regAddrs.getAddresses(regSpace.getMaxAddress(), false); + return new AddressRangeImpl(minIt.next(), maxIt.next()); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java index 70801d261f..fc284f595c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstruction.java @@ -668,8 +668,9 @@ public class DBTraceInstruction extends AbstractDBTraceCodeUnit - implements TraceMemoryManager, DBTraceDelegatingManager { + implements TraceMemoryManager, InternalTraceMemoryOperations, + DBTraceDelegatingManager { protected static final String NAME = "Memory"; @@ -65,6 +66,11 @@ public class DBTraceMemoryManager extends AbstractDBTraceSpaceBasedManager rn) { // Nothing @@ -733,6 +743,7 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace @Override public int getViewBytes(long snap, Address start, ByteBuffer buf) { + assertInSpace(start); AddressRange toRead; int len = truncateLen(buf.remaining(), start); if (len == 0) { @@ -849,7 +860,6 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace return null; } - // TODO: Test this protected boolean doCheckBytesChanged(OffsetSnap loc, int srcOffset, int maxLen, ByteBuffer eBuf, ByteBuffer pBuf) throws IOException { DBTraceMemoryBlockEntry ent = findMostRecentBlockEntry(loc, true); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/InternalTraceMemoryOperations.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/InternalTraceMemoryOperations.java new file mode 100644 index 0000000000..3342019583 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/InternalTraceMemoryOperations.java @@ -0,0 +1,139 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import java.nio.ByteBuffer; +import java.util.Collection; +import java.util.Map.Entry; +import java.util.concurrent.locks.ReadWriteLock; + +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.memory.TraceMemoryOperations; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.util.TraceRegisterUtils; +import ghidra.util.LockHold; + +public interface InternalTraceMemoryOperations extends TraceMemoryOperations { + + static TraceMemoryState requireOne( + Collection> states, Register register) { + if (states.isEmpty()) { + return TraceMemoryState.UNKNOWN; + } + if (states.size() != 1) { + throw new IllegalStateException("More than one state is present in " + register); + } + return states.iterator().next().getValue(); + } + + /** + * For register mapping conventions + * + * @return the address space + */ + AddressSpace getSpace(); + + ReadWriteLock getLock(); + + @Override + default void setState(TracePlatform platform, long snap, Register register, + TraceMemoryState state) { + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + setState(snap, range, state); + } + + @Override + default TraceMemoryState getState(TracePlatform platform, long snap, Register register) { + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + return requireOne(getStates(snap, range), register); + } + + @Override + default Collection> getStates( + TracePlatform platform, long snap, Register register) { + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + return getStates(snap, range); + } + + @Override + default int putBytes(TracePlatform platform, long snap, Register register, ByteBuffer buf) { + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + int byteLength = (int) range.getLength(); + int limit = buf.limit(); + buf.limit(Math.min(limit, buf.position() + byteLength)); + int result = putBytes(snap, range.getMinAddress(), buf); + buf.limit(limit); + return result; + } + + @Override + default int setValue(TracePlatform platform, long snap, RegisterValue value) { + if (!value.hasAnyValue()) { + return 0; + } + try (LockHold hold = LockHold.lock(getLock().writeLock())) { + Register register = value.getRegister(); + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + if (!value.hasValue() || !TraceRegisterUtils.isByteBound(register)) { + // Don't try to inline to keep range. Base register may have different range + RegisterValue old = getValue(platform, snap, register.getBaseRegister()); + // Do not use .getRegisterValue, as that will zero unmasked bits + // Instead, we'll pass the original register to bufferForValue + value = old.combineValues(value); + } + ByteBuffer buf = TraceRegisterUtils.bufferForValue(register, value); + return putBytes(snap, range.getMinAddress(), buf); + } + } + + @Override + default RegisterValue getValue(TracePlatform platform, long snap, Register register) { + ByteBuffer buf = TraceRegisterUtils.prepareBuffer(register); + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + getBytes(snap, range.getMinAddress(), buf); + return TraceRegisterUtils.finishBuffer(buf, register); + } + + @Override + default RegisterValue getViewValue(TracePlatform platform, long snap, Register register) { + ByteBuffer buf = TraceRegisterUtils.prepareBuffer(register); + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + getViewBytes(snap, range.getMinAddress(), buf); + return TraceRegisterUtils.finishBuffer(buf, register); + } + + @Override + default int getBytes(TracePlatform platform, long snap, Register register, ByteBuffer buf) { + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + int byteLength = (int) range.getLength(); + int limit = buf.limit(); + buf.limit(Math.min(limit, buf.position() + byteLength)); + int result = getBytes(snap, range.getMinAddress(), buf); + buf.limit(limit); + return result; + } + + @Override + default void removeValue(TracePlatform platform, long snap, Register register) { + AddressRange range = platform.getConventionalRegisterRange(getSpace(), register); + removeBytes(snap, range.getMinAddress(), (int) range.getLength()); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java index b39282def7..ed5dd390dc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java @@ -23,6 +23,7 @@ import com.google.common.collect.Range; import ghidra.dbg.target.*; import ghidra.dbg.util.PathMatcher; +import ghidra.dbg.util.PathPredicates.Align; import ghidra.dbg.util.PathUtils; import ghidra.program.model.address.*; import ghidra.trace.database.DBTraceUtils; @@ -248,7 +249,7 @@ public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInte @Override public TraceObjectSection getSectionByName(String sectionName) { PathMatcher matcher = object.getTargetSchema().searchFor(TargetSection.class, true); - PathMatcher applied = matcher.applyKeys(List.of(sectionName)); + PathMatcher applied = matcher.applyKeys(Align.LEFT, List.of(sectionName)); return object.getSuccessors(getLifespan(), applied) .map(p -> p.getDestination(object).queryInterface(TraceObjectSection.class)) .findAny() diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewReferenceManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewReferenceManager.java index 2766886972..7595703d39 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewReferenceManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewReferenceManager.java @@ -15,7 +15,7 @@ */ package ghidra.trace.database.program; -import static ghidra.lifecycle.Unfinished.*; +import static ghidra.lifecycle.Unfinished.TODO; import java.util.*; import java.util.function.Function; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java index ab83326ef5..8b0a192614 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java @@ -25,17 +25,25 @@ import org.apache.commons.lang3.tuple.Pair; import db.DBHandle; import db.DBRecord; import generic.CatenatedCollection; +import ghidra.dbg.target.TargetRegisterContainer; +import ghidra.dbg.util.PathPattern; +import ghidra.dbg.util.PathPredicates; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.trace.database.*; import ghidra.trace.database.thread.DBTraceThreadManager; +import ghidra.trace.model.stack.TraceObjectStackFrame; import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceAddressSpace; import ghidra.util.LockHold; import ghidra.util.Msg; import ghidra.util.database.*; import ghidra.util.database.annot.*; +import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; @@ -97,6 +105,8 @@ public abstract class AbstractDBTraceSpaceBasedManager memSpaces = new TreeMap<>(); // Note: can use hash map here. I see no need to order these spaces protected final Map, M> regSpaces = new HashMap<>(); + protected final Map regSpacesByObject = new HashMap<>(); + protected final Collection memSpacesView = Collections.unmodifiableCollection(memSpaces.values()); protected final Collection regSpacesView = @@ -168,12 +178,11 @@ public abstract class AbstractDBTraceSpaceBasedManager frame = ImmutablePair.of(thread, frameLevel); if (!createIfAbsent) { try (LockHold hold = LockHold.lock(lock.readLock())) { @@ -228,9 +239,94 @@ public abstract class AbstractDBTraceSpaceBasedManager getActiveSpaces() { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceSpaceBased.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceSpaceBased.java index ca1056416e..3e9562ded1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceSpaceBased.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/DBTraceSpaceBased.java @@ -15,14 +15,29 @@ */ package ghidra.trace.database.space; -import com.google.common.collect.Range; -import com.google.common.primitives.UnsignedLong; - import ghidra.program.model.address.*; public interface DBTraceSpaceBased extends DBTraceSpaceKey { + + default boolean isMySpace(AddressSpace space) { + if (space == getAddressSpace()) { + return true; + } + /** + * This turned out to be a bad idea! Every place a client gives an address would have to be + * translated into the overlay space first. Every manager, every method. It's a lot. For + * now, I'll leave that burden on the client. Especially, since the register overlay spaces + * will all be delegated from the manger directly, it doesn't make sense to permit sloppy + * access. + */ + /*if (space.isRegisterSpace() && space == getAddressSpace().getPhysicalSpace()) { + return true; + }*/ + return false; + } + default long assertInSpace(Address addr) { - if (addr.getAddressSpace() != getAddressSpace()) { + if (!isMySpace(addr.getAddressSpace())) { throw new IllegalArgumentException( "Address '" + addr + "' is not in this space: '" + getAddressSpace() + "'"); } @@ -30,32 +45,19 @@ public interface DBTraceSpaceBased extends DBTraceSpaceKey { } default void assertInSpace(AddressRange range) { - if (range.getAddressSpace() != getAddressSpace()) { + if (!isMySpace(range.getAddressSpace())) { throw new IllegalArgumentException( "Address Range '" + range + "' is not in this space: '" + getAddressSpace() + "'"); } } - default UnsignedLong toOffset(Address address) { - return UnsignedLong.fromLongBits(address.getOffset()); - } - - default Range toOffsetRange(AddressRange range) { - return Range.closed(toOffset(range.getMinAddress()), toOffset(range.getMaxAddress())); - } - - default Address toAddress(UnsignedLong offset) { - return getAddressSpace().getAddress(offset.longValue()); + default Address toOverlay(Address physical) { + return getAddressSpace().getOverlayAddress(physical); } default Address toAddress(long offset) { return getAddressSpace().getAddress(offset); } - default AddressRange toAddressRange(Range range) { - return new AddressRangeImpl(toAddress(range.lowerEndpoint()), - toAddress(range.upperEndpoint())); - } - void invalidateCache(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java index 5c2b534fcf..8dbad557bf 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java @@ -100,12 +100,11 @@ public class DBTraceStackManager implements TraceStackManager, DBTraceManager { public static PathPredicates single(TraceObject seed, Class targetIf) { PathMatcher stackMatcher = seed.getTargetSchema().searchFor(targetIf, false); - PathPattern singleton = stackMatcher.getSingletonPattern(); - if (singleton.getSingletonPath() == null) { + if (stackMatcher.getSingletonPath() == null) { throw new IllegalStateException("Schema doesn't provide a unique " + targetIf.getSimpleName() + " for " + seed.getCanonicalPath()); } - return singleton; + return stackMatcher.getSingletonPattern(); } protected TraceObjectStack doGetOrAddObjectStack(TraceThread thread, long snap, diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbolSingleTypeView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbolSingleTypeView.java index bfe77e2ebe..af12dd7ee6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbolSingleTypeView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbolSingleTypeView.java @@ -15,8 +15,7 @@ */ package ghidra.trace.database.symbol; -import java.util.Collection; -import java.util.Collections; +import java.util.*; import java.util.function.Predicate; import com.google.common.collect.Collections2; @@ -65,7 +64,8 @@ public abstract class AbstractDBTraceSymbolSingleTypeView getChildrenNamed(String name, TraceNamespaceSymbol parent) { try (LockHold hold = LockHold.lock(manager.lock.readLock())) { - DBTraceNamespaceSymbol dbnsParent = manager.assertIsMine((Namespace) parent); + DBTraceNamespaceSymbol dbnsParent = + manager.assertIsMine((Namespace) Objects.requireNonNull(parent)); return Collections.unmodifiableCollection(Collections2.filter( symbolsByParentID.get(dbnsParent.getID()), s -> name.equals(s.name))); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceNamespaceSymbolView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceNamespaceSymbolView.java index 71126f262c..80c1684d51 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceNamespaceSymbolView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceNamespaceSymbolView.java @@ -37,7 +37,7 @@ public class DBTraceNamespaceSymbolView @Override public DBTraceNamespaceSymbol add(String name, TraceNamespaceSymbol parent, SourceType source) - throws DuplicateNameException, InvalidInputException, IllegalArgumentException { + throws DuplicateNameException, InvalidInputException { if (source == SourceType.DEFAULT) { throw new IllegalArgumentException(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolManager.java index 1f585efed0..2e14673ef4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolManager.java @@ -666,10 +666,6 @@ public class DBTraceSymbolManager implements TraceSymbolManager, DBTraceManager throw new IllegalArgumentException( "Memory addresses cannot be associated with a thread"); } - if (thread == null && address.getAddressSpace().isRegisterSpace()) { - throw new IllegalArgumentException( - "Register addresses must be associated with a thread"); - } } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java index 5c6918d36b..362cb68a8f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java @@ -44,7 +44,8 @@ import ghidra.trace.database.stack.DBTraceObjectStack; import ghidra.trace.database.stack.DBTraceObjectStackFrame; import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple; import ghidra.trace.database.target.InternalTraceObjectValue.ValueLifespanSetter; -import ghidra.trace.database.target.InternalTreeTraversal.Visitor; +import ghidra.trace.database.target.visitors.*; +import ghidra.trace.database.target.visitors.TreeTraversal.Visitor; import ghidra.trace.database.thread.DBTraceObjectThread; import ghidra.trace.model.Trace.TraceObjectChangeType; import ghidra.trace.model.breakpoint.TraceObjectBreakpointLocation; @@ -307,12 +308,12 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } @Override - public Stream getAllPaths(Range span) { + public Stream getAllPaths(Range span) { try (LockHold hold = manager.trace.lockRead()) { if (isRoot()) { return Stream.of(DBTraceObjectValPath.of()); } - return doStreamVisitor(span, InternalAllPathsVisitor.INSTANCE); + return doStreamVisitor(span, AllPathsVisitor.INSTANCE); } } @@ -516,43 +517,48 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { protected Collection doGetValues(long lower, long upper, String key) { - try (LockHold hold = manager.trace.lockRead()) { - // Collect triplet-indexed values - Set result = new LinkedHashSet<>(); - PrimaryTriple min = new PrimaryTriple(this, key, lower); - PrimaryTriple max = new PrimaryTriple(this, key, upper); - DBTraceObjectValue floor = manager.valuesByTriple.floorValue(min); - if (floor != null && floor.getParent() == this && key.equals(floor.getEntryKey()) && - floor.getLifespan().contains(lower)) { - result.add(floor); - } - for (DBTraceObjectValue val : manager.valuesByTriple.sub(min, true, max, true) + // Collect triplet-indexed values + Set result = new LinkedHashSet<>(); + PrimaryTriple min = new PrimaryTriple(this, key, lower); + PrimaryTriple max = new PrimaryTriple(this, key, upper); + DBTraceObjectValue floor = manager.valuesByTriple.floorValue(min); + if (floor != null && floor.getParent() == this && key.equals(floor.getEntryKey()) && + floor.getLifespan().contains(lower)) { + result.add(floor); + } + for (DBTraceObjectValue val : manager.valuesByTriple.sub(min, true, max, true) + .values()) { + result.add(val); + } + + // Collect R*-Tree-indexed values + for (DBTraceAddressSnapRangePropertyMapSpace space : manager.rangeValueMap + .getActiveMemorySpaces()) { + AddressSpace as = space.getAddressSpace(); + + for (DBTraceObjectAddressRangeValue val : manager.rangeValueMap + .reduce(TraceAddressSnapRangeQuery + .intersecting(as.getMinAddress(), as.getMaxAddress(), lower, upper)) .values()) { + if (val.getParent() != this) { + continue; + } + if (!key.equals(val.getEntryKey())) { + continue; + } result.add(val); } + } - // Collect R*-Tree-indexed values - for (DBTraceAddressSnapRangePropertyMapSpace space : manager.rangeValueMap - .getActiveMemorySpaces()) { - AddressSpace as = space.getAddressSpace(); + return result.stream() + .sorted(Comparator.comparing(v -> v.getMinSnap())) + .collect(Collectors.toList()); + } - for (DBTraceObjectAddressRangeValue val : manager.rangeValueMap - .reduce(TraceAddressSnapRangeQuery - .intersecting(as.getMinAddress(), as.getMaxAddress(), lower, upper)) - .values()) { - if (val.getParent() != this) { - continue; - } - if (!key.equals(val.getEntryKey())) { - continue; - } - result.add(val); - } - } - - return result.stream() - .sorted(Comparator.comparing(v -> v.getMinSnap())) - .collect(Collectors.toList()); + @Override + public Collection getValues(Range span, String key) { + try (LockHold hold = manager.trace.lockRead()) { + return doGetValues(span, key); } } @@ -606,7 +612,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { .starting(dir)) .orderedValues() .iterator(), - v -> key.equals(v.getEntryKey()))) + v -> key.equals(v.getEntryKey()) && this == v.getParent())) .collect(Collectors.toList()); Comparator order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder(); Comparator comparator = @@ -614,8 +620,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { Iterator merged = Iterators.mergeSorted(iterators, comparator); return StreamSupport - .stream(Spliterators.spliteratorUnknownSize(merged, Spliterator.ORDERED), false) - .filter(v -> key.equals(v.getEntryKey())); + .stream(Spliterators.spliteratorUnknownSize(merged, Spliterator.ORDERED), false); } @Override @@ -668,9 +673,9 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { return getValue(snap, name); } - protected Stream doStreamVisitor(Range span, + protected Stream doStreamVisitor(Range span, Visitor visitor) { - return InternalTreeTraversal.INSTANCE.walkObject(visitor, this, span, + return TreeTraversal.INSTANCE.walkObject(visitor, this, span, DBTraceObjectValPath.of()); } @@ -678,8 +683,8 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { public Stream getAncestors(Range span, PathPredicates relativePredicates) { try (LockHold hold = manager.trace.lockRead()) { - Stream ancestors = - doStreamVisitor(span, new InternalAncestorsRelativeVisitor(relativePredicates)); + Stream ancestors = + doStreamVisitor(span, new AncestorsRelativeVisitor(relativePredicates)); if (relativePredicates.matches(List.of())) { return Stream.concat(Stream.of(DBTraceObjectValPath.of()), ancestors); } @@ -688,19 +693,19 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } @Override - public Stream getAncestorsRoot( + public Stream getAncestorsRoot( Range span, PathPredicates rootPredicates) { try (LockHold hold = manager.trace.lockRead()) { - return doStreamVisitor(span, new InternalAncestorsRootVisitor(rootPredicates)); + return doStreamVisitor(span, new AncestorsRootVisitor(rootPredicates)); } } @Override - public Stream getSuccessors( + public Stream getSuccessors( Range span, PathPredicates relativePredicates) { try (LockHold hold = manager.trace.lockRead()) { - Stream succcessors = - doStreamVisitor(span, new InternalSuccessorsRelativeVisitor(relativePredicates)); + Stream succcessors = + doStreamVisitor(span, new SuccessorsRelativeVisitor(relativePredicates)); if (relativePredicates.matches(List.of())) { // Pre-cat the empty path (not the empty stream) return Stream.concat(Stream.of(DBTraceObjectValPath.of()), succcessors); @@ -710,7 +715,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } @Override - public Stream getOrderedSuccessors(Range span, + public Stream getOrderedSuccessors(Range span, TraceObjectKeyPath relativePath, boolean forward) { DBTraceObjectValPath empty = DBTraceObjectValPath.of(); try (LockHold hold = manager.trace.lockRead()) { @@ -718,7 +723,21 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { return Stream.of(empty); // Not the empty stream } return doStreamVisitor(span, - new InternalOrderedSuccessorsVisitor(relativePath, forward)); + new OrderedSuccessorsVisitor(relativePath, forward)); + } + } + + @Override + public Stream getCanonicalSuccessors( + PathPredicates relativePredicates) { + try (LockHold hold = manager.trace.lockRead()) { + Stream successors = doStreamVisitor(Range.all(), + new CanonicalSuccessorsRelativeVisitor(relativePredicates)); + if (relativePredicates.matches(List.of())) { + // Pre-cat the empty path (not the empty stream) + return Stream.concat(Stream.of(DBTraceObjectValPath.of()), successors); + } + return successors; } } @@ -808,7 +827,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } @Override - public Stream queryAncestorsTargetInterface(Range span, + public Stream queryAncestorsTargetInterface(Range span, Class targetIf) { // This is a sort of meet-in-the-middle. The type search must originate from the root PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false); @@ -841,7 +860,7 @@ public class DBTraceObject extends DBAnnotatedObject implements TraceObject { } @Override - public Stream querySuccessorsTargetInterface(Range span, + public Stream querySuccessorsTargetInterface(Range span, Class targetIf) { PathMatcher matcher = getTargetSchema().searchFor(targetIf, true); // TODO: Post filter until GP-1301 diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java index f19271e259..2a1e1d00ce 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java @@ -42,6 +42,7 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.module.TraceObjectSection; import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple; +import ghidra.trace.database.target.visitors.SuccessorsRelativeVisitor; import ghidra.trace.database.thread.DBTraceObjectThread; import ghidra.trace.model.ImmutableTraceAddressSnapRange; import ghidra.trace.model.Trace; @@ -361,14 +362,14 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager } @Override - public Stream getValuePaths(Range span, + public Stream getValuePaths(Range span, PathPredicates predicates) { try (LockHold hold = trace.lockRead()) { DBTraceObjectValue rootVal = valueStore.getObjectAt(0); if (rootVal == null) { return Stream.of(); } - return rootVal.doStreamVisitor(span, new InternalSuccessorsRelativeVisitor(predicates)); + return rootVal.doStreamVisitor(span, new SuccessorsRelativeVisitor(predicates)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java index 73fe2ff742..71b86adb8b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java @@ -22,11 +22,17 @@ import ghidra.dbg.util.PathUtils.PathComparator; import ghidra.trace.model.target.*; public class DBTraceObjectValPath implements TraceObjectValPath { - static DBTraceObjectValPath of(Collection entryList) { + public static final DBTraceObjectValPath EMPTY = new DBTraceObjectValPath(List.of()); + + public static DBTraceObjectValPath of() { + return EMPTY; + } + + public static DBTraceObjectValPath of(Collection entryList) { return new DBTraceObjectValPath(List.copyOf(entryList)); } - static DBTraceObjectValPath of(InternalTraceObjectValue... entries) { + public static DBTraceObjectValPath of(InternalTraceObjectValue... entries) { return DBTraceObjectValPath.of(Arrays.asList(entries)); } @@ -66,6 +72,7 @@ public class DBTraceObjectValPath implements TraceObjectValPath { return entryList.contains(entry); } + @Override public DBTraceObjectValPath prepend(TraceObjectValue entry) { InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()]; arr[0] = (DBTraceObjectValue) entry; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java index 517a882aa5..74efe93e9e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java @@ -28,10 +28,10 @@ import com.google.common.collect.Range; import db.*; import ghidra.trace.database.DBTraceUtils; -import ghidra.trace.database.target.InternalTreeTraversal.Visitor; +import ghidra.trace.database.target.visitors.TreeTraversal; +import ghidra.trace.database.target.visitors.TreeTraversal.Visitor; import ghidra.trace.model.Trace; -import ghidra.trace.model.target.TraceObject; -import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.trace.model.target.*; import ghidra.util.LockHold; import ghidra.util.database.*; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; @@ -321,9 +321,9 @@ public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTra } } - protected Stream doStreamVisitor(Range span, + protected Stream doStreamVisitor(Range span, Visitor visitor) { - return InternalTreeTraversal.INSTANCE.walkValue(visitor, this, span, null); + return TreeTraversal.INSTANCE.walkValue(visitor, this, span, null); } protected TraceObjectKeyPath doGetCanonicalPath() { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTreeTraversal.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTreeTraversal.java deleted file mode 100644 index 2e5b4eb8b0..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTreeTraversal.java +++ /dev/null @@ -1,114 +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.trace.database.target; - -import java.util.stream.Stream; - -import com.google.common.collect.Range; - -public enum InternalTreeTraversal { - INSTANCE; - - public enum VisitResult { - INCLUDE_CONTINUE, - INCLUDE_FINISH, - EXCLUDE_CONTINUE, - EXCLUDE_FINISH, - ; - - public static VisitResult result(boolean include, boolean cont) { - if (include) { - if (cont) { - return INCLUDE_CONTINUE; - } - else { - return INCLUDE_FINISH; - } - } - else { - if (cont) { - return EXCLUDE_CONTINUE; - } - else { - return EXCLUDE_FINISH; - } - } - } - } - - public interface Visitor { - Range composeSpan(Range pre, InternalTraceObjectValue value); - - DBTraceObjectValPath composePath(DBTraceObjectValPath pre, InternalTraceObjectValue value); - - VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path); - - DBTraceObject continueObject(InternalTraceObjectValue value); - - Stream continueValues(DBTraceObject object, - Range span, DBTraceObjectValPath path); - } - - public interface SpanIntersectingVisitor extends Visitor { - @Override - default Range composeSpan(Range pre, InternalTraceObjectValue value) { - Range valSpan = value.getLifespan(); - if (!pre.isConnected(valSpan)) { - return null; - } - Range span = pre.intersection(valSpan); - if (span.isEmpty()) { - return null; - } - return span; - } - } - - public Stream walkValue(Visitor visitor, - InternalTraceObjectValue value, Range span, DBTraceObjectValPath path) { - Range compSpan = visitor.composeSpan(span, value); - if (compSpan == null) { - return Stream.empty(); - } - DBTraceObjectValPath compPath = visitor.composePath(path, value); - if (compPath == null) { - return Stream.empty(); - } - - switch (visitor.visitValue(value, compPath)) { - case INCLUDE_FINISH: - return Stream.of(compPath); - case EXCLUDE_FINISH: - return Stream.empty(); - case INCLUDE_CONTINUE: { - DBTraceObject object = visitor.continueObject(value); - return Stream.concat(Stream.of(compPath), - walkObject(visitor, object, compSpan, compPath)); - } - case EXCLUDE_CONTINUE: { - DBTraceObject object = visitor.continueObject(value); - return walkObject(visitor, object, compSpan, compPath); - } - } - throw new AssertionError(); - } - - public Stream walkObject(Visitor visitor, DBTraceObject object, - Range span, DBTraceObjectValPath path) { - return visitor.continueValues(object, span, path) - .flatMap(v -> walkValue(visitor, v, span, path)); - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAllPathsVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AllPathsVisitor.java similarity index 55% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAllPathsVisitor.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AllPathsVisitor.java index cdb13f3251..07214d9ccd 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAllPathsVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AllPathsVisitor.java @@ -13,44 +13,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.trace.database.target; +package ghidra.trace.database.target.visitors; import java.util.stream.Stream; import com.google.common.collect.Range; -import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor; -import ghidra.trace.database.target.InternalTreeTraversal.VisitResult; +import ghidra.trace.database.target.visitors.TreeTraversal.SpanIntersectingVisitor; +import ghidra.trace.database.target.visitors.TreeTraversal.VisitResult; +import ghidra.trace.model.target.*; -public enum InternalAllPathsVisitor implements SpanIntersectingVisitor { +public enum AllPathsVisitor implements SpanIntersectingVisitor { INSTANCE; @Override - public DBTraceObjectValPath composePath(DBTraceObjectValPath pre, - InternalTraceObjectValue value) { + public TraceObjectValPath composePath(TraceObjectValPath pre, TraceObjectValue value) { return pre.prepend(value); } @Override - public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) { + public VisitResult visitValue(TraceObjectValue value, TraceObjectValPath path) { if (value.getParent() == null) { - return VisitResult.EXCLUDE_FINISH; + return VisitResult.EXCLUDE_PRUNE; } if (value.getParent().isRoot()) { // It may have other parents - return VisitResult.INCLUDE_CONTINUE; + return VisitResult.INCLUDE_DESCEND; } - return VisitResult.EXCLUDE_CONTINUE; + return VisitResult.EXCLUDE_DESCEND; } @Override - public DBTraceObject continueObject(InternalTraceObjectValue value) { + public TraceObject continueObject(TraceObjectValue value) { return value.getParent(); } @Override - public Stream continueValues(DBTraceObject object, - Range span, DBTraceObjectValPath path) { + public Stream continueValues(TraceObject object, + Range span, TraceObjectValPath path) { return object.getParents().stream().filter(v -> !path.contains(v)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsRelativeVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRelativeVisitor.java similarity index 56% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsRelativeVisitor.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRelativeVisitor.java index e1d0322537..1eb4190544 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsRelativeVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRelativeVisitor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.trace.database.target; +package ghidra.trace.database.target.visitors; import java.util.List; import java.util.Set; @@ -22,44 +22,45 @@ import java.util.stream.Stream; import com.google.common.collect.Range; import ghidra.dbg.util.PathPredicates; -import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor; -import ghidra.trace.database.target.InternalTreeTraversal.VisitResult; +import ghidra.trace.database.target.visitors.TreeTraversal.SpanIntersectingVisitor; +import ghidra.trace.database.target.visitors.TreeTraversal.VisitResult; +import ghidra.trace.model.target.*; -public class InternalAncestorsRelativeVisitor implements SpanIntersectingVisitor { +public class AncestorsRelativeVisitor implements SpanIntersectingVisitor { protected final PathPredicates predicates; - public InternalAncestorsRelativeVisitor(PathPredicates predicates) { + public AncestorsRelativeVisitor(PathPredicates predicates) { this.predicates = predicates; } @Override - public DBTraceObjectValPath composePath(DBTraceObjectValPath pre, - InternalTraceObjectValue value) { - return pre == null ? DBTraceObjectValPath.of() : pre.prepend(value); + public TraceObjectValPath composePath(TraceObjectValPath pre, + TraceObjectValue value) { + return pre == null ? TraceObjectValPath.of() : pre.prepend(value); } @Override - public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) { + public VisitResult visitValue(TraceObjectValue value, TraceObjectValPath path) { List keyList = path.getKeyList(); return VisitResult.result(predicates.matches(keyList), - predicates.ancestorCouldMatchRight(keyList, true) && value.getChildOrNull() != null); + predicates.ancestorCouldMatchRight(keyList, true) && value.isObject()); } @Override - public DBTraceObject continueObject(InternalTraceObjectValue value) { + public TraceObject continueObject(TraceObjectValue value) { return value.getParent(); } @Override - public Stream continueValues(DBTraceObject object, - Range span, DBTraceObjectValPath pre) { + public Stream continueValues(TraceObject object, + Range span, TraceObjectValPath pre) { Set prevKeys = predicates.getPrevKeys(pre.getKeyList()); if (prevKeys.isEmpty()) { return Stream.empty(); } - return object.doGetParents() + return object.getParents() .stream() .filter(v -> PathPredicates.anyMatches(prevKeys, v.getEntryKey())); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsRootVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRootVisitor.java similarity index 60% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsRootVisitor.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRootVisitor.java index 92ecbaaeb2..be4763c1ad 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalAncestorsRootVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/AncestorsRootVisitor.java @@ -13,44 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.trace.database.target; +package ghidra.trace.database.target.visitors; import java.util.stream.Stream; import com.google.common.collect.Range; import ghidra.dbg.util.PathPredicates; -import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor; -import ghidra.trace.database.target.InternalTreeTraversal.VisitResult; +import ghidra.trace.database.target.visitors.TreeTraversal.SpanIntersectingVisitor; +import ghidra.trace.database.target.visitors.TreeTraversal.VisitResult; +import ghidra.trace.model.target.*; -public class InternalAncestorsRootVisitor implements SpanIntersectingVisitor { +public class AncestorsRootVisitor implements SpanIntersectingVisitor { protected final PathPredicates predicates; - public InternalAncestorsRootVisitor(PathPredicates predicates) { + public AncestorsRootVisitor(PathPredicates predicates) { this.predicates = predicates; } @Override - public DBTraceObjectValPath composePath(DBTraceObjectValPath pre, - InternalTraceObjectValue value) { - return pre == null ? DBTraceObjectValPath.of() : pre.prepend(value); + public TraceObjectValPath composePath(TraceObjectValPath pre, + TraceObjectValue value) { + return pre == null ? TraceObjectValPath.of() : pre.prepend(value); } @Override - public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) { + public VisitResult visitValue(TraceObjectValue value, TraceObjectValPath path) { return VisitResult.result( predicates.matches(value.getParent().getCanonicalPath().getKeyList()), true); } @Override - public DBTraceObject continueObject(InternalTraceObjectValue value) { + public TraceObject continueObject(TraceObjectValue value) { return value.getParent(); } @Override - public Stream continueValues(DBTraceObject object, - Range span, DBTraceObjectValPath path) { + public Stream continueValues(TraceObject object, + Range span, TraceObjectValPath path) { if (object.isRoot()) { return Stream.empty(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/CanonicalSuccessorsRelativeVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/CanonicalSuccessorsRelativeVisitor.java new file mode 100644 index 0000000000..60d9bc0dd8 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/CanonicalSuccessorsRelativeVisitor.java @@ -0,0 +1,97 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target.visitors; + +import java.util.*; +import java.util.stream.Stream; + +import com.google.common.collect.Range; + +import ghidra.dbg.util.PathPredicates; +import ghidra.trace.database.target.visitors.TreeTraversal.VisitResult; +import ghidra.trace.database.target.visitors.TreeTraversal.Visitor; +import ghidra.trace.model.target.*; + +public class CanonicalSuccessorsRelativeVisitor implements Visitor { + + protected final PathPredicates predicates; + protected final Set seen = new HashSet<>(); + + public CanonicalSuccessorsRelativeVisitor(PathPredicates predicates) { + this.predicates = predicates; + } + + @Override + public Range composeSpan(Range pre, TraceObjectValue value) { + return Range.all(); + } + + @Override + public TraceObjectValPath composePath(TraceObjectValPath pre, + TraceObjectValue value) { + return pre == null ? TraceObjectValPath.of() : pre.append(value); + } + + @Override + public VisitResult visitValue(TraceObjectValue value, TraceObjectValPath path) { + List keyList = path.getKeyList(); + return VisitResult.result(predicates.matches(keyList), + predicates.successorCouldMatch(keyList, true) && value.isObject()); + } + + @Override + public TraceObject continueObject(TraceObjectValue value) { + return value.isObject() ? value.getChild() : null; + } + + protected TraceObjectValue getCanonicalValue(TraceObject parent, String key) { + return parent.getOrderedValues(Range.all(), key, true) + .filter(TraceObjectValue::isCanonical) + .findFirst() + .orElse(null); + } + + @Override + public Stream continueValues(TraceObject object, + Range span, TraceObjectValPath pre) { + Set nextKeys = predicates.getNextKeys(pre.getKeyList()); + if (nextKeys.isEmpty()) { + return Stream.empty(); + } + + Stream attrStream; + if (nextKeys.contains("")) { + attrStream = object.getAttributes().stream().filter(TraceObjectValue::isCanonical); + } + else { + attrStream = Stream.empty(); + } + + Stream elemStream; + if (nextKeys.contains("[]")) { + elemStream = object.getElements().stream().filter(TraceObjectValue::isCanonical); + } + else { + elemStream = Stream.empty(); + } + + Stream restStream = nextKeys.stream() + .filter(k -> !"".equals(k) && !"[]".equals(k)) + .map(k -> getCanonicalValue(object, k)); + + return Stream.concat(Stream.concat(attrStream, elemStream), restStream); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalOrderedSuccessorsVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/OrderedSuccessorsVisitor.java similarity index 56% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalOrderedSuccessorsVisitor.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/OrderedSuccessorsVisitor.java index 9deced413c..ec8cca54e5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalOrderedSuccessorsVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/OrderedSuccessorsVisitor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.trace.database.target; +package ghidra.trace.database.target.visitors; import java.util.List; import java.util.Set; @@ -23,47 +23,47 @@ import com.google.common.collect.Range; import ghidra.dbg.util.PathPattern; import ghidra.dbg.util.PathPredicates; -import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor; -import ghidra.trace.database.target.InternalTreeTraversal.VisitResult; -import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.trace.database.target.visitors.TreeTraversal.SpanIntersectingVisitor; +import ghidra.trace.database.target.visitors.TreeTraversal.VisitResult; +import ghidra.trace.model.target.*; -public class InternalOrderedSuccessorsVisitor implements SpanIntersectingVisitor { +public class OrderedSuccessorsVisitor implements SpanIntersectingVisitor { protected final PathPredicates predicates; protected final boolean forward; - public InternalOrderedSuccessorsVisitor(TraceObjectKeyPath path, boolean forward) { + public OrderedSuccessorsVisitor(TraceObjectKeyPath path, boolean forward) { this.predicates = new PathPattern(path.getKeyList()); this.forward = forward; } @Override - public DBTraceObjectValPath composePath(DBTraceObjectValPath pre, - InternalTraceObjectValue value) { - return pre == null ? DBTraceObjectValPath.of() : pre.append(value); + public TraceObjectValPath composePath(TraceObjectValPath pre, + TraceObjectValue value) { + return pre == null ? TraceObjectValPath.of() : pre.append(value); } @Override - public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) { + public VisitResult visitValue(TraceObjectValue value, TraceObjectValPath path) { List keyList = path.getKeyList(); if (predicates.matches(keyList)) { // Singleton path, so if I match, no successor can - return VisitResult.INCLUDE_FINISH; + return VisitResult.INCLUDE_PRUNE; } - if (value.getChildOrNull() == null || !predicates.successorCouldMatch(keyList, true)) { - return VisitResult.EXCLUDE_FINISH; + if (!value.isObject() || !predicates.successorCouldMatch(keyList, true)) { + return VisitResult.EXCLUDE_PRUNE; } - return VisitResult.EXCLUDE_CONTINUE; + return VisitResult.EXCLUDE_DESCEND; } @Override - public DBTraceObject continueObject(InternalTraceObjectValue value) { - return value.getChildOrNull(); + public TraceObject continueObject(TraceObjectValue value) { + return value.isObject() ? value.getChild() : null; } @Override - public Stream continueValues(DBTraceObject object, - Range span, DBTraceObjectValPath path) { + public Stream continueValues(TraceObject object, + Range span, TraceObjectValPath path) { Set nextKeys = predicates.getNextKeys(path.getKeyList()); if (nextKeys.isEmpty()) { return Stream.empty(); @@ -75,6 +75,6 @@ public class InternalOrderedSuccessorsVisitor implements SpanIntersectingVisitor if (PathPattern.isWildcard(next)) { throw new IllegalArgumentException("predicates must be a singleton"); } - return object.doGetOrderedValues(span, next, forward); + return object.getOrderedValues(span, next, forward); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalSuccessorsRelativeVisitor.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/SuccessorsRelativeVisitor.java similarity index 56% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalSuccessorsRelativeVisitor.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/SuccessorsRelativeVisitor.java index 0f40c644d3..bfed1fd4cf 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalSuccessorsRelativeVisitor.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/SuccessorsRelativeVisitor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.trace.database.target; +package ghidra.trace.database.target.visitors; import java.util.List; import java.util.Set; @@ -23,46 +23,47 @@ import com.google.common.collect.Range; import ghidra.dbg.util.PathPredicates; import ghidra.trace.database.DBTraceUtils; -import ghidra.trace.database.target.InternalTreeTraversal.SpanIntersectingVisitor; -import ghidra.trace.database.target.InternalTreeTraversal.VisitResult; +import ghidra.trace.database.target.visitors.TreeTraversal.SpanIntersectingVisitor; +import ghidra.trace.database.target.visitors.TreeTraversal.VisitResult; +import ghidra.trace.model.target.*; -public class InternalSuccessorsRelativeVisitor implements SpanIntersectingVisitor { +public class SuccessorsRelativeVisitor implements SpanIntersectingVisitor { protected final PathPredicates predicates; - public InternalSuccessorsRelativeVisitor(PathPredicates predicates) { + public SuccessorsRelativeVisitor(PathPredicates predicates) { this.predicates = predicates; } @Override - public DBTraceObjectValPath composePath(DBTraceObjectValPath pre, - InternalTraceObjectValue value) { - return pre == null ? DBTraceObjectValPath.of() : pre.append(value); + public TraceObjectValPath composePath(TraceObjectValPath pre, + TraceObjectValue value) { + return pre == null ? TraceObjectValPath.of() : pre.append(value); } @Override - public VisitResult visitValue(InternalTraceObjectValue value, DBTraceObjectValPath path) { + public VisitResult visitValue(TraceObjectValue value, TraceObjectValPath path) { List keyList = path.getKeyList(); return VisitResult.result(predicates.matches(keyList), - predicates.successorCouldMatch(keyList, true) && value.getChildOrNull() != null); + predicates.successorCouldMatch(keyList, true) && value.isObject()); } @Override - public DBTraceObject continueObject(InternalTraceObjectValue value) { - return value.getChildOrNull(); + public TraceObject continueObject(TraceObjectValue value) { + return value.isObject() ? value.getChild() : null; } @Override - public Stream continueValues(DBTraceObject object, - Range span, DBTraceObjectValPath pre) { + public Stream continueValues(TraceObject object, + Range span, TraceObjectValPath pre) { Set nextKeys = predicates.getNextKeys(pre.getKeyList()); if (nextKeys.isEmpty()) { return Stream.empty(); } - Stream attrStream; + Stream attrStream; if (nextKeys.contains("")) { - attrStream = object.doGetAttributes() + attrStream = object.getAttributes() .stream() .filter(v -> DBTraceUtils.intersect(span, v.getLifespan())); } @@ -70,9 +71,9 @@ public class InternalSuccessorsRelativeVisitor implements SpanIntersectingVisito attrStream = Stream.empty(); } - Stream elemStream; + Stream elemStream; if (nextKeys.contains("[]")) { - elemStream = object.doGetElements() + elemStream = object.getElements() .stream() .filter(v -> DBTraceUtils.intersect(span, v.getLifespan())); } @@ -80,9 +81,9 @@ public class InternalSuccessorsRelativeVisitor implements SpanIntersectingVisito elemStream = Stream.empty(); } - Stream restStream = nextKeys.stream() + Stream restStream = nextKeys.stream() .filter(k -> !"".equals(k) && !"[]".equals(k)) - .flatMap(k -> object.doGetValues(span, k).stream()); + .flatMap(k -> object.getValues(span, k).stream()); return Stream.concat(Stream.concat(attrStream, elemStream), restStream); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/TreeTraversal.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/TreeTraversal.java new file mode 100644 index 0000000000..5ed64f4fa4 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/visitors/TreeTraversal.java @@ -0,0 +1,232 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target.visitors; + +import java.util.stream.Stream; + +import com.google.common.collect.Range; + +import ghidra.trace.model.target.*; + +/** + * Support for traversing a trace's object tree + * + *

+ * Many of these are already built into the object and value interfaces. Direct use of this + * traversal support is only needed when performing customized traversals. In most cases, it's + * sufficient to use a built-in traversal and filter the resulting stream. Customized traversal is + * only needed when it's beneficial to prune subtrees in a way that no built-in traversal provides. + */ +public enum TreeTraversal { + /** The singleton instance */ + INSTANCE; + + /** + * A result directing the traversal how to proceed + */ + public enum VisitResult { + /** + * Include the value that was just traversed, and descend + */ + INCLUDE_DESCEND, + /** + * Include the value that was just traversed, but prune its subtree + */ + INCLUDE_PRUNE, + /** + * Exclude the value that was just traversed, but descend + */ + EXCLUDE_DESCEND, + /** + * Exclude the value that was just traversed, and prune its subtree + */ + EXCLUDE_PRUNE, + ; + + /** + * Get the result that indicates the given inclusion and continuation + * + * @param include true to include the value just traversed, false to exclude + * @param cont true to continue traversal, false to terminate + * @return the result + */ + public static VisitResult result(boolean include, boolean cont) { + if (include) { + if (cont) { + return INCLUDE_DESCEND; + } + else { + return INCLUDE_PRUNE; + } + } + else { + if (cont) { + return EXCLUDE_DESCEND; + } + else { + return EXCLUDE_PRUNE; + } + } + } + } + + /** + * An object-tree visitor + * + *

+ * Traversal starts at a seed object or value (node or edge, respectively) and proceeds in + * alternating fashion from object to value to object and so on via + * {@link #continueObject(TraceObjectValue)} and + * {@link #continueValues(TraceObject, Range, TraceObjectValPath)}. Filtering is performed on + * values via {@link #visitValue(TraceObjectValue, TraceObjectValPath)}. As traversal descends, + * paths and spans are composed to inform filtering and construct the final result stream. Note + * that some traversals start at a seed and "descend" along the ancestry. + */ + public interface Visitor { + /** + * When descending in a value, what span to consider in the subtree + * + *

+ * Usually this is intersection. See {@link SpanIntersectingVisitor} + * + * @param pre the span composed from values from seed to but excluding the current value + * @param value the current value + * @return the span composed from values from seed to and including the current value + */ + Range composeSpan(Range pre, TraceObjectValue value); + + /** + * When descending in a value, what path leads to the value + * + *

+ * This is usually {@link TraceObjectValPath#append(TraceObjectValue)} or + * {@link TraceObjectValPath#prepend(TraceObjectValue)}. + * + * @param pre the path from seed to the but excluding the current value + * @param value the path from seed to the and including the current value + * @return the path from seed to and including the current value + */ + TraceObjectValPath composePath(TraceObjectValPath pre, TraceObjectValue value); + + /** + * Visit a value + * + *

+ * Note that the path is the composed path, so it will likely have the current value at its + * beginning or end. + * + * @param value the current value + * @param path the path from seed to value + * @return directions for how traversal should proceed + */ + VisitResult visitValue(TraceObjectValue value, TraceObjectValPath path); + + /** + * When descending in a value, the object to consider next + * + *

+ * This is usually {@link TraceObjectValue#getChild()} or + * {@link TraceObjectValue#getParent()}. + * + * @param value the current value + * @return the next object + */ + TraceObject continueObject(TraceObjectValue value); + + /** + * When descending in an object, the values to consider next + * + * @param object the current object + * @param span the composed span of values from seed to the current object + * @param path the path from seed to the current object + * @return the next values + */ + Stream continueValues(TraceObject object, + Range span, TraceObjectValPath path); + } + + /** + * A visitor providing default {@link #composeSpan(Range, TraceObjectValue)} that intersects the + * spans + */ + public interface SpanIntersectingVisitor extends Visitor { + @Override + default Range composeSpan(Range pre, TraceObjectValue value) { + Range valSpan = value.getLifespan(); + if (!pre.isConnected(valSpan)) { + return null; + } + Range span = pre.intersection(valSpan); + if (span.isEmpty()) { + return null; + } + return span; + } + } + + /** + * Walk a value and possibly its subtree + * + * @param visitor the visitor + * @param value the current value + * @param span the composed span from seed to but excluding the current value + * @param path the path from seed to but excluding the current value + * @return the result stream of the value and subtree walked + */ + public Stream walkValue(Visitor visitor, + TraceObjectValue value, Range span, TraceObjectValPath path) { + Range compSpan = visitor.composeSpan(span, value); + if (compSpan == null) { + return Stream.empty(); + } + TraceObjectValPath compPath = visitor.composePath(path, value); + if (compPath == null) { + return Stream.empty(); + } + + switch (visitor.visitValue(value, compPath)) { + case INCLUDE_PRUNE: + return Stream.of(compPath); + case EXCLUDE_PRUNE: + return Stream.empty(); + case INCLUDE_DESCEND: { + TraceObject object = visitor.continueObject(value); + return Stream.concat(Stream.of(compPath), + walkObject(visitor, object, compSpan, compPath)); + } + case EXCLUDE_DESCEND: { + TraceObject object = visitor.continueObject(value); + return walkObject(visitor, object, compSpan, compPath); + } + } + throw new AssertionError(); + } + + /** + * Walk an object and its subtree + * + * @param visitor the visitor + * @param object the current object + * @param span the composed span from seed to current object + * @param path the path from seed to current object + * @return the result stream of the object and subtree walked + */ + public Stream walkObject(Visitor visitor, TraceObject object, + Range span, TraceObjectValPath path) { + return visitor.continueValues(object, span, path) + .flatMap(v -> walkValue(visitor, v, span, path)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java index 2e17c9a11e..9196d5f36b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceTimeManager.java @@ -26,7 +26,8 @@ import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceManager; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.Trace.TraceSnapshotChangeType; -import ghidra.trace.model.time.*; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.trace.model.time.TraceTimeManager; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java index 454e4cdcbe..6857463248 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/Trace.java @@ -275,6 +275,12 @@ public interface Trace extends DataTypeManagerDomainObject { // NOTE: No MOVING, SPLITTING, or JOINING } + public static final class TraceOverlaySpaceChangeType + extends DefaultTraceChangeType { + public static final TraceOverlaySpaceChangeType ADDED = new TraceOverlaySpaceChangeType(); + public static final TraceOverlaySpaceChangeType DELETED = new TraceOverlaySpaceChangeType(); + } + public static final class TraceMemoryStateChangeType extends DefaultTraceChangeType { public static final TraceMemoryStateChangeType CHANGED = @@ -386,7 +392,7 @@ public interface Trace extends DataTypeManagerDomainObject { public static final TracePlatformChangeType MAPPING_ADDED = new TracePlatformChangeType<>(); public static final TracePlatformChangeType MAPPING_DELETED = - new TracePlatformChangeType<>(); + new TracePlatformChangeType<>(); } public interface TraceProgramViewListener { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java index 4c8554cdd7..6e057af99b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceDomainObjectListener.java @@ -165,8 +165,16 @@ public class TraceDomainObjectListener implements DomainObjectListener { } } + @SuppressWarnings("unchecked") + public void handleTraceChangeRecord(TraceChangeRecord rec) { + @SuppressWarnings("rawtypes") + EventRecordHandler handler = typedMap.get(rec.getType()); + if (handler != null) { + handler.handle(rec); + } + } + @Override - @SuppressWarnings({ "unchecked", "rawtypes" }) public void domainObjectChanged(DomainObjectChangedEvent ev) { //TimedMsg.info(this, "Handing (" + this + "): " + ev); if (restoredHandler != null && ev.containsEvent(DomainObject.DO_OBJECT_RESTORED)) { @@ -184,11 +192,7 @@ public class TraceDomainObjectListener implements DomainObjectListener { //String typeName = DefaultTraceChangeType.getName(rec.getEventType()); //CountsByType.compute(typeName, (k, v) -> v == null ? 1 : v + 1); if (rec instanceof TraceChangeRecord) { - TraceChangeRecord tcRec = (TraceChangeRecord) rec; - EventRecordHandler handler = typedMap.get(tcRec.getType()); - if (handler != null) { - handler.handle(tcRec); - } + handleTraceChangeRecord((TraceChangeRecord) rec); continue; } Consumer handler; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/bookmark/TraceBookmarkSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/bookmark/TraceBookmarkSpace.java index ab23e26315..6c9003e52a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/bookmark/TraceBookmarkSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/bookmark/TraceBookmarkSpace.java @@ -19,14 +19,11 @@ import com.google.common.collect.Range; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; -import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceRegisterUtils; public interface TraceBookmarkSpace extends TraceBookmarkOperations { AddressSpace getAddressSpace(); - TraceThread getThread(); - default TraceBookmark addBookmark(Range lifespan, Register register, TraceBookmarkType type, String category, String comment) { return addBookmark(lifespan, register.getAddress(), type, category, comment); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/context/TraceRegisterContextOperations.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/context/TraceRegisterContextOperations.java index 3c570dfac7..e407331636 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/context/TraceRegisterContextOperations.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/context/TraceRegisterContextOperations.java @@ -22,6 +22,7 @@ import com.google.common.collect.Range; import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.trace.model.guest.TracePlatform; public interface TraceRegisterContextOperations { /** @@ -43,7 +44,7 @@ public interface TraceRegisterContextOperations { Entry getEntry(Language language, Register register, long snap, Address address); - RegisterValue getValueWithDefault(Language language, Register register, long snap, + RegisterValue getValueWithDefault(TracePlatform platform, Register register, long snap, Address address); AddressSetView getRegisterValueAddressRanges(Language language, Register register, long snap, diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TraceGuestPlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TraceGuestPlatform.java index d42311fe48..d1030bcb0e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TraceGuestPlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TraceGuestPlatform.java @@ -20,6 +20,9 @@ import ghidra.program.model.address.AddressOverflowException; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; +/** + * A guest platform in a trace + */ public interface TraceGuestPlatform extends TracePlatform { /** @@ -34,6 +37,19 @@ public interface TraceGuestPlatform extends TracePlatform { TraceGuestPlatformMappedRange addMappedRange(Address hostStart, Address guestStart, long length) throws AddressOverflowException; + /** + * Add an address mapping from host register space to guest register space + * + *

+ * In guest space, the mapping is placed at 0 and has length large enough to accommodate all + * registers in the guest language. In host space, the mapping is placed after every other + * register mapping for every platform. + * + * @return the mapped range + * @throws AddressOverflowException if host register space was exhausted + */ + TraceGuestPlatformMappedRange addMappedRegisterRange() throws AddressOverflowException; + /** * Remove the mapped language, including all code units of the language */ diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TraceGuestPlatformMappedRange.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TraceGuestPlatformMappedRange.java index b382999be6..fbe8471681 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TraceGuestPlatformMappedRange.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TraceGuestPlatformMappedRange.java @@ -17,25 +17,78 @@ package ghidra.trace.model.guest; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; -import ghidra.program.model.lang.CompilerSpec; -import ghidra.program.model.lang.Language; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; +/** + * A range of mapped memory from guest platform to host platform + */ public interface TraceGuestPlatformMappedRange { - Language getHostLanguage(); - - CompilerSpec getHostCompilerSpec(); + /** + * Get the host platform + * + * @return the host platform + */ + TracePlatform getHostPlatform(); + /** + * Get the address range in the host + * + * @return the host range + */ AddressRange getHostRange(); + /** + * Get the guest platform + * + * @return the guest platform + */ TraceGuestPlatform getGuestPlatform(); + /** + * Get the address range in the guest + * + * @return the guest range + */ AddressRange getGuestRange(); + /** + * Translate an address from host to guest, if in the host range + * + * @param hostAddress the host address + * @return the guest address, or null + */ Address mapHostToGuest(Address hostAddress); + /** + * Translate an address range from host to guest, if wholly contained in the host range + * + * @param hostRange the host range + * @return the guest range, or null + */ + AddressRange mapHostToGuest(AddressRange hostRange); + + /** + * Translate an address from guest to host, if in the guest range + * + * @param guestAddress the guest address + * @return the host address, or null + */ Address mapGuestToHost(Address guestAddress); + /** + * Translate an address range from guest to host, if wholly contained in the guest range + * + * @param guestRange the guest range + * @return the host range, or null + */ + AddressRange mapGuestToHost(AddressRange guestRange); + + /** + * Delete this mapping entry + * + * @param monitor a monitor for cleaning up dependent objects, e.g., code units + * @throws CancelledException if the user cancels via the monitor + */ void delete(TaskMonitor monitor) throws CancelledException; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java index 039f7cc8e3..8b302bdc5d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/guest/TracePlatform.java @@ -15,12 +15,27 @@ */ package ghidra.trace.model.guest; +import ghidra.dbg.target.TargetRegister; +import ghidra.dbg.util.PathMatcher; import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.model.Trace; +import ghidra.trace.model.symbol.TraceLabelSymbol; +/** + * A platform within a trace + * + *

+ * Traces can model systems where multiple processors or languages are involved. Every trace has a + * "host" platform. There may also be zero or more "guest" platforms. The guest platforms' memories + * and registers must be mapped into the host platform to be used in the trace. This class provides + * access to the properties of a platform and a mechanisms for translating addresses between this + * and the host platform. If this is the host platform, the translation methods are the identity + * function. + */ public interface TracePlatform { + /** * Get the trace * @@ -82,7 +97,7 @@ public interface TracePlatform { AddressSetView getGuestAddressSet(); /** - * Map an address from host to guest + * Translate an address from host to guest * * @param hostAddress the host address * @return the guest address @@ -90,13 +105,89 @@ public interface TracePlatform { Address mapHostToGuest(Address hostAddress); /** - * Map an address from guest to host + * Translate a range from host to guest + * + *

+ * The entire range must be mapped to a single range. + * + * @param hostRange the host range + * @return the guest range + */ + AddressRange mapHostToGuest(AddressRange hostRange); + + /** + * Translate a set from host to guest + * + *

+ * Only those ranges (or parts of ranges) that map are included. + * + * @param hostSet the host set + * @return the guest set + */ + AddressSetView mapHostToGuest(AddressSetView hostSet); + + /** + * Translate an address from guest to host * * @param guestAddress the guest address * @return the host address */ Address mapGuestToHost(Address guestAddress); + /** + * Translate a range from guest to host + * + *

+ * The entire range must be mapped to a single range. + * + * @param guestRange the guest range + * @return the host range + */ + AddressRange mapGuestToHost(AddressRange guestRange); + + /** + * Translate a set from guest to host + * + *

+ * Only those ranges (or parts of ranges) that map are included. + * + * @param guestSet the guest set + * @return the host set + */ + AddressSetView mapGuestToHost(AddressSetView guestSet); + + /** + * Translate the given platform register to the given host overlay space + * + * @param overlay the overlay space, usually that allocated for a thread or frame + * @param register the platform register + * @return the host range + */ + AddressRange getConventionalRegisterRange(AddressSpace overlay, Register register); + + /** + * Get the expected path where an object defining the register value would be + * + *

+ * This will check for a label in the host physical space, allowing a mapper to specify an + * alternative register name. + * + * @param overlay the overlay space allocated for a thread or frame + * @param register the platform register + * @return the path matcher, or null if there is no root schema + */ + PathMatcher getConventionalRegisterPath(AddressSpace overlay, Register register); + + /** + * Add a label the conventionally maps the value of a {@link TargetRegister} in the object + * manager to a register from this platform + * + * @param register the language register + * @param objectName the name of the {@link TargetRegister} in the object tree + * @return the label + */ + TraceLabelSymbol addRegisterMapOverride(Register register, String objectName); + /** * Get a memory buffer, which presents the host bytes in the guest address space * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseCodeUnitsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseCodeUnitsView.java index c0f27a8321..8b0c69d4ea 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseCodeUnitsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceBaseCodeUnitsView.java @@ -24,7 +24,6 @@ import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.trace.model.Trace; import ghidra.trace.model.TraceAddressSnapRange; -import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceRegisterUtils; import ghidra.util.IntersectionAddressSetView; import ghidra.util.UnionAddressSetView; @@ -44,20 +43,6 @@ public interface TraceBaseCodeUnitsView { */ Trace getTrace(); - /** - * Get the associated thread, if applicable - * - * @return the thread, or null - */ - TraceThread getThread(); - - /** - * Get the associated frame level, if applicable - * - * @return the frame level, or 0 - */ - int getFrameLevel(); - /** * Get the total number of defined units in this view * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceCodeSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceCodeSpace.java index c5662037d2..3d12508708 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceCodeSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceCodeSpace.java @@ -17,7 +17,6 @@ package ghidra.trace.model.listing; import ghidra.program.database.code.CodeManager; import ghidra.program.model.address.AddressSpace; -import ghidra.trace.model.thread.TraceThread; /** * A space within a {@link CodeManager} bound to a specific address space or thread and frame @@ -39,19 +38,4 @@ public interface TraceCodeSpace extends TraceCodeOperations { * @return the address space */ AddressSpace getAddressSpace(); - - /** - * Get the associated thread, if applicable - * - * @return the thread, or null - */ - TraceThread getThread(); - - /** - * Get the associated frame level, if applicable - * - * @return the frame level, or 0 - */ - int getFrameLevel(); - } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceDefinedDataView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceDefinedDataView.java index b7469e7bf7..9f43229b88 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceDefinedDataView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/listing/TraceDefinedDataView.java @@ -76,7 +76,7 @@ public interface TraceDefinedDataView extends TraceBaseDefinedUnitsView lifespan, Register register, DataType dataType) throws CodeUnitInsertionException { // TODO: A better way to handle memory-mapped registers? - Trace trace = getThread().getTrace(); + Trace trace = getTrace(); if (register.getAddressSpace() != trace .getBaseLanguage() .getAddressFactory() diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java index d79b0d9909..4e5a275d10 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryOperations.java @@ -25,8 +25,11 @@ import java.util.function.Predicate; import com.google.common.collect.Range; import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.model.*; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.time.TraceSnapshot; import ghidra.util.exception.DuplicateNameException; import ghidra.util.task.TaskMonitor; @@ -505,4 +508,260 @@ public interface TraceMemoryOperations { * recommended whenever the trace is saved to disk. */ void pack(); + + /** + * Set the state of a given register at a given time + * + *

+ * Setting state to {@link TraceMemoryState#KNOWN} via this method is not recommended. Setting + * bytes will automatically update the state accordingly. + * + * @param platform the platform whose language defines the register + * @param snap the time + * @param register the register + * @param state the state + */ + void setState(TracePlatform platform, long snap, Register register, TraceMemoryState state); + + /** + * Set the state of a given register at a given time + * + *

+ * Setting state to {@link TraceMemoryState#KNOWN} via this method is not recommended. Setting + * bytes will automatically update the state accordingly. + * + * @param snap the time + * @param register the register + * @param state the state + */ + default void setState(long snap, Register register, TraceMemoryState state) { + setState(getTrace().getPlatformManager().getHostPlatform(), snap, register, state); + } + + /** + * Assert that a register's range has a single state at the given snap and get that state + * + * @param platform the platform whose language defines the register + * @param snap the time + * @param register the register to examine + * @return the state + * @throws IllegalStateException if the register is mapped to more than one state. See + * {@link #getStates(long, Register)} + */ + TraceMemoryState getState(TracePlatform platform, long snap, Register register); + + /** + * Assert that a register's range has a single state at the given snap and get that state + * + * @param snap the time + * @param register the register to examine + * @return the state + * @throws IllegalStateException if the register is mapped to more than one state. See + * {@link #getStates(long, Register)} + */ + default TraceMemoryState getState(long snap, Register register) { + return getState(getTrace().getPlatformManager().getHostPlatform(), snap, register); + } + + /** + * Break the register's range into smaller ranges each mapped to its state at the given snap + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + * @param platform the platform whose language defines the register + * @param snap the time + * @param register the register to examine + * @return the map of ranges to states + */ + Collection> getStates(TracePlatform platform, + long snap, Register register); + + /** + * Break the register's range into smaller ranges each mapped to its state at the given snap + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + * @param snap the time + * @param register the register to examine + * @return the map of ranges to states + */ + default Collection> getStates(long snap, + Register register) { + return getStates(getTrace().getPlatformManager().getHostPlatform(), snap, register); + } + + /** + * @see #setValue(long, RegisterValue) + * @param platform the platform whose language defines the register + * @param snap the snap + * @param value the register value + * @return the number of bytes written + */ + int setValue(TracePlatform platform, long snap, RegisterValue value); + + /** + * Set the value of a register at the given snap + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. In those + * cases, the assignment affects all threads. + * + *

+ * IMPORTANT: The trace database cannot track the state ({@link TraceMemoryState#KNOWN}, + * etc.) with per-bit accuracy. It only has byte precision. If the given value specifies, e.g., + * only a single bit, then the entire byte will become marked {@link TraceMemoryState#KNOWN}, + * even though the remaining 7 bits could technically be unknown. + * + * @param snap the snap + * @param value the register value + * @return the number of bytes written + */ + default int setValue(long snap, RegisterValue value) { + return setValue(getTrace().getPlatformManager().getHostPlatform(), snap, value); + } + + /** + * @see #putBytes(long, Register, ByteBuffer) + * @param platform the platform whose language defines the register + * @param snap the snap + * @param register the register to modify + * @param buf the buffer of bytes to write + * @return the number of bytes written + */ + int putBytes(TracePlatform platform, long snap, Register register, ByteBuffer buf); + + /** + * Write bytes at the given snap and register address + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. In those + * cases, the assignment affects all threads. + * + *

+ * Note that bit-masked registers are not properly heeded. If the caller wishes to preserve + * non-masked bits, it must first retrieve the current value and combine it with the desired + * value. The caller must also account for any bit shift in the passed buffer. Alternatively, + * consider {@link #setValue(long, RegisterValue)}. + * + * @param snap the snap + * @param register the register to modify + * @param buf the buffer of bytes to write + * @return the number of bytes written + */ + default int putBytes(long snap, Register register, ByteBuffer buf) { + return putBytes(getTrace().getPlatformManager().getHostPlatform(), snap, register, buf); + } + + /** + * Get the most-recent value of a given register at the given time + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + * @param platform the platform whose language defines the register + * @param snap the time + * @param register the register + * @return the value + */ + RegisterValue getValue(TracePlatform platform, long snap, Register register); + + /** + * Get the most-recent value of a given register at the given time + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + * @param snap the time + * @param register the register + * @return the value + */ + default RegisterValue getValue(long snap, Register register) { + return getValue(getTrace().getPlatformManager().getHostPlatform(), snap, register); + } + + /** + * Get the most-recent value of a given register at the given time + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + * @param platform the platform whose language defines the register + * @param snap the time + * @param register the register + * @return the value + */ + RegisterValue getViewValue(TracePlatform platform, long snap, Register register); + + /** + * Get the most-recent value of a given register at the given time, following schedule forks + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + * @param snap the time + * @param register the register + * @return the value + */ + default RegisterValue getViewValue(long snap, Register register) { + return getViewValue(getTrace().getPlatformManager().getHostPlatform(), snap, register); + } + + /** + * Get the most-recent bytes of a given register at the given time + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + * @param platform the platform whose language defines the register + * @param snap the time + * @param register the register + * @param buf the destination buffer + * @return the number of bytes read + */ + int getBytes(TracePlatform platform, long snap, Register register, ByteBuffer buf); + + /** + * Get the most-recent bytes of a given register at the given time + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + * @param snap the time + * @param register the register + * @param buf the destination buffer + * @return the number of bytes read + */ + default int getBytes(long snap, Register register, ByteBuffer buf) { + return getBytes(getTrace().getPlatformManager().getHostPlatform(), snap, register, buf); + } + + /** + * @see #removeValue(long, Register) + * @param platform the platform whose language defines the register + * @param snap the snap + * @param register the register + */ + void removeValue(TracePlatform platform, long snap, Register register); + + /** + * Remove a value from the given time and register + * + *

+ * If the register is memory mapped, this will delegate to the appropriate space. + * + *

+ * IMPORANT: The trace database cannot track the state ({@link TraceMemoryState#KNOWN}, + * etc.) with per-bit accuracy. It only has byte precision. If the given register specifies, + * e.g., only a single bit, then the entire byte will become marked + * {@link TraceMemoryState#UNKNOWN}, even though the remaining 7 bits could technically be + * known. + * + * @param snap the snap + * @param register the register + */ + default void removeValue(long snap, Register register) { + removeValue(getTrace().getPlatformManager().getHostPlatform(), snap, register); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemorySpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemorySpace.java index 8e19158a73..0aa4051e8e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemorySpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemorySpace.java @@ -15,19 +15,9 @@ */ package ghidra.trace.model.memory; -import java.nio.ByteBuffer; -import java.util.*; -import java.util.Map.Entry; - -import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.lang.Register; -import ghidra.program.model.lang.RegisterValue; -import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.listing.TraceCodeManager; import ghidra.trace.model.listing.TraceCodeSpace; -import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.util.TraceRegisterUtils; /** * A portion of the memory manager bound to a particular address space @@ -57,259 +47,4 @@ public interface TraceMemorySpace extends TraceMemoryOperations { * @return the code space */ TraceCodeSpace getCodeSpace(boolean createIfAbsent); - - /** - * Get the thread for this register space - * - * @return the thread - */ - TraceThread getThread(); - - /** - * Get the registers - * - *

- * This is a convenience for {@code getTrace().getBaseLanguage().getRegisters()} - * - * @return the list of registers - */ - default List getRegisters() { - return getTrace().getBaseLanguage().getRegisters(); - } - - /** - * Set the state of a given register at a given time - * - *

- * Setting state to {@link TraceMemoryState#KNOWN} via this method is not recommended. Setting - * bytes will automatically update the state accordingly. - * - * @param snap the time - * @param register the register - * @param state the state - */ - default void setState(long snap, Register register, TraceMemoryState state) { - setState(snap, TraceRegisterUtils.rangeForRegister(register), state); - } - - /** - * Assert that a register's range has a single state at the given snap and get that state - * - * @param snap the time - * @param register the register to examine - * @return the state - * @throws IllegalStateException if the register is mapped to more than one state. See - * {@link #getStates(long, Register)} - */ - default TraceMemoryState getState(long snap, Register register) { - Collection> states = - getStates(snap, register); - if (states.isEmpty()) { - return TraceMemoryState.UNKNOWN; - } - if (states.size() != 1) { - throw new IllegalStateException("More than one state is present in " + register); - } - return states.iterator().next().getValue(); - } - - /** - * Break the register's range into smaller ranges each mapped to its state at the given snap - * - *

- * If the register is memory mapped, this will delegate to the appropriate space. - * - * @param snap the time - * @param register the register to examine - * @return the map of ranges to states - */ - default Collection> getStates(long snap, - Register register) { - AddressRange range = TraceRegisterUtils.rangeForRegister(register); - if (register.getAddressSpace() != getAddressSpace()) { - return getTrace().getMemoryManager().getStates(snap, range); - } - return getStates(snap, range); - } - - /** - * Set the value of a register at the given snap - * - *

- * If the register is memory mapped, this will delegate to the appropriate space. In those - * cases, the assignment affects all threads. - * - *

- * IMPORTANT: The trace database cannot track the state ({@link TraceMemoryState#KNOWN}, - * etc.) with per-bit accuracy. It only has byte precision. If the given value specifies, e.g., - * only a single bit, then the entire byte will become marked {@link TraceMemoryState#KNOWN}, - * even though the remaining 7 bits could technically be unknown. - * - * @param snap the snap - * @param value the register value - * @return the number of bytes written - */ - default int setValue(long snap, RegisterValue value) { - if (!value.hasAnyValue()) { - return 0; - } - Register reg = value.getRegister(); - if (!value.hasValue() || !TraceRegisterUtils.isByteBound(reg)) { - RegisterValue old = getValue(snap, reg.getBaseRegister()); - // Do not use .getRegisterValue, as that will zero unmasked bits - // Instead, we'll pass the original register to bufferForValue - value = old.combineValues(value); - } - ByteBuffer buf = TraceRegisterUtils.bufferForValue(reg, value); - // TODO: A better way to deal with memory-mapped registers? - if (reg.getAddressSpace() != getAddressSpace()) { - return getTrace().getMemoryManager().putBytes(snap, reg.getAddress(), buf); - } - return putBytes(snap, reg.getAddress(), buf); - } - - /** - * Write bytes at the given snap and register address - * - *

- * If the register is memory mapped, this will delegate to the appropriate space. In those - * cases, the assignment affects all threads. - * - *

- * Note that bit-masked registers are not properly heeded. If the caller wishes to preserve - * non-masked bits, it must first retrieve the current value and combine it with the desired - * value. The caller must also account for any bit shift in the passed buffer. Alternatively, - * consider {@link #setValue(long, RegisterValue)}. - * - * @param snap the snap - * @param register the register to modify - * @param buf the buffer of bytes to write - * @return the number of bytes written - */ - default int putBytes(long snap, Register register, ByteBuffer buf) { - int byteLength = register.getNumBytes(); - int limit = buf.limit(); - buf.limit(Math.min(limit, buf.position() + byteLength)); - // TODO: A better way to deal with memory-mapped registers? - int result; - if (register.getAddressSpace() != getAddressSpace()) { - result = getTrace().getMemoryManager().putBytes(snap, register.getAddress(), buf); - } - else { - result = putBytes(snap, register.getAddress(), buf); - } - buf.limit(limit); - return result; - } - - /** - * Get the most-recent value of a given register at the given time - * - *

- * If the register is memory mapped, this will delegate to the appropriate space. - * - * @param snap the time - * @param register the register - * @return the value - */ - default RegisterValue getValue(long snap, Register register) { - return TraceRegisterUtils.getRegisterValue(register, (a, buf) -> { - // TODO: A better way to deal with memory-mapped registers? - if (a.getAddressSpace() != getAddressSpace()) { - getTrace().getMemoryManager().getBytes(snap, a, buf); - } - else { - getBytes(snap, a, buf); - } - }); - } - - /** - * Get the most-recent value of a given register at the given time, following schedule forks - * - *

- * If the register is memory mapped, this will delegate to the appropriate space. - * - * @param snap the time - * @param register the register - * @return the value - */ - default RegisterValue getViewValue(long snap, Register register) { - return TraceRegisterUtils.getRegisterValue(register, (a, buf) -> { - // TODO: A better way to deal with memory-mapped registers? - if (a.getAddressSpace() != getAddressSpace()) { - getTrace().getMemoryManager().getViewBytes(snap, a, buf); - } - else { - getViewBytes(snap, a, buf); - } - }); - } - - /** - * Get the most-recent bytes of a given register at the given time - * - *

- * If the register is memory mapped, this will delegate to the appropriate space. - * - * @param snap the time - * @param register the register - * @param buf the destination buffer - * @return the number of bytes read - */ - default int getBytes(long snap, Register register, ByteBuffer buf) { - int byteLength = register.getNumBytes(); - int limit = buf.limit(); - buf.limit(Math.min(limit, buf.position() + byteLength)); - // TODO: A better way to deal with memory-mapped registers? - int result; - if (register.getAddressSpace() != getAddressSpace()) { - result = getTrace().getMemoryManager().getBytes(snap, register.getAddress(), buf); - } - else { - result = getBytes(snap, register.getAddress(), buf); - } - buf.limit(limit); - return result; - } - - /** - * Remove a value from the given time and register - * - *

- * If the register is memory mapped, this will delegate to the appropriate space. - * - *

- * IMPORANT: The trace database cannot track the state ({@link TraceMemoryState#KNOWN}, - * etc.) with per-bit accuracy. It only has byte precision. If the given register specifies, - * e.g., only a single bit, then the entire byte will become marked - * {@link TraceMemoryState#UNKNOWN}, even though the remaining 7 bits could technically be - * known. - * - * @param snap the snap - * @param register the register - */ - default void removeValue(long snap, Register register) { - int byteLength = register.getNumBytes(); - if (register.getAddressSpace() != getAddressSpace()) { - getTrace().getMemoryManager().removeBytes(snap, register.getAddress(), byteLength); - } - else { - removeBytes(snap, register.getAddress(), byteLength); - } - } - - /** - * Get the most recent values for all registers at the given time - * - * @param snap the time - * @return all register values - */ - default Collection getAllValues(long snap) { - Set result = new LinkedHashSet<>(); - for (Register reg : getRegisters()) { - result.add(getValue(snap, reg)); - } - return result; - } -} +} \ No newline at end of file diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapSpace.java index be308b51f1..ba972f6a8e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapSpace.java @@ -24,7 +24,6 @@ import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; import ghidra.trace.model.Trace; import ghidra.trace.model.TraceAddressSnapRange; -import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceRegisterUtils; /** @@ -55,19 +54,6 @@ public interface TracePropertyMapSpace extends TracePropertyMapOperations * @return the address space */ AddressSpace getAddressSpace(); - /** - * Get the thread for this space - * - * @return the thread - */ - TraceThread getThread(); - - /** - * Get the frame level for this space - * - * @return the frame level, 0 being the innermost - */ - int getFrameLevel(); /** * Set a property on the given register for the given lifespan diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/symbol/TraceNamespaceSymbolView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/symbol/TraceNamespaceSymbolView.java index 8627a3eaf0..bcd894fd69 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/symbol/TraceNamespaceSymbolView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/symbol/TraceNamespaceSymbolView.java @@ -23,5 +23,5 @@ public interface TraceNamespaceSymbolView extends TraceSymbolNoDuplicatesView { TraceNamespaceSymbol add(String name, TraceNamespaceSymbol parent, SourceType source) - throws DuplicateNameException, InvalidInputException, IllegalArgumentException; + throws DuplicateNameException, InvalidInputException; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java index d737e8ea4e..900220509c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java @@ -16,6 +16,7 @@ package ghidra.trace.model.target; import java.util.Collection; +import java.util.List; import java.util.stream.Stream; import com.google.common.collect.Range; @@ -205,6 +206,15 @@ public interface TraceObject extends TraceUniqueObject { */ Collection getValues(); + /** + * Get values with the given key intersecting the given span + * + * @param span the span + * @param key the key + * @return the collection of values + */ + Collection getValues(Range span, String key); + /** * Get values with the given key intersecting the given span ordered by time * @@ -323,6 +333,18 @@ public interface TraceObject extends TraceUniqueObject { Stream getOrderedSuccessors(Range span, TraceObjectKeyPath relativePath, boolean forward); + /** + * Stream all canonical successor values of this object matching the given predicates + * + *

+ * If an object has a disjoint life, i.e., multiple canonical parents, then only the + * least-recent of those is traversed. + * + * @param relativePath the path relative to this object + * @return the stream of value paths + */ + Stream getCanonicalSuccessors(PathPredicates relativePredicates); + /** * Set a value for the given lifespan * @@ -507,4 +529,23 @@ public interface TraceObject extends TraceUniqueObject { } return false; } + + /** + * Search for a suitable object having the given target interface + * + *

+ * This operates by examining the schema for a unique suitable path, without regard to + * lifespans. If needed, the caller should inspect the object's life. + * + * @param targetIf the target interface + * @return the suitable object, or null if not found + */ + default TraceObject querySuitableTargetInterface(Class targetIf) { + List path = getRoot().getTargetSchema() + .searchForSuitable(targetIf, getCanonicalPath().getKeyList()); + if (path == null) { + return null; + } + return getTrace().getObjectManager().getObjectByCanonicalPath(TraceObjectKeyPath.of(path)); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java index 0c9165c8f4..ece0f8b69c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java @@ -175,6 +175,12 @@ public final class TraceObjectKeyPath implements Comparable return PathUtils.getIndex(keyList); } + /** + * {@inheritDoc} + * + *

+ * Gives the dot-joined path + */ @Override public String toString() { return PathUtils.toString(keyList); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java index 4bb64248c8..14f2e821cb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java @@ -18,6 +18,7 @@ package ghidra.trace.model.target; import java.util.List; import ghidra.dbg.util.PathPredicates; +import ghidra.trace.database.target.DBTraceObjectValPath; /** * A path of values leading from one object to another @@ -30,6 +31,16 @@ import ghidra.dbg.util.PathPredicates; * "intersect" any given span. */ public interface TraceObjectValPath extends Comparable { + + /** + * Get the zero-length path + * + * @return the empty path + */ + static TraceObjectValPath of() { + return DBTraceObjectValPath.of(); + } + /** * Get the values in the path, ordered from source to destination * @@ -113,4 +124,29 @@ public interface TraceObjectValPath extends Comparable { * @throws ClassCastException if the destination value is not an object */ TraceObject getDestination(TraceObject ifEmpty); + + /** + * Append the entry to this path, generating a new path + * + *

+ * This performs no validation. The parent of the given entry should be the child of the last + * entry in this path. + * + * @param entry the entry to append + * @return the new path + */ + TraceObjectValPath append(TraceObjectValue entry); + + /** + * Prepend the entry to this path, generating a new path + * + *

+ * This performs no validation. The child of the given entry should be the parent of the first + * entry in this path. + * + * @param entry the entry to prepend + * @return the new path + */ + TraceObjectValPath prepend(TraceObjectValue entry); + } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java index 3f30d30e44..5d37590a55 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/Sequence.java @@ -421,4 +421,23 @@ public class Sequence implements Comparable { } return steps.get(steps.size() - 1).getThreadKey(); } + + /** + * Collect all the threads involved in this sequence + * + * @param into a set to collect the threads + * @param trace the trace whose threads to collect + * @param eventThread the default starting thread + * @return the last thread named in the sequence + */ + public TraceThread collectThreads(Set into, Trace trace, TraceThread eventThread) { + // TODO: Visitor pattern? + TraceThreadManager tm = trace.getThreadManager(); + TraceThread thread = eventThread; + for (Step step : steps) { + into.add(thread); + thread = step.getThread(tm, eventThread); + } + return thread; + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java index 66bd4c1a82..638b77429c 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/TraceSchedule.java @@ -559,4 +559,20 @@ public class TraceSchedule implements Comparable { ticks.coalescePatches(thread.getTrace().getBaseLanguage()); return new TraceSchedule(snap, ticks, new Sequence()); } + + /** + * Get the threads involved in the schedule + * + * @param trace the trace whose threads to get + * @return the set of threads + */ + public Set getThreads(Trace trace) { + Set result = new HashSet<>(); + TraceThread lastThread = getEventThread(trace); + lastThread = steps.collectThreads(result, trace, lastThread); + lastThread = pSteps.collectThreads(result, trace, lastThread); + result.add(lastThread); + result.remove(null); + return result; + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java index 4787069a86..b242035826 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/OverlappingObjectIterator.java @@ -27,7 +27,7 @@ import ghidra.trace.model.TraceAddressSnapRange; import ghidra.util.AbstractPeekableIterator; public class OverlappingObjectIterator extends AbstractPeekableIterator> { - interface Ranger { + public interface Ranger { Address getMinAddress(T t); Address getMaxAddress(T t); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java index 43ad7087fe..e68e8e70a3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceRegisterUtils.java @@ -51,15 +51,15 @@ public enum TraceRegisterUtils { return Arrays.copyOfRange(arr, arr.length - length, arr.length); } - public static ByteBuffer bufferForValue(Register reg, RegisterValue value) { + public static ByteBuffer bufferForValue(Register register, RegisterValue value) { byte[] bytes = value.toBytes().clone(); int start = bytes.length / 2; // NB: I guess contextreg is always big? - if (!reg.isBigEndian() && !reg.isProcessorContext()) { + if (!register.isBigEndian() && !register.isProcessorContext()) { ArrayUtils.reverse(bytes, start, bytes.length); } - int offset = TraceRegisterUtils.computeMaskOffset(reg); - return ByteBuffer.wrap(bytes, start + offset, reg.getNumBytes()); + int offset = TraceRegisterUtils.computeMaskOffset(register); + return ByteBuffer.wrap(bytes, start + offset, register.getNumBytes()); } public static int computeMaskOffset(Register reg) { @@ -161,6 +161,32 @@ public enum TraceRegisterUtils { return regs.getValue(snap, reg.getBaseRegister()).combineValues(rv); } + public static ByteBuffer prepareBuffer(Register register) { + /* + * The byte array for reg values spans the whole base register, but we'd like to avoid + * over-reading, so we'll zero in on the bytes actually included in the mask. We'll then + * have to handle endianness and such. The regval instance should then apply the actual mask + * for the sub-register, if applicable. + */ + int byteLength = register.getNumBytes(); + byte[] mask = register.getBaseMask(); + ByteBuffer buf = ByteBuffer.allocate(mask.length * 2); + buf.put(mask); + int maskOffset = TraceRegisterUtils.computeMaskOffset(register); + int startVal = buf.position() + maskOffset; + buf.position(startVal); + buf.limit(buf.position() + byteLength); + return buf; + } + + public static RegisterValue finishBuffer(ByteBuffer buf, Register register) { + byte[] arr = buf.array(); + if (!register.isBigEndian() && !register.isProcessorContext()) { + ArrayUtils.reverse(arr, register.getBaseMask().length, buf.capacity()); + } + return new RegisterValue(register, arr); + } + public static RegisterValue getRegisterValue(Register reg, BiConsumer readAction) { /* diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceViewportSpanIterator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceViewportSpanIterator.java index ea760eb430..ef1c63471a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceViewportSpanIterator.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceViewportSpanIterator.java @@ -18,7 +18,8 @@ package ghidra.trace.util; import com.google.common.collect.*; import ghidra.trace.model.Trace; -import ghidra.trace.model.time.*; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.trace.model.time.TraceTimeManager; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.AbstractPeekableIterator; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java index a106edc799..5a5072264e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java @@ -27,16 +27,27 @@ import org.junit.Test; import com.google.common.collect.Range; -import ghidra.app.plugin.assembler.Assembler; -import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.assembler.*; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.lifecycle.Unfinished; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRangeImpl; import ghidra.program.model.lang.*; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.context.DBTraceRegisterContextManager; +import ghidra.trace.database.target.DBTraceObjectManager; +import ghidra.trace.database.target.DBTraceObjectManagerTest; +import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceThread; import ghidra.util.NumericUtilities; import ghidra.util.database.UndoableTransaction; @@ -61,9 +72,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "PUSH 0xdeadbeef")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); // Verify no changes to trace @@ -73,7 +83,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest TraceSleighUtils.evaluate("*:4 0x0010fffc:8", tb.trace, 0, thread, 0)); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } // 4, not 8 bytes pushed? @@ -102,14 +112,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x0010fff8), @@ -143,9 +152,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV EAX,0xdeadbeef", // 5 bytes "MOV ECX,0xbaadf00d")); // 5 bytes - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); assertEquals(tb.addr(0x00400007), emuThread.getCounter()); @@ -155,7 +163,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -205,9 +213,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest asm.patchProgram(mov, tb.addr(0x00401000)); } - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); emuThread.stepInstruction(); @@ -222,7 +229,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -249,13 +256,12 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "imm r0, #911")); // decimal - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -283,14 +289,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "imm r0, #860", "imm r1, #861")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); // brds and 1st imm executed emuThread.stepInstruction(); // 3rd imm executed try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x00400008), @@ -327,15 +332,14 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "XOR byte ptr [0x00400007], 0xcc", // 7 bytes "MOV EAX,0xdeadbeef")); // 5 bytes - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -365,9 +369,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); assertNull(emuThread.getFrame()); emuThread.stepPcodeOp(); @@ -389,7 +392,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest assertNull(emuThread.getFrame()); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x0040000c), @@ -433,7 +436,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0) { @Override protected PcodeUseropLibrary createUseropLibrary() { return hexLib; @@ -441,7 +444,6 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest }; emu.inject(tb.addr(0x00400006), "hexdump(RSP);"); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); assertEquals("", dumped.toString()); @@ -481,7 +483,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0) { @Override protected PcodeUseropLibrary createUseropLibrary() { return hexLib; @@ -495,7 +497,6 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest hexdump(RIP); """); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); try { emuThread.run(); @@ -515,7 +516,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest dumped.delete(0, dumped.length()); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0xbaadf00dL), @@ -538,11 +539,10 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); emu.addBreakpoint(tb.addr(0x00400000), "RAX == 1"); emu.addBreakpoint(tb.addr(0x00400006), "RAX == 0"); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); try { emuThread.run(); @@ -571,13 +571,12 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "clz r1, r0")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(16), @@ -605,9 +604,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOVAPS XMM0, xmmword ptr [0x00600000]")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); assertEquals(tb.addr(0x00400007), emuThread.getCounter()); @@ -615,7 +613,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.getState().getVar(pc)); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(new BigInteger("0123456789abcdeffedcba9876543210", 16), @@ -643,9 +641,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "SAR EAX, CL")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); assertEquals(tb.addr(0x00400002), emuThread.getCounter()); @@ -653,7 +650,7 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.getState().getVar(pc)); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x7ffffff), @@ -673,14 +670,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "XOR AH, AH", "MOV RCX, RAX")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x12340078), @@ -697,15 +693,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOV RCX,RAX")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0) { @Override - protected TracePcodeExecutorState newState(TraceThread thread) { - return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, - thread, 0); + protected TracePcodeExecutorState newState(PcodeTraceDataAccess data) { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(data); } }; PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); } } @@ -721,15 +715,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOV RCX,RAX")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0) { @Override - protected TracePcodeExecutorState newState(TraceThread thread) { - return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, - thread, 0); + protected TracePcodeExecutorState newState(PcodeTraceDataAccess data) { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(data); } }; PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); // No assertions. It should simply not throw an exception. } @@ -747,15 +739,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV RCX,RAX")); // Start emulator one snap later - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 1) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 1) { @Override - protected TracePcodeExecutorState newState(TraceThread thread) { - return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, - thread, 0); + protected TracePcodeExecutorState newState(PcodeTraceDataAccess data) { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(data); } }; PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); // No assertions. It should throw an exception. } @@ -773,15 +763,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV RCX,RAX")); // Start emulator one snap later, but with "has-known" checks - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 1) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 1) { @Override - protected TracePcodeExecutorState newState(TraceThread thread) { - return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, - thread, 0); + protected TracePcodeExecutorState newState(PcodeTraceDataAccess data) { + return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(data); } }; PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); // No assertions. It should simply not throw an exception. } @@ -797,15 +785,13 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV RAX,0", // Have the program initialize it "MOV RCX,RAX")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0) { @Override - protected TracePcodeExecutorState newState(TraceThread thread) { - return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, - thread, 0); + protected TracePcodeExecutorState newState(PcodeTraceDataAccess data) { + return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(data); } }; PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); emuThread.stepInstruction(); // No assertions. It should simply not throw an exception. @@ -838,16 +824,15 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest tb.trace.getMemoryManager().getBytes(0, tb.addr(0x00400000), buf); assertArrayEquals(tb.arr(0x48, 0x89, 0xc1), buf.array()); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); // TODO: Seems the Trace-bound thread ought to know to do this in reInitialize() - ctxVal = ctxManager.getValueWithDefault(lang, ctxReg, 0, tb.addr(0x00400000)); - emuThread.overrideContext(ctxVal); + ctxVal = ctxManager.getValueWithDefault(tb.host, ctxReg, 0, tb.addr(0x00400000)); emuThread.stepInstruction(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x00400003), @@ -876,13 +861,12 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOV EAX, dword ptr [RBP + -0x4]")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x12345678), @@ -910,9 +894,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOV EAX, dword ptr [RBP + -0x2]")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); } } @@ -933,9 +916,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "MOV EAX, dword ptr [EBP + -0x2]")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); } } @@ -956,9 +938,8 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "unimpl")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); } } @@ -979,13 +960,12 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "mov.w [W1], W0")); - BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.host, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); - //emuThread.overrideContextWithDefault(); // default context is null? emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1); + emu.writeDown(tb.host, 1, 1); } assertEquals(BigInteger.valueOf(0x000102), @@ -997,4 +977,73 @@ public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest } } + /** + * Test a single instruction in guest mode without identical mapping + * + *

+ * This is a repeat of the {@link #testSinglePUSH()} test, but as a guest platform, where the + * guest is mapped to addresses with shifted offsets (not identical). This will ensure the + * translation is happening properly. + */ + @Test + public void testGuestSinglePUSH() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "DATA:BE:64:default")) { + TraceMemoryManager mm = tb.trace.getMemoryManager(); + TraceThread thread; + TraceGuestPlatform x64; + try (UndoableTransaction tid = tb.startTransaction()) { + SchemaContext ctx = XmlSchemaContext.deserialize(DBTraceObjectManagerTest.XML_CTX); + DBTraceObjectManager objects = tb.trace.getObjectManager(); + objects.createRootObject(ctx.getSchema(new SchemaName("Session"))); + thread = tb.getOrAddThread("Targets[0].Threads[0]", 0); + + mm.addRegion("Targets[0].Memory[bin:.text]", Range.atLeast(0L), + tb.range(0x00000000, 0x0000ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + mm.addRegion("Targets[0].Memory[stack1]", Range.atLeast(0L), + tb.range(0x20000000, 0x2000ffff), + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + + x64 = tb.trace.getPlatformManager() + .addGuestPlatform(getSLEIGH_X86_64_LANGUAGE().getDefaultCompilerSpec()); + x64.addMappedRegisterRange(); + x64.addMappedRange(tb.addr(0x00000000), tb.addr(x64, 0x00400000), 0x10000); + x64.addMappedRange(tb.addr(0x20000000), tb.addr(x64, 0x00100000), 0x10000); + objects.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers")) + .insert(Range.atLeast(0L), ConflictResolution.DENY); + + tb.exec(x64, 0, thread, 0, """ + RIP = 0x00400000; + RSP = 0x00110000; + """); + + Assembler asm = Assemblers.getAssembler(x64.getLanguage()); + AssemblyBuffer buf = new AssemblyBuffer(asm, tb.addr(x64, 0x00400000)); + buf.assemble("PUSH 0xdeadbeef"); + mm.putBytes(0, tb.addr(0x00000000), ByteBuffer.wrap(buf.getBytes())); + } + + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(x64, 0); + PcodeThread emuThread = emu.newThread(thread.getPath()); + emuThread.stepInstruction(); + + String changedExpr = "*:4 0x2000fffc:8"; + // Verify no changes to trace + TraceMemorySpace regs = mm.getMemoryRegisterSpace(thread, 0, false); + assertEquals(BigInteger.valueOf(0x00110000), + regs.getValue(x64, 0, tb.reg(x64, "RSP")).getUnsignedValue()); + assertEquals(BigInteger.valueOf(0), + TraceSleighUtils.evaluate(changedExpr, tb.trace, 0, thread, 0)); + + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(x64, 1, 1); + } + + // 4, not 8 bytes pushed? + assertEquals(BigInteger.valueOf(0x0010fffc), + regs.getValue(x64, 1, tb.reg(x64, "RSP")).getUnsignedValue()); + assertEquals(BigInteger.valueOf(0xefbeaddeL), // Guest is LE, host is BE + TraceSleighUtils.evaluate(changedExpr, tb.trace, 1, thread, 0)); + } + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java index 4e411ae10e..a0cfef81d4 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java @@ -220,7 +220,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest PcodeExecutor executor = new PcodeExecutor<>(sp.getLanguage(), BytesPcodeArithmetic.forLanguage(b.language), - new DirectBytesTracePcodeExecutorState(b.trace, 0, thread, 0)); + new DirectBytesTracePcodeExecutorState(b.host, 0, thread, 0)); sp.execute(executor, PcodeUseropLibrary.nil()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java index 98340dae4e..4db2e4ba3b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java @@ -139,6 +139,24 @@ public class ToyDBTraceBuilder implements AutoCloseable { .execute(program, PcodeUseropLibrary.nil()); } + /** + * Manipulate the trace's memory and registers using Sleigh + * + * @param platform the platform whose language to use + * @param snap the snap to modify + * @param frame the frame to modify + * @param thread the thread to modify, can be {@code null} if only memory is used + * @param sleigh the lines of Sleigh, including semicolons. + */ + public void exec(TracePlatform platform, long snap, TraceThread thread, int frame, + String sleigh) { + TraceSleighUtils.buildByteExecutor(platform, snap, thread, frame) + .execute( + SleighProgramCompiler.compileProgram((SleighLanguage) platform.getLanguage(), + "builder", sleigh, PcodeUseropLibrary.nil()), + PcodeUseropLibrary.nil()); + } + /** * Get the named register * @@ -149,6 +167,17 @@ public class ToyDBTraceBuilder implements AutoCloseable { return language.getRegister(name); } + /** + * Get the named register + * + * @param the platform + * @param name the name + * @return the register or null if it doesn't exist + */ + public Register reg(TracePlatform platform, String name) { + return platform.getLanguage().getRegister(name); + } + /** * A shortcut for {@code space.getAdddress(offset)} * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerObjectTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerObjectTest.java index ca03586491..f197636d72 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerObjectTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerObjectTest.java @@ -18,8 +18,8 @@ package ghidra.trace.database.breakpoint; import org.junit.Before; import ghidra.dbg.target.schema.SchemaContext; -import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.util.database.UndoableTransaction; public class DBTraceBreakpointManagerObjectTest extends DBTraceBreakpointManagerTest { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java new file mode 100644 index 0000000000..e71048915d --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTraceObjectRegisterSupportTest.java @@ -0,0 +1,463 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.guest; + +import static org.junit.Assert.*; + +import java.math.BigInteger; +import java.nio.ByteBuffer; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetRegister; +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.util.PathMatcher; +import ghidra.dbg.util.PathUtils; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.*; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.database.target.DBTraceObjectManager; +import ghidra.trace.database.target.DBTraceObjectManagerTest; +import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.exception.DuplicateNameException; + +public class DBTraceObjectRegisterSupportTest extends AbstractGhidraHeadlessIntegrationTest { + protected ToyDBTraceBuilder b; + protected DBTraceObjectManager manager; + + protected SchemaContext ctx; + protected TraceObject root; + + @Before + public void setUpObjectManagerTest() throws Exception { + b = new ToyDBTraceBuilder("Testing", "Toy:BE:64:default"); + manager = b.trace.getObjectManager(); + + ctx = XmlSchemaContext.deserialize(DBTraceObjectManagerTest.XML_CTX); + } + + @Test + public void testRegisterMappingHost() throws DuplicateNameException { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regR0 = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[r0]")); + regR0.insert(Range.atLeast(0L), ConflictResolution.DENY); + b.trace.getMemoryManager() + .createOverlayAddressSpace("Targets[0].Threads[0].Registers", + b.trace.getBaseAddressFactory().getRegisterSpace()); + + regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); + } + + AddressSpace overlaySpace = + b.trace.getBaseAddressFactory().getAddressSpace("Targets[0].Threads[0].Registers"); + TraceMemorySpace regSpace = b.trace.getMemoryManager().getMemorySpace(overlaySpace, false); + assertEquals(BigInteger.valueOf(0x1234), + regSpace.getValue(0, b.reg("r0")).getUnsignedValue()); + } + + @Test + public void testRegisterMappingGuest() throws Throwable { + TraceGuestPlatform amd64; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regRAX = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[RAX]")); + regRAX.insert(Range.atLeast(0L), ConflictResolution.DENY); + b.trace.getMemoryManager() + .createOverlayAddressSpace("Targets[0].Threads[0].Registers", + b.trace.getBaseAddressFactory().getRegisterSpace()); + + amd64 = b.trace.getPlatformManager() + .addGuestPlatform( + getSLEIGH_X86_64_LANGUAGE().getCompilerSpecByID(new CompilerSpecID("gcc"))); + amd64.addMappedRegisterRange(); + + regRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); + } + + AddressSpace overlaySpace = + b.trace.getBaseAddressFactory().getAddressSpace("Targets[0].Threads[0].Registers"); + TraceMemorySpace regSpace = b.trace.getMemoryManager().getMemorySpace(overlaySpace, false); + assertEquals(BigInteger.valueOf(0x1234), + regSpace.getValue(amd64, 0, amd64.getLanguage().getRegister("RAX")).getUnsignedValue()); + } + + @Test + public void testRegisterMappingLabel() throws Throwable { + TraceGuestPlatform amd64; + Register RAX; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regOrigRAX = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[orig_rax]")); + regOrigRAX.insert(Range.atLeast(0L), ConflictResolution.DENY); + b.trace.getMemoryManager() + .createOverlayAddressSpace("Targets[0].Threads[0].Registers", + b.trace.getBaseAddressFactory().getRegisterSpace()); + + amd64 = b.trace.getPlatformManager() + .addGuestPlatform( + getSLEIGH_X86_64_LANGUAGE().getCompilerSpecByID(new CompilerSpecID("gcc"))); + amd64.addMappedRegisterRange(); + RAX = amd64.getLanguage().getRegister("RAX"); + amd64.addRegisterMapOverride(RAX, "orig_rax"); + + regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); + } + + AddressSpace overlaySpace = + b.trace.getBaseAddressFactory().getAddressSpace("Targets[0].Threads[0].Registers"); + TraceMemorySpace regSpace = b.trace.getMemoryManager().getMemorySpace(overlaySpace, false); + assertEquals(BigInteger.valueOf(0x1234), + regSpace.getValue(amd64, 0, RAX).getUnsignedValue()); + } + + @Test + public void testRegisterMappingGuestMemoryMapped() throws Throwable { + TraceGuestPlatform avr8; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regR0 = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[R0]")); + regR0.insert(Range.atLeast(0L), ConflictResolution.DENY); + + avr8 = b.trace.getPlatformManager() + .addGuestPlatform( + getLanguageService().getLanguage(new LanguageID("avr8:LE:16:default")) + .getCompilerSpecByID(new CompilerSpecID("gcc"))); + avr8.addMappedRange(b.addr(0), + avr8.getLanguage().getDefaultDataSpace().getAddress(0), 0x1000); + + regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 1); + regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x12); + } + + assertEquals(BigInteger.valueOf(0x12), b.trace.getMemoryManager() + .getValue(avr8, 0, avr8.getLanguage().getRegister("R0")) + .getUnsignedValue()); + ByteBuffer buf = ByteBuffer.allocate(1); + assertEquals(1, b.trace.getMemoryManager().getBytes(0, b.addr(0), buf)); + assertArrayEquals(b.arr(0x12), buf.array()); + } + + @Test + public void testRegisterMappingGuestMemoryMappedHostOverlay() throws Throwable { + TraceGuestPlatform avr8; + AddressSpace overlay; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regR0 = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[R0]")); + regR0.insert(Range.atLeast(0L), ConflictResolution.DENY); + + overlay = b.trace.getMemoryManager() + .createOverlayAddressSpace("custom", + b.trace.getBaseAddressFactory().getDefaultAddressSpace()); + + avr8 = b.trace.getPlatformManager() + .addGuestPlatform( + getLanguageService().getLanguage(new LanguageID("avr8:LE:16:default")) + .getCompilerSpecByID(new CompilerSpecID("gcc"))); + avr8.addMappedRange(b.addr(overlay, 0), + avr8.getLanguage().getDefaultDataSpace().getAddress(0), 0x1000); + + regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 1); + regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x12); + } + + assertEquals(BigInteger.valueOf(0x12), b.trace.getMemoryManager() + .getValue(avr8, 0, avr8.getLanguage().getRegister("R0")) + .getUnsignedValue()); + ByteBuffer buf = ByteBuffer.allocate(1); + assertEquals(1, b.trace.getMemoryManager().getBytes(0, b.addr(overlay, 0), buf)); + assertArrayEquals(b.arr(0x12), buf.array()); + } + + @Test + public void testRegisterMappingLabelMemoryMapped() throws Throwable { + TraceGuestPlatform avr8; + Register R0; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regR0 = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[orig_r0]")); + regR0.insert(Range.atLeast(0L), ConflictResolution.DENY); + + avr8 = b.trace.getPlatformManager() + .addGuestPlatform( + getLanguageService().getLanguage(new LanguageID("avr8:LE:16:default")) + .getCompilerSpecByID(new CompilerSpecID("gcc"))); + avr8.addMappedRange(b.addr(0), + avr8.getLanguage().getDefaultDataSpace().getAddress(0), 0x1000); + R0 = avr8.getLanguage().getRegister("R0"); + avr8.addRegisterMapOverride(R0, "orig_r0"); + + regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 1); + regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x12); + } + + assertEquals(BigInteger.valueOf(0x12), + b.trace.getMemoryManager().getValue(avr8, 0, R0).getUnsignedValue()); + ByteBuffer buf = ByteBuffer.allocate(1); + assertEquals(1, b.trace.getMemoryManager().getBytes(0, b.addr(0), buf)); + assertArrayEquals(b.arr(0x12), buf.array()); + } + + @Test + public void testAddLabelCopiesRegisterValues() throws Throwable { + TraceGuestPlatform amd64; + Register RAX; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regOrigRAX = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[orig_rax]")); + regOrigRAX.insert(Range.atLeast(0L), ConflictResolution.DENY); + b.trace.getMemoryManager() + .createOverlayAddressSpace("Targets[0].Threads[0].Registers", + b.trace.getBaseAddressFactory().getRegisterSpace()); + + amd64 = b.trace.getPlatformManager() + .addGuestPlatform( + getSLEIGH_X86_64_LANGUAGE().getCompilerSpecByID(new CompilerSpecID("gcc"))); + amd64.addMappedRegisterRange(); + + regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regOrigRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); + + RAX = amd64.getLanguage().getRegister("RAX"); + amd64.addRegisterMapOverride(RAX, "orig_rax"); + } + + AddressSpace overlaySpace = + b.trace.getBaseAddressFactory().getAddressSpace("Targets[0].Threads[0].Registers"); + TraceMemorySpace regSpace = b.trace.getMemoryManager().getMemorySpace(overlaySpace, false); + assertEquals(BigInteger.valueOf(0x1234), + regSpace.getValue(amd64, 0, RAX).getUnsignedValue()); + } + + @Test + public void testAddLabelCopiesRegisterValuesMemoryMapped() throws Throwable { + TraceGuestPlatform avr8; + Register R0; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regR0 = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[orig_r0]")); + regR0.insert(Range.atLeast(0L), ConflictResolution.DENY); + + avr8 = b.trace.getPlatformManager() + .addGuestPlatform( + getLanguageService().getLanguage(new LanguageID("avr8:LE:16:default")) + .getCompilerSpecByID(new CompilerSpecID("gcc"))); + avr8.addMappedRange(b.addr(0), + avr8.getLanguage().getDefaultDataSpace().getAddress(0), 0x1000); + + regR0.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 1); + regR0.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x12); + + R0 = avr8.getLanguage().getRegister("R0"); + avr8.addRegisterMapOverride(R0, "orig_r0"); + } + + assertEquals(BigInteger.valueOf(0x12), + b.trace.getMemoryManager().getValue(avr8, 0, R0).getUnsignedValue()); + ByteBuffer buf = ByteBuffer.allocate(1); + assertEquals(1, b.trace.getMemoryManager().getBytes(0, b.addr(0), buf)); + assertArrayEquals(b.arr(0x12), buf.array()); + } + + @Test + public void testAddGuestMappingCopiesRegisterValues() throws Throwable { + TraceGuestPlatform amd64; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regRAX = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[RAX]")); + regRAX.insert(Range.atLeast(0L), ConflictResolution.DENY); + b.trace.getMemoryManager() + .createOverlayAddressSpace("Targets[0].Threads[0].Registers", + b.trace.getBaseAddressFactory().getRegisterSpace()); + + regRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); + + amd64 = b.trace.getPlatformManager() + .addGuestPlatform( + getSLEIGH_X86_64_LANGUAGE().getCompilerSpecByID(new CompilerSpecID("gcc"))); + amd64.addMappedRegisterRange(); + } + + AddressSpace overlaySpace = + b.trace.getBaseAddressFactory().getAddressSpace("Targets[0].Threads[0].Registers"); + TraceMemorySpace regSpace = b.trace.getMemoryManager().getMemorySpace(overlaySpace, false); + assertEquals(BigInteger.valueOf(0x1234), + regSpace.getValue(amd64, 0, amd64.getLanguage().getRegister("RAX")).getUnsignedValue()); + } + + @Test + public void testAddOverlaySpaceCopiesRegisterValues() throws Throwable { + TraceGuestPlatform amd64; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObject thread = + manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]")); + thread.insert(Range.atLeast(0L), ConflictResolution.DENY); + + TraceObject regRAX = manager.createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers.User[RAX]")); + regRAX.insert(Range.atLeast(0L), ConflictResolution.DENY); + + regRAX.setValue(Range.atLeast(0L), TargetRegister.LENGTH_ATTRIBUTE_NAME, 8); + regRAX.setValue(Range.atLeast(0L), TargetRegister.VALUE_ATTRIBUTE_NAME, 0x1234); + + amd64 = b.trace.getPlatformManager() + .addGuestPlatform( + getSLEIGH_X86_64_LANGUAGE().getCompilerSpecByID(new CompilerSpecID("gcc"))); + amd64.addMappedRegisterRange(); + + b.trace.getMemoryManager() + .createOverlayAddressSpace("Targets[0].Threads[0].Registers", + b.trace.getBaseAddressFactory().getRegisterSpace()); + } + + AddressSpace overlaySpace = + b.trace.getBaseAddressFactory().getAddressSpace("Targets[0].Threads[0].Registers"); + TraceMemorySpace regSpace = b.trace.getMemoryManager().getMemorySpace(overlaySpace, false); + assertEquals(BigInteger.valueOf(0x1234), + regSpace.getValue(amd64, 0, amd64.getLanguage().getRegister("RAX")).getUnsignedValue()); + } + + @Test + public void testPlatformGetConventionalRegisterRange() throws Throwable { + DBTraceGuestPlatform x86; + AddressSpace registers = b.trace.getBaseAddressFactory().getRegisterSpace(); + AddressSpace overlay; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + x86 = b.trace.getPlatformManager() + .addGuestPlatform(b.getCompiler("x86:LE:32:default", "gcc")); + x86.addMappedRegisterRange(); + + overlay = b.trace.getMemoryManager() + .createOverlayAddressSpace("Targets[0].Threads[0].Registers", registers); + } + Register EAX = x86.getLanguage().getRegister("EAX"); + + // TODO: These hardcoded offsets could be fragile + assertEquals(b.range(registers, 0x1104, 0x1107), + x86.getConventionalRegisterRange(registers, EAX)); + assertEquals(b.range(overlay, 0x1104, 0x1107), + x86.getConventionalRegisterRange(overlay, EAX)); + } + + protected static void assertMatches(String path, PathMatcher matcher) { + String message = matcher + " does not match " + path; + assertTrue(message, matcher.matches(PathUtils.parse(path))); + } + + @Test + public void testPlatformGetConventionalRegisterPath() throws Throwable { + DBTraceGuestPlatform x86; + AddressSpace registers = b.trace.getBaseAddressFactory().getRegisterSpace(); + AddressSpace overlay; + Register EAX; + Register EBX; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + x86 = b.trace.getPlatformManager() + .addGuestPlatform(b.getCompiler("x86:LE:32:default", "gcc")); + x86.addMappedRegisterRange(); + EAX = x86.getLanguage().getRegister("EAX"); + EBX = x86.getLanguage().getRegister("EBX"); + + x86.addRegisterMapOverride(EAX, "orig_eax"); + + overlay = b.trace.getMemoryManager() + .createOverlayAddressSpace("Targets[0].Threads[0].Registers", registers); + } + + assertMatches("Targets[0].Threads[0].Registers.User[EBX]", + x86.getConventionalRegisterPath(overlay, EBX)); + assertMatches("Targets[0].Threads[0].Registers.User[orig_eax]", + x86.getConventionalRegisterPath(overlay, EAX)); + assertMatches("Targets[0].Threads[0].Registers.User[r0]", + b.host.getConventionalRegisterPath(overlay, b.reg("r0"))); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTracePlatformManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTracePlatformManagerTest.java index 76604cba67..72c83f69cf 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTracePlatformManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/guest/DBTracePlatformManagerTest.java @@ -243,7 +243,7 @@ public class DBTracePlatformManagerTest extends AbstractGhidraHeadlessIntegratio DBTraceGuestPlatformMappedRange range = guest.addMappedRange(b.addr(0x01000000), b.addr(guest, 0x02000000), 0x1000); assertEquals("Toy:BE:64:default", - range.getHostLanguage().getLanguageID().getIdAsString()); + range.getHostPlatform().getLanguage().getLanguageID().getIdAsString()); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java new file mode 100644 index 0000000000..65ee395f77 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerMemoryTest.java @@ -0,0 +1,973 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import static org.junit.Assert.*; + +import java.nio.ByteBuffer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; + +import org.junit.Test; + +import db.DBHandle; +import ghidra.program.model.address.*; +import ghidra.trace.database.DBTrace; +import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.util.database.DBOpenMode; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.task.ConsoleTaskMonitor; +import ghidra.util.task.TaskMonitor; + +public abstract class AbstractDBTraceMemoryManagerMemoryTest + extends AbstractDBTraceMemoryManagerTest { + + @Test + public void testSetState() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(0, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); + + // +1 Overflow + memory.setState(0, b.range(0x4000, 0x7fffffffffffffffL), TraceMemoryState.KNOWN); + memory.setState(0, b.range(0x4000, 0xffffffffffffffffL), TraceMemoryState.KNOWN); + + // NOP + memory.setState(0, b.addr(0x4500), b.addr(0x5000), TraceMemoryState.KNOWN); + + AddressSet set = new AddressSet(); + set.add(b.addr(0x2000), b.addr(0x2500)); + set.add(b.addr(0x4000), b.addr(0x5000)); // NOP when set + memory.setState(0, set, TraceMemoryState.KNOWN); + + // ERR + AddressSpace regs = b.trace.getBaseAddressFactory().getRegisterSpace(); + try { + memory.setState(0, regs.getAddress(0x4000), TraceMemoryState.KNOWN); + fail(); + } + catch (IllegalArgumentException e) { + // pass; + } + + try { + memory.setState(0, + new AddressRangeImpl(regs.getAddress(0x4000), regs.getAddress(0x5000)), + TraceMemoryState.KNOWN); + fail(); + } + catch (IllegalArgumentException e) { + // pass; + } + } + } + + @Test + public void testSetGetStateOneByte() { + assertEquals(null, memory.getState(3, b.addr(0x4000))); + + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), TraceMemoryState.KNOWN); + } + + assertEquals(TraceMemoryState.KNOWN, memory.getState(3, b.addr(0x4000))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x4001))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x3fff))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(2, b.addr(0x4000))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(4, b.addr(0x4000))); + + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), TraceMemoryState.ERROR); + } + + assertEquals(TraceMemoryState.ERROR, memory.getState(3, b.addr(0x4000))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x4001))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x3fff))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(2, b.addr(0x4000))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(4, b.addr(0x4000))); + } + + @Test + public void testSetRangeGetState() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); + } + + assertEquals(TraceMemoryState.KNOWN, memory.getState(3, b.addr(0x4800))); + assertEquals(TraceMemoryState.KNOWN, memory.getState(3, b.addr(0x4000))); + assertEquals(TraceMemoryState.KNOWN, memory.getState(3, b.addr(0x5000))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x3fff))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x5001))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(2, b.addr(0x4800))); + assertEquals(TraceMemoryState.UNKNOWN, memory.getState(4, b.addr(0x4800))); + } + + @Test + public void testGetMostRecentStateSingleRange() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); + } + + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3fff))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3fff))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x4000))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x3fff))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(4, b.addr(0x4000))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(4, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x5001))); + } + + @Test + public void testGetMostRecentStateSameSnap() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); + memory.setState(3, b.addr(0x4020), b.addr(0x4080), TraceMemoryState.ERROR); + } + + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3fff))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x401f))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4020))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4080))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4081))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3fff))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x4000))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x401f))); + assertSnapState(3, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(3, b.addr(0x4020))); + assertSnapState(3, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(3, b.addr(0x4080))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x4081))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x3fff))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(5, b.addr(0x4000))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(5, b.addr(0x401f))); + assertSnapState(3, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(5, b.addr(0x4020))); + assertSnapState(3, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(5, b.addr(0x4080))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(5, b.addr(0x4081))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(5, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x5001))); + } + + @Test + public void testGetMostRecentStateLaterBefore() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); + memory.setState(4, b.addr(0x3000), b.addr(0x3500), TraceMemoryState.ERROR); + } + + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x2fff))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3500))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3501))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3fff))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x2fff))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3000))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3500))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3501))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3fff))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x4000))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x2fff))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(4, b.addr(0x3000))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(4, b.addr(0x3500))); + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x3501))); + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x3fff))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(4, b.addr(0x4000))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(4, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x2fff))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(5, b.addr(0x3000))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(5, b.addr(0x3500))); + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x3501))); + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x3fff))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(5, b.addr(0x4000))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(5, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x5001))); + } + + @Test + public void testGetMostRecentStateLaterOverStart() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); + memory.setState(4, b.addr(0x3000), b.addr(0x4500), TraceMemoryState.ERROR); + } + + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x2fff))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3fff))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4500))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4501))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x2fff))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3000))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3fff))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x4000))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x4500))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x4501))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(3, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x2fff))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(4, b.addr(0x3000))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(4, b.addr(0x3fff))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(4, b.addr(0x4000))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(4, b.addr(0x4500))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(4, b.addr(0x4501))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(4, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x5001))); + + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0))); + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x2fff))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(5, b.addr(0x3000))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(5, b.addr(0x3fff))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(5, b.addr(0x4000))); + assertSnapState(4, TraceMemoryState.ERROR, + memory.getMostRecentStateEntry(5, b.addr(0x4500))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(5, b.addr(0x4501))); + assertSnapState(3, TraceMemoryState.KNOWN, + memory.getMostRecentStateEntry(5, b.addr(0x5000))); + assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x5001))); + } + + @Test + public void testGetMostRecentStates() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), b.addr(0x7000), TraceMemoryState.KNOWN); + memory.setState(3, b.addr(0x5000), b.addr(0x6000), TraceMemoryState.ERROR); + memory.setState(4, b.addr(0x3000), b.addr(0x4800), TraceMemoryState.KNOWN); + } + + Map expected; + + expected = new HashMap<>(); + assertEquals(expected, + collectAsMap(memory.getMostRecentStates(2, b.range(0x2800, 0x9000)))); + + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4fff), TraceMemoryState.KNOWN); + expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR); + expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN); + assertEquals(expected, + collectAsMap(memory.getMostRecentStates(3, b.range(0x2800, 0x9000)))); + + expected = new HashMap<>(); + expected.put(b.srange(4, 0x3000, 0x4800), TraceMemoryState.KNOWN); + expected.put(b.srange(3, 0x4801, 0x4fff), TraceMemoryState.KNOWN); + expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR); + expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN); + assertEquals(expected, + collectAsMap(memory.getMostRecentStates(4, b.range(0x2800, 0x9000)))); + assertEquals(expected, + collectAsMap(memory.getMostRecentStates(5, b.range(0x2800, 0x9000)))); + } + + @Test + public void testGetAddressesWithState() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), b.addr(0x7000), TraceMemoryState.KNOWN); + memory.setState(3, b.addr(0x5000), b.addr(0x6000), TraceMemoryState.ERROR); + memory.setState(4, b.addr(0x3000), b.addr(0x4800), TraceMemoryState.KNOWN); + } + + AddressSet set = new AddressSet(); + set.add(b.range(0x2800, 0x3800)); + set.add(b.range(0x3c00, 0x4800)); + set.add(b.range(0x4c00, 0x6100)); + set.add(b.range(0x8000, 0x9000)); + + AddressSet expected; + AddressSetView result; + + expected = new AddressSet(); + expected.add(b.range(0x3000, 0x3800)); + expected.add(b.range(0x3c00, 0x4800)); + result = memory.getAddressesWithState(4, set, state -> state == TraceMemoryState.KNOWN); + assertEquals(expected, set.intersect(result)); + + expected = new AddressSet(); + expected.add(b.range(0x4000, 0x4800)); + expected.add(b.range(0x4c00, 0x4fff)); + expected.add(b.range(0x6001, 0x6100)); + result = memory.getAddressesWithState(3, set, state -> state == TraceMemoryState.KNOWN); + assertEquals(expected, set.intersect(result)); + + // Test gaps + expected = new AddressSet(); + expected.add(b.range(0x2800, 0x3800)); + expected.add(b.range(0x3c00, 0x3fff)); + expected.add(b.range(0x8000, 0x9000)); + result = memory.getAddressesWithState(3, set, state -> true); + assertEquals(expected, set.subtract(result)); + } + + @Test + public void testGetStates() { + try (UndoableTransaction tid = b.startTransaction()) { + memory.setState(3, b.addr(0x4000), b.addr(0x7000), TraceMemoryState.KNOWN); + memory.setState(3, b.addr(0x5000), b.addr(0x6000), TraceMemoryState.ERROR); + } + + Map expected; + + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4fff), TraceMemoryState.KNOWN); + expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR); + expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x8000)))); + } + + @Test + public void testBytes0Length() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(0, memory.putBytes(3, b.addr(0x4000), b.buf())); + assertEquals(0, getBlockRecordCount()); + assertEquals(0, getBufferRecordCount()); + } + + assertEquals(0, memory.getBytes(3, b.addr(0x4000), b.buf())); + + ByteBuffer read = b.buf(-1, -2, -3, -4); + assertEquals(4, memory.getBytes(3, b.addr(0x3ffe), read)); + assertEquals(0, read.remaining()); + // NOTE: I think this is OK, because the state ought to be UNKNOWN anyway. + assertArrayEquals(b.arr(-1, -2, -3, -4), read.array()); + } + + @Test + public void testBytesSimple4Zeros() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(0, 0, 0, 0))); + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(0, 0, 0, 0))); // Should have no effect + assertEquals(1, getBlockRecordCount()); + // Zeros do not require buffer backing + assertEquals(0, getBufferRecordCount()); + } + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + + ByteBuffer read = b.buf(-1, -2, -3, -4); // Verify zeros actually written + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(0, 0, 0, 0), read.array()); + } + + @Test + public void testBytesSimple4Bytes() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + } + DBTraceMemoryBufferEntry bufEnt = getBufferStore().getObjectAt(0); + assertFalse(bufEnt.isCompressed()); + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + } + + @Test + public void testBytesSpan4Bytes() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x3ffe), b.buf(1, 2, 3, 4))); + assertEquals(2, getBlockRecordCount()); + assertEquals(2, getBufferRecordCount()); + } + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, memory.getBytes(3, b.addr(0x3ffe), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + } + + @Test + public void testBytesSpan12BytesInChunks() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x3ffa), b.buf(1, 2, 3, 4))); + assertEquals(1, getBlockRecordCount()); + assertEquals(4, memory.putBytes(3, b.addr(0x3ffe), b.buf(5, 6, 7, 8))); + assertEquals(2, getBlockRecordCount()); + assertEquals(4, memory.putBytes(3, b.addr(0x4002), b.buf(9, 10, 11, 12))); + assertEquals(2, getBlockRecordCount()); + assertEquals(2, getBufferRecordCount()); + } + + ByteBuffer read = ByteBuffer.allocate(12); + assertEquals(12, memory.getBytes(3, b.addr(0x3ffa), read)); + assertArrayEquals(b.arr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), read.array()); + } + + @Test + public void testBytesOverflow() { + try (UndoableTransaction tid = b.startTransaction()) { + ByteBuffer write = b.buf(1, 2, 3, 4); + assertEquals(2, memory.putBytes(3, b.addr(0xfffffffffffffffeL), write)); + assertEquals(2, write.remaining()); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + } + + ByteBuffer read = b.buf(-1, -1, -1, -1); + assertEquals(2, memory.getBytes(3, b.addr(0xfffffffffffffffeL), read)); + assertEquals(2, read.remaining()); + assertArrayEquals(b.arr(1, 2, -1, -1), read.array()); + } + + @Test + public void testBytesWriteSameLater() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + + assertEquals(4, memory.putBytes(5, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertEquals(1, getBlockRecordCount()); // Should not require a new block + assertEquals(1, getBufferRecordCount()); // Definitely not another buffer + } + + // verify the corresponding change in state; + Map expected; + + expected = new HashMap<>(); + assertEquals(expected, collectAsMap(memory.getStates(2, b.range(0x3000, 0x5000)))); + assertEquals(expected, collectAsMap(memory.getStates(4, b.range(0x3000, 0x5000)))); + assertEquals(expected, collectAsMap(memory.getStates(6, b.range(0x3000, 0x5000)))); + + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + expected = new HashMap<>(); + expected.put(b.srange(5, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(5, b.range(0x3000, 0x5000)))); + + ByteBuffer read = b.buf(0, 0, 0, 0); + assertEquals(4, memory.getBytes(5, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + } + + @Test + public void testBytesArrayOffset() { + try (UndoableTransaction tid = b.startTransaction()) { + byte[] array = new byte[20]; + array[9] = -1; + array[10] = 1; + array[11] = 2; + array[12] = 3; + array[13] = 4; + array[14] = -1; + assertEquals(4, memory.putBytes(3, b.addr(0x4000), ByteBuffer.wrap(array, 10, 4))); + } + + byte[] array = new byte[20]; + array[9] = -2; + array[16] = -2; + ByteBuffer read = ByteBuffer.wrap(array, 10, 6); + assertEquals(6, memory.getBytes(3, b.addr(0x3fff), read)); + assertArrayEquals(b.arr(-2, 0, 1, 2, 3, 4, 0, -2), Arrays.copyOfRange(array, 9, 17)); + } + + @Test + public void testGetBytesMostRecent() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertEquals(4, memory.putBytes(4, b.addr(0x4002), b.buf(5, 6, 7, 8))); + assertEquals(1, memory.putBytes(5, b.addr(0x4003), b.buf(0))); + assertEquals(3, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + } + + ByteBuffer read = b.buf(-1, -1, -1, -1); + assertEquals(4, memory.getBytes(2, b.addr(0x4000), read)); + assertArrayEquals(b.arr(-1, -1, -1, -1), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(4, b.addr(0x4002), read)); + assertArrayEquals(b.arr(5, 6, 7, 8), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(4, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 5, 6), read.array()); + + read = ByteBuffer.allocate(10); + assertEquals(10, memory.getBytes(5, b.addr(0x3ffe), read)); + assertArrayEquals(b.arr(0, 0, 1, 2, 5, 0, 7, 8, 0, 0), read.array()); + } + + @Test + public void testPutBytesIntoPastGetBytesMostRecent() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(4, b.addr(0x4800), b.buf(5, 6, 7, 8))); + assertEquals(4, memory.putBytes(3, b.addr(0x4802), b.buf(1, 2, 3, 4))); + assertEquals(10, + memory.putBytes(2, b.addr(0x47fe), b.buf(9, 10, 11, 12, 13, 14, 15, 16, 17, 18))); + assertEquals(3, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + } + + ByteBuffer read = b.buf(-1, -1, -1, -1); + assertEquals(4, memory.getBytes(1, b.addr(0x4802), read)); + assertArrayEquals(b.arr(-1, -1, -1, -1), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(3, b.addr(0x4802), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(4, b.addr(0x4800), read)); + assertArrayEquals(b.arr(5, 6, 7, 8), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(4, b.addr(0x4802), read)); + assertArrayEquals(b.arr(7, 8, 3, 4), read.array()); + + read = ByteBuffer.allocate(14); + assertEquals(14, memory.getBytes(4, b.addr(0x47fc), read)); + assertArrayEquals(b.arr(0, 0, 9, 10, 5, 6, 7, 8, 3, 4, 17, 18, 0, 0), read.array()); + } + + @Test + public void testGetBytesCrossScratch() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(Long.MIN_VALUE, b.addr(0x4000), b.buf(1, 2, 3, 4))); + } + + ByteBuffer read = b.buf(-1, -1, -1, -1); + assertEquals(4, memory.getBytes(1, b.addr(0x4000), read)); + assertArrayEquals(b.arr(-1, -1, -1, -1), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(-1, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + } + + @Test + public void testPutBytesPackGetBytes() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + memory.pack(); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + } + DBTraceMemoryBufferEntry bufEnt = getBufferStore().getObjectAt(0); + assertTrue(bufEnt.isCompressed()); + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + + assertTrue(bufEnt.isCompressed()); + } + + @Test + public void testPutBytesPackPutBytes() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + memory.pack(); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + DBTraceMemoryBufferEntry bufEnt = getBufferStore().getObjectAt(0); + assertTrue(bufEnt.isCompressed()); + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertFalse(bufEnt.isCompressed()); // TODO: This is an implementation quirk. Do I care? + } + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + } + + @Test + public void testFindBytes() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(5, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4, 5))); + } + + try { + memory.findBytes(3, b.range(0x4000, 0x4003), b.buf(1, 2, 3, 4), b.buf(-1, -1, -1), + true, TaskMonitor.DUMMY); + } + catch (IllegalArgumentException e) { + // pass + } + + // Degenerate + assertNull( + memory.findBytes(2, b.range(0x4000, 0x4003), b.buf(), b.buf(), + true, TaskMonitor.DUMMY)); + + // Too soon + assertNull( + memory.findBytes(2, b.range(0x4000, 0x4003), b.buf(1, 2, 3, 4), b.buf(-1, -1, -1, -1), + true, TaskMonitor.DUMMY)); + + // Too small + assertNull( + memory.findBytes(3, b.range(0x4000, 0x4002), b.buf(1, 2, 3, 4), b.buf(-1, -1, -1, -1), + true, TaskMonitor.DUMMY)); + + // Too high + assertNull( + memory.findBytes(3, b.range(0x4001, 0x4004), b.buf(1, 2, 3, 4), b.buf(-1, -1, -1, -1), + true, TaskMonitor.DUMMY)); + + // Too high, into unknown + assertNull( + memory.findBytes(3, b.range(0x4001, 0x4005), b.buf(1, 2, 3, 4, 5), + b.buf(-1, -1, -1, -1, -1), true, TaskMonitor.DUMMY)); + + // Too low + assertNull( + memory.findBytes(3, b.range(0x3fff, 0x4002), b.buf(1, 2, 3, 4), b.buf(-1, -1, -1, -1), + true, TaskMonitor.DUMMY)); + + // Perfect match + assertEquals(b.addr(0x4000), + memory.findBytes(3, b.range(0x4000, 0x4003), b.buf(1, 2, 3, 4), b.buf(-1, -1, -1, -1), + true, TaskMonitor.DUMMY)); + + // Make it work for the match + assertEquals(b.addr(0x4000), + memory.findBytes(3, b.range(0x0, -1), b.buf(1, 2, 3, 4), b.buf(-1, -1, -1, -1), + true, TaskMonitor.DUMMY)); + + // Make it work for the match + assertEquals(b.addr(0x4000), + memory.findBytes(3, b.range(0x0, -1), b.buf(1), b.buf(-1), + true, TaskMonitor.DUMMY)); + + // Sub match + assertEquals(b.addr(0x4001), + memory.findBytes(3, b.range(0x4000, 0x4003), b.buf(2, 3, 4), b.buf(-1, -1, -1), + true, TaskMonitor.DUMMY)); + } + + @Test + public void testRemoveBytes() { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(10, + memory.putBytes(2, b.addr(0x47fe), b.buf(9, 10, 11, 12, 13, 14, 15, 16, 17, 18))); + assertEquals(4, memory.putBytes(4, b.addr(0x4800), b.buf(5, 6, 7, 8))); + assertEquals(4, memory.putBytes(3, b.addr(0x4802), b.buf(1, 2, 3, 4))); + assertEquals(3, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + /* 0x fe ff 00 01 02 03 04 05 06 07 + * + * 4: 5 6 7 8 + * 3: 1 2 3 4 + * 2: 9 10 11 12 13 14 15 16 17 18 + */ + + memory.removeBytes(2, b.addr(0x47ff), 14); + /* 0x fe ff 00 01 02 03 04 05 06 07 + * + * 4: 5 6 7 8 + * 3: 1 2 3 4 + * 2: 9 .. .. .. .. .. .. .. .. .. + */ + } + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, memory.getBytes(1, b.addr(0x4802), read)); + assertArrayEquals(b.arr(0, 0, 0, 0), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(3, b.addr(0x4802), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(4, b.addr(0x4800), read)); + assertArrayEquals(b.arr(5, 6, 7, 8), read.array()); + + read.position(0); + assertEquals(4, memory.getBytes(4, b.addr(0x4802), read)); + assertArrayEquals(b.arr(7, 8, 3, 4), read.array()); + + read = ByteBuffer.allocate(14); + assertEquals(14, memory.getBytes(4, b.addr(0x47fc), read)); + assertArrayEquals(b.arr(0, 0, 9, 0, 5, 6, 7, 8, 3, 4, 0, 0, 0, 0), read.array()); + + // Check overall effect on state + Map expected; + expected = new HashMap<>(); + expected.put(b.srange(2, 0x47fe, 0x47fe), TraceMemoryState.KNOWN); + expected.put(b.srange(4, 0x4800, 0x4803), TraceMemoryState.KNOWN); + expected.put(b.srange(3, 0x4804, 0x4805), TraceMemoryState.KNOWN); + assertEquals(expected, + collectAsMap(memory.getMostRecentStates(6, b.range(0x4700, 0x4900)))); + } + + @Test + public void testSaveAndLoad() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + } + + Path tmp = Files.createTempFile("test", ".db"); + Files.delete(tmp); // saveAs must create the file + b.trace.getDBHandle().saveAs(tmp.toFile(), false, new ConsoleTaskMonitor()); + + DBHandle opened = new DBHandle(tmp.toFile()); + DBTrace restored = null; + try { + restored = new DBTrace(opened, DBOpenMode.UPDATE, new ConsoleTaskMonitor(), this); + + DBTraceMemorySpace rSpace = + restored.getMemoryManager().getMemorySpace(b.language.getDefaultDataSpace(), true); + assertEquals(1, rSpace.bufferStore.getRecordCount()); + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, rSpace.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + } + finally { + if (restored != null) { + restored.release(this); + } + } + } + + @Test + public void testAddButAbortedStillEmpty() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + + tid.abort(); + } + assertEquals(0, getBlockRecordCount()); + assertEquals(0, getBufferRecordCount()); + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(0, 0, 0, 0), read.array()); + } + + @Test + public void testAddThenUndo() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + } + b.trace.undo(); + + assertEquals(0, getBlockRecordCount()); + assertEquals(0, getBufferRecordCount()); + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(0, 0, 0, 0), read.array()); + } + + @Test + public void testAddThenUndoThenRedo() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + assertEquals(4, memory.putBytes(3, b.addr(0x4000), b.buf(1, 2, 3, 4))); + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + } + b.trace.undo(); + + assertEquals(0, getBlockRecordCount()); + assertEquals(0, getBufferRecordCount()); + + // verify the corresponding change in state; + Map expected; + expected = new HashMap<>(); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + + ByteBuffer read = ByteBuffer.allocate(4); + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(0, 0, 0, 0), read.array()); + + b.trace.redo(); + + assertEquals(1, getBlockRecordCount()); + assertEquals(1, getBufferRecordCount()); + + expected = new HashMap<>(); + expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); + assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); + + read.position(0); + assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + } + + @Test + public void testHighBlockNumbers() throws Exception { + /** + * This test gets down into the block buffer implementation. If the block number exceeds + * 127, if might (accidentally) get treated as negative. Thanks Java! We can create this + * situation by writing to the same b.address at more than 127 different snaps. + */ + + try (UndoableTransaction tid = b.startTransaction()) { + for (int i = 0; i < 300; i++) { + memory.putBytes(i, b.addr(0x4000), b.buf(1, 2, 3, i % 256)); + } + } + + for (int i = 0; i < 300; i++) { + ByteBuffer buf = ByteBuffer.allocate(4); + memory.getBytes(i, b.addr(0x4000), buf); + assertArrayEquals(b.arr(1, 2, 3, i % 256), buf.array()); + } + } + + @Test + public void testOverlaySpaces() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + AddressSpace os = memory.createOverlayAddressSpace("test", + b.trace.getBaseAddressFactory().getDefaultAddressSpace()); + DBTraceMemorySpace space = memory.getMemorySpace(os, true); + assertEquals(4, space.putBytes(0, os.getAddress(0x4000), b.buf(1, 2, 3, 4))); + + ByteBuffer read = ByteBuffer.allocate(4); + // This is from original space, not overlay, so should be 0s + assertEquals(4, memory.getBytes(0, b.addr(0x4000), read)); + assertArrayEquals(b.arr(0, 0, 0, 0), read.array()); + read.clear(); + + assertEquals(4, space.getBytes(0, os.getAddress(0x4000), read)); + assertArrayEquals(b.arr(1, 2, 3, 4), read.array()); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegistersTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegistersTest.java new file mode 100644 index 0000000000..1202f2ca5d --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegistersTest.java @@ -0,0 +1,177 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import static org.junit.Assert.*; + +import java.math.BigInteger; + +import org.junit.Test; + +import ghidra.program.model.lang.*; +import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.stack.TraceStack; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.database.UndoableTransaction; + +public abstract class AbstractDBTraceMemoryManagerRegistersTest + extends AbstractDBTraceMemoryManagerTest { + + protected TraceThread getOrAddThread(String name, long creationSnap) { + return b.getOrAddThread(name, creationSnap); + } + + protected abstract boolean isRegistersPerFrame(); + + @Test + public void testRegisters() throws Exception { + Register r0 = b.language.getRegister("r0"); + Register r0h = b.language.getRegister("r0h"); + Register r0l = b.language.getRegister("r0l"); + + TraceThread thread; + try (UndoableTransaction tid = b.startTransaction()) { + thread = getOrAddThread("Threads[1]", 0); + DBTraceMemorySpace regs = memory.getMemoryRegisterSpace(thread, true); + + regs.setValue(0, new RegisterValue(r0, new BigInteger("0123456789ABCDEF", 16))); + assertEquals(new BigInteger("0123456789ABCDEF", 16), + regs.getValue(0, r0).getUnsignedValue()); + + regs.setValue(0, new RegisterValue(r0h, new BigInteger("76543210", 16))); + assertEquals(new BigInteger("7654321089ABCDEF", 16), + regs.getValue(0, r0).getUnsignedValue()); + assertEquals(new BigInteger("76543210", 16), regs.getValue(0, r0h).getUnsignedValue()); + assertEquals(new BigInteger("89ABCDEF", 16), regs.getValue(0, r0l).getUnsignedValue()); + + regs.setValue(0, new RegisterValue(r0l, new BigInteger("FEDCBA98", 16))); + assertEquals(new BigInteger("76543210FEDCBA98", 16), + regs.getValue(0, r0).getUnsignedValue()); + assertEquals(new BigInteger("76543210", 16), regs.getValue(0, r0h).getUnsignedValue()); + assertEquals(new BigInteger("FEDCBA98", 16), regs.getValue(0, r0l).getUnsignedValue()); + + TraceStack stack = b.trace.getStackManager().getStack(thread, 0, true); + stack.setDepth(2, true); + assertSame(regs, memory.getMemoryRegisterSpace(stack.getFrame(0, false), false)); + DBTraceMemorySpace frame = + memory.getMemoryRegisterSpace(stack.getFrame(1, false), true); + if (isRegistersPerFrame()) { + assertNotSame(regs, frame); + } + else { + assertSame(regs, frame); + } + + frame.setValue(0, new RegisterValue(r0, new BigInteger("1032547698BADCFE", 16))); + assertEquals(new BigInteger("1032547698BADCFE", 16), + frame.getValue(0, r0).getUnsignedValue()); + } + } + + /** + * This has to be called by the sub-class, having created a trace with the Toy:??:32:builder + * language. + */ + protected void runTestRegisterBits(TracePlatform platform) throws Exception { + Language language = platform.getLanguage(); + Register contextreg = language.getRegister("contextreg"); + Register fctx = language.getRegister("fctx"); + Register nfctx = language.getRegister("nfctx"); + Register phase = language.getRegister("phase"); + Register counter = language.getRegister("counter"); + + TraceThread thread; + try (UndoableTransaction tid = b.startTransaction()) { + thread = getOrAddThread("Threads[1]", 0); + waitForSwing(); + DBTraceMemorySpace regs = memory.getMemoryRegisterSpace(thread, true); + + regs.setValue(platform, 0, new RegisterValue(fctx, BigInteger.valueOf(0xa))); + assertEquals(BigInteger.valueOf(0xa), + regs.getValue(platform, 0, fctx).getUnsignedValue()); + + regs.setValue(platform, 0, new RegisterValue(nfctx, BigInteger.valueOf(0xb))); + assertEquals(BigInteger.valueOf(0xb), + regs.getValue(platform, 0, nfctx).getUnsignedValue()); + assertEquals(BigInteger.valueOf(0xa), + regs.getValue(platform, 0, fctx).getUnsignedValue()); + + regs.setValue(platform, 0, new RegisterValue(phase, BigInteger.valueOf(0x3))); + assertEquals(BigInteger.valueOf(0x3), + regs.getValue(platform, 0, phase).getUnsignedValue()); + assertEquals(BigInteger.valueOf(0), + regs.getValue(platform, 0, counter).getUnsignedValue()); + + regs.setValue(platform, 0, new RegisterValue(counter, BigInteger.valueOf(0xf))); + assertEquals(BigInteger.valueOf(0xf), + regs.getValue(platform, 0, counter).getUnsignedValue()); + + assertEquals("abfc000000000000", + regs.getValue(platform, 0, contextreg).getUnsignedValue().toString(16)); + + regs.setValue(platform, 0, new RegisterValue(fctx, BigInteger.valueOf(0x5))); + assertEquals("5bfc000000000000", + regs.getValue(platform, 0, contextreg).getUnsignedValue().toString(16)); + } + } + + /** + * This test is based on the MWE submitted in GitHub issue #2760. + */ + @Test + public void testManyStateEntries() throws Exception { + Register pc = b.language.getRegister("pc"); + TraceThread thread; + try (UndoableTransaction tid = b.startTransaction()) { + thread = getOrAddThread("Threads[1]", 0); + DBTraceMemorySpace regs = memory.getMemoryRegisterSpace(thread, true); + + for (int i = 1; i < 2000; i++) { + //System.err.println("Snap " + i); + regs.setState(i, pc, TraceMemoryState.KNOWN); + //regs.stateMapSpace.checkIntegrity(); + } + } + } + + protected void runTestGuestMappingRegisterBits(LanguageID langID, CompilerSpecID cSpecID) + throws Throwable { + TraceGuestPlatform guest; + try (UndoableTransaction tid = b.startTransaction()) { + guest = b.trace.getPlatformManager() + .addGuestPlatform( + getLanguageService().getLanguage(langID).getCompilerSpecByID(cSpecID)); + guest.addMappedRegisterRange(); + } + runTestRegisterBits(guest); + } + + @Test + // Test both BE and LE guest on both BE and LE hosts + public void testGuestMappingBERegisterBits() throws Throwable { + runTestGuestMappingRegisterBits(new LanguageID("Toy:BE:32:builder"), + new CompilerSpecID("default")); + } + + @Test + // Test both BE and LE guest on both BE and LE hosts + public void testGuestMappingLERegisterBits() throws Throwable { + runTestGuestMappingRegisterBits(new LanguageID("Toy:LE:32:builder"), + new CompilerSpecID("default")); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java index f9728c11cb..a828e4099e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java @@ -18,29 +18,20 @@ package ghidra.trace.database.memory; import static org.junit.Assert.*; import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; +import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; import org.junit.*; -import db.DBHandle; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.*; +import ghidra.program.model.lang.LanguageID; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; -import ghidra.trace.database.DBTrace; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.memory.TraceMemoryState; -import ghidra.trace.model.stack.TraceStack; -import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.LanguageTestWatcher; -import ghidra.util.database.*; -import ghidra.util.task.ConsoleTaskMonitor; -import ghidra.util.task.TaskMonitor; +import ghidra.util.database.DBCachedObjectStore; +import ghidra.util.database.UndoableTransaction; public abstract class AbstractDBTraceMemoryManagerTest extends AbstractGhidraHeadlessIntegrationTest { @@ -67,347 +58,12 @@ public abstract class AbstractDBTraceMemoryManagerTest b.close(); } - @Test - public void testSetState() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(0, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); - - // +1 Overflow - memory.setState(0, b.range(0x4000, 0x7fffffffffffffffL), TraceMemoryState.KNOWN); - memory.setState(0, b.range(0x4000, 0xffffffffffffffffL), TraceMemoryState.KNOWN); - - // NOP - memory.setState(0, b.addr(0x4500), b.addr(0x5000), TraceMemoryState.KNOWN); - - AddressSet set = new AddressSet(); - set.add(b.addr(0x2000), b.addr(0x2500)); - set.add(b.addr(0x4000), b.addr(0x5000)); // NOP when set - memory.setState(0, set, TraceMemoryState.KNOWN); - - // ERR - AddressSpace regs = b.trace.getBaseAddressFactory().getRegisterSpace(); - try { - memory.setState(0, regs.getAddress(0x4000), TraceMemoryState.KNOWN); - fail(); - } - catch (IllegalArgumentException e) { - // pass; - } - - try { - memory.setState(0, - new AddressRangeImpl(regs.getAddress(0x4000), regs.getAddress(0x5000)), - TraceMemoryState.KNOWN); - fail(); - } - catch (IllegalArgumentException e) { - // pass; - } - } - } - - @Test - public void testSetGetStateOneByte() { - assertEquals(null, memory.getState(3, b.addr(0x4000))); - - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), TraceMemoryState.KNOWN); - } - - assertEquals(TraceMemoryState.KNOWN, memory.getState(3, b.addr(0x4000))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x4001))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x3fff))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(2, b.addr(0x4000))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(4, b.addr(0x4000))); - - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), TraceMemoryState.ERROR); - } - - assertEquals(TraceMemoryState.ERROR, memory.getState(3, b.addr(0x4000))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x4001))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x3fff))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(2, b.addr(0x4000))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(4, b.addr(0x4000))); - } - - @Test - public void testSetRangeGetState() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); - } - - assertEquals(TraceMemoryState.KNOWN, memory.getState(3, b.addr(0x4800))); - assertEquals(TraceMemoryState.KNOWN, memory.getState(3, b.addr(0x4000))); - assertEquals(TraceMemoryState.KNOWN, memory.getState(3, b.addr(0x5000))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x3fff))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(3, b.addr(0x5001))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(2, b.addr(0x4800))); - assertEquals(TraceMemoryState.UNKNOWN, memory.getState(4, b.addr(0x4800))); - } - protected static void assertSnapState(long snap, TraceMemoryState state, Entry entry) { assertEquals(snap, entry.getKey().getY1().longValue()); assertEquals(state, entry.getValue()); } - @Test - public void testGetMostRecentStateSingleRange() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); - } - - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3fff))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3fff))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x4000))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x3fff))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(4, b.addr(0x4000))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(4, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x5001))); - } - - @Test - public void testGetMostRecentStateSameSnap() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); - memory.setState(3, b.addr(0x4020), b.addr(0x4080), TraceMemoryState.ERROR); - } - - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3fff))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x401f))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4020))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4080))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4081))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3fff))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x4000))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x401f))); - assertSnapState(3, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(3, b.addr(0x4020))); - assertSnapState(3, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(3, b.addr(0x4080))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x4081))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x3fff))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(5, b.addr(0x4000))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(5, b.addr(0x401f))); - assertSnapState(3, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(5, b.addr(0x4020))); - assertSnapState(3, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(5, b.addr(0x4080))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(5, b.addr(0x4081))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(5, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x5001))); - } - - @Test - public void testGetMostRecentStateLaterBefore() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); - memory.setState(4, b.addr(0x3000), b.addr(0x3500), TraceMemoryState.ERROR); - } - - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x2fff))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3500))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3501))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3fff))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x2fff))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3000))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3500))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3501))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3fff))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x4000))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x2fff))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(4, b.addr(0x3000))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(4, b.addr(0x3500))); - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x3501))); - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x3fff))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(4, b.addr(0x4000))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(4, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x2fff))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(5, b.addr(0x3000))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(5, b.addr(0x3500))); - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x3501))); - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x3fff))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(5, b.addr(0x4000))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(5, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x5001))); - } - - @Test - public void testGetMostRecentStateLaterOverStart() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), b.addr(0x5000), TraceMemoryState.KNOWN); - memory.setState(4, b.addr(0x3000), b.addr(0x4500), TraceMemoryState.ERROR); - } - - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x2fff))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x3fff))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4500))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x4501))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(2, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x2fff))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3000))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x3fff))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x4000))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x4500))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x4501))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(3, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(3, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x2fff))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(4, b.addr(0x3000))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(4, b.addr(0x3fff))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(4, b.addr(0x4000))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(4, b.addr(0x4500))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(4, b.addr(0x4501))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(4, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(4, b.addr(0x5001))); - - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0))); - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x2fff))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(5, b.addr(0x3000))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(5, b.addr(0x3fff))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(5, b.addr(0x4000))); - assertSnapState(4, TraceMemoryState.ERROR, - memory.getMostRecentStateEntry(5, b.addr(0x4500))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(5, b.addr(0x4501))); - assertSnapState(3, TraceMemoryState.KNOWN, - memory.getMostRecentStateEntry(5, b.addr(0x5000))); - assertEquals(null, memory.getMostRecentStateEntry(5, b.addr(0x5001))); - } - - @Test - public void testGetAddressesWithState() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), b.addr(0x7000), TraceMemoryState.KNOWN); - memory.setState(3, b.addr(0x5000), b.addr(0x6000), TraceMemoryState.ERROR); - memory.setState(4, b.addr(0x3000), b.addr(0x4800), TraceMemoryState.KNOWN); - } - - AddressSet set = new AddressSet(); - set.add(b.range(0x2800, 0x3800)); - set.add(b.range(0x3c00, 0x4800)); - set.add(b.range(0x4c00, 0x6100)); - set.add(b.range(0x8000, 0x9000)); - - AddressSet expected; - AddressSetView result; - - expected = new AddressSet(); - expected.add(b.range(0x3000, 0x3800)); - expected.add(b.range(0x3c00, 0x4800)); - result = memory.getAddressesWithState(4, set, state -> state == TraceMemoryState.KNOWN); - assertEquals(expected, set.intersect(result)); - - expected = new AddressSet(); - expected.add(b.range(0x4000, 0x4800)); - expected.add(b.range(0x4c00, 0x4fff)); - expected.add(b.range(0x6001, 0x6100)); - result = memory.getAddressesWithState(3, set, state -> state == TraceMemoryState.KNOWN); - assertEquals(expected, set.intersect(result)); - - // Test gaps - expected = new AddressSet(); - expected.add(b.range(0x2800, 0x3800)); - expected.add(b.range(0x3c00, 0x3fff)); - expected.add(b.range(0x8000, 0x9000)); - result = memory.getAddressesWithState(3, set, state -> true); - assertEquals(expected, set.subtract(result)); - } - - @Test - public void testGetStates() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), b.addr(0x7000), TraceMemoryState.KNOWN); - memory.setState(3, b.addr(0x5000), b.addr(0x6000), TraceMemoryState.ERROR); - } - - Map expected; - - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4fff), TraceMemoryState.KNOWN); - expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR); - expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x8000)))); - } - protected static Map collectAsMap( Iterable> it) { Map result = new HashMap<>(); @@ -419,50 +75,6 @@ public abstract class AbstractDBTraceMemoryManagerTest return result; } - @Test - public void testGetMostRecentStates() { - try (UndoableTransaction tid = b.startTransaction()) { - memory.setState(3, b.addr(0x4000), b.addr(0x7000), TraceMemoryState.KNOWN); - memory.setState(3, b.addr(0x5000), b.addr(0x6000), TraceMemoryState.ERROR); - memory.setState(4, b.addr(0x3000), b.addr(0x4800), TraceMemoryState.KNOWN); - } - - Map expected; - - expected = new HashMap<>(); - assertEquals(expected, - collectAsMap(memory.getMostRecentStates(2, b.range(0x2800, 0x9000)))); - - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4fff), TraceMemoryState.KNOWN); - expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR); - expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN); - assertEquals(expected, - collectAsMap(memory.getMostRecentStates(3, b.range(0x2800, 0x9000)))); - - expected = new HashMap<>(); - expected.put(b.srange(4, 0x3000, 0x4800), TraceMemoryState.KNOWN); - expected.put(b.srange(3, 0x4801, 0x4fff), TraceMemoryState.KNOWN); - expected.put(b.srange(3, 0x5000, 0x6000), TraceMemoryState.ERROR); - expected.put(b.srange(3, 0x6001, 0x7000), TraceMemoryState.KNOWN); - assertEquals(expected, - collectAsMap(memory.getMostRecentStates(4, b.range(0x2800, 0x9000)))); - assertEquals(expected, - collectAsMap(memory.getMostRecentStates(5, b.range(0x2800, 0x9000)))); - } - - protected byte[] arr(int... e) { - byte[] result = new byte[e.length]; - for (int i = 0; i < e.length; i++) { - result[i] = (byte) e[i]; - } - return result; - } - - protected ByteBuffer buf(int... e) { - return ByteBuffer.wrap(arr(e)); - } - protected int getBlockRecordCount() { DBTraceMemorySpace space = memory.getForSpace(b.language.getDefaultSpace(), false); if (space == null) { @@ -487,669 +99,4 @@ public abstract class AbstractDBTraceMemoryManagerTest return bufferStore.getRecordCount(); } - @Test - public void testBytes0Length() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(0, memory.putBytes(3, b.addr(0x4000), buf())); - assertEquals(0, getBlockRecordCount()); - assertEquals(0, getBufferRecordCount()); - } - - assertEquals(0, memory.getBytes(3, b.addr(0x4000), buf())); - - ByteBuffer read = buf(-1, -2, -3, -4); - assertEquals(4, memory.getBytes(3, b.addr(0x3ffe), read)); - assertEquals(0, read.remaining()); - // NOTE: I think this is OK, because the state ought to be UNKNOWN anyway. - assertArrayEquals(arr(-1, -2, -3, -4), read.array()); - } - - @Test - public void testBytesSimple4Zeros() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(0, 0, 0, 0))); - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(0, 0, 0, 0))); // Should have no effect - assertEquals(1, getBlockRecordCount()); - // Zeros do not require buffer backing - assertEquals(0, getBufferRecordCount()); - } - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - - ByteBuffer read = buf(-1, -2, -3, -4); // Verify zeros actually written - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(0, 0, 0, 0), read.array()); - } - - @Test - public void testBytesSimple4Bytes() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - } - DBTraceMemoryBufferEntry bufEnt = getBufferStore().getObjectAt(0); - assertFalse(bufEnt.isCompressed()); - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - } - - @Test - public void testBytesSpan4Bytes() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x3ffe), buf(1, 2, 3, 4))); - assertEquals(2, getBlockRecordCount()); - assertEquals(2, getBufferRecordCount()); - } - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, memory.getBytes(3, b.addr(0x3ffe), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - } - - @Test - public void testBytesSpan12BytesInChunks() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x3ffa), buf(1, 2, 3, 4))); - assertEquals(1, getBlockRecordCount()); - assertEquals(4, memory.putBytes(3, b.addr(0x3ffe), buf(5, 6, 7, 8))); - assertEquals(2, getBlockRecordCount()); - assertEquals(4, memory.putBytes(3, b.addr(0x4002), buf(9, 10, 11, 12))); - assertEquals(2, getBlockRecordCount()); - assertEquals(2, getBufferRecordCount()); - } - - ByteBuffer read = ByteBuffer.allocate(12); - assertEquals(12, memory.getBytes(3, b.addr(0x3ffa), read)); - assertArrayEquals(arr(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), read.array()); - } - - @Test - public void testBytesOverflow() { - try (UndoableTransaction tid = b.startTransaction()) { - ByteBuffer write = buf(1, 2, 3, 4); - assertEquals(2, memory.putBytes(3, b.addr(0xfffffffffffffffeL), write)); - assertEquals(2, write.remaining()); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - } - - ByteBuffer read = buf(-1, -1, -1, -1); - assertEquals(2, memory.getBytes(3, b.addr(0xfffffffffffffffeL), read)); - assertEquals(2, read.remaining()); - assertArrayEquals(arr(1, 2, -1, -1), read.array()); - } - - @Test - public void testBytesWriteSameLater() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - - assertEquals(4, memory.putBytes(5, b.addr(0x4000), buf(1, 2, 3, 4))); - assertEquals(1, getBlockRecordCount()); // Should not require a new block - assertEquals(1, getBufferRecordCount()); // Definitely not another buffer - } - - // verify the corresponding change in state; - Map expected; - - expected = new HashMap<>(); - assertEquals(expected, collectAsMap(memory.getStates(2, b.range(0x3000, 0x5000)))); - assertEquals(expected, collectAsMap(memory.getStates(4, b.range(0x3000, 0x5000)))); - assertEquals(expected, collectAsMap(memory.getStates(6, b.range(0x3000, 0x5000)))); - - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - expected = new HashMap<>(); - expected.put(b.srange(5, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(5, b.range(0x3000, 0x5000)))); - - ByteBuffer read = buf(0, 0, 0, 0); - assertEquals(4, memory.getBytes(5, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - } - - @Test - public void testBytesArrayOffset() { - try (UndoableTransaction tid = b.startTransaction()) { - byte[] array = new byte[20]; - array[9] = -1; - array[10] = 1; - array[11] = 2; - array[12] = 3; - array[13] = 4; - array[14] = -1; - assertEquals(4, memory.putBytes(3, b.addr(0x4000), ByteBuffer.wrap(array, 10, 4))); - } - - byte[] array = new byte[20]; - array[9] = -2; - array[16] = -2; - ByteBuffer read = ByteBuffer.wrap(array, 10, 6); - assertEquals(6, memory.getBytes(3, b.addr(0x3fff), read)); - assertArrayEquals(arr(-2, 0, 1, 2, 3, 4, 0, -2), Arrays.copyOfRange(array, 9, 17)); - } - - @Test - public void testGetBytesMostRecent() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - assertEquals(4, memory.putBytes(4, b.addr(0x4002), buf(5, 6, 7, 8))); - assertEquals(1, memory.putBytes(5, b.addr(0x4003), buf(0))); - assertEquals(3, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - } - - ByteBuffer read = buf(-1, -1, -1, -1); - assertEquals(4, memory.getBytes(2, b.addr(0x4000), read)); - assertArrayEquals(arr(-1, -1, -1, -1), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(4, b.addr(0x4002), read)); - assertArrayEquals(arr(5, 6, 7, 8), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(4, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 5, 6), read.array()); - - read = ByteBuffer.allocate(10); - assertEquals(10, memory.getBytes(5, b.addr(0x3ffe), read)); - assertArrayEquals(arr(0, 0, 1, 2, 5, 0, 7, 8, 0, 0), read.array()); - } - - @Test - public void testPutBytesIntoPastGetBytesMostRecent() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(4, b.addr(0x4800), buf(5, 6, 7, 8))); - assertEquals(4, memory.putBytes(3, b.addr(0x4802), buf(1, 2, 3, 4))); - assertEquals(10, - memory.putBytes(2, b.addr(0x47fe), buf(9, 10, 11, 12, 13, 14, 15, 16, 17, 18))); - assertEquals(3, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - } - - ByteBuffer read = buf(-1, -1, -1, -1); - assertEquals(4, memory.getBytes(1, b.addr(0x4802), read)); - assertArrayEquals(arr(-1, -1, -1, -1), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(3, b.addr(0x4802), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(4, b.addr(0x4800), read)); - assertArrayEquals(arr(5, 6, 7, 8), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(4, b.addr(0x4802), read)); - assertArrayEquals(arr(7, 8, 3, 4), read.array()); - - read = ByteBuffer.allocate(14); - assertEquals(14, memory.getBytes(4, b.addr(0x47fc), read)); - assertArrayEquals(arr(0, 0, 9, 10, 5, 6, 7, 8, 3, 4, 17, 18, 0, 0), read.array()); - } - - @Test - public void testGetBytesCrossScratch() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(Long.MIN_VALUE, b.addr(0x4000), buf(1, 2, 3, 4))); - } - - ByteBuffer read = buf(-1, -1, -1, -1); - assertEquals(4, memory.getBytes(1, b.addr(0x4000), read)); - assertArrayEquals(arr(-1, -1, -1, -1), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(-1, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - } - - @Test - public void testPutBytesPackGetBytes() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - memory.pack(); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - } - DBTraceMemoryBufferEntry bufEnt = getBufferStore().getObjectAt(0); - assertTrue(bufEnt.isCompressed()); - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - - assertTrue(bufEnt.isCompressed()); - } - - @Test - public void testPutBytesPackPutBytes() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - memory.pack(); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - DBTraceMemoryBufferEntry bufEnt = getBufferStore().getObjectAt(0); - assertTrue(bufEnt.isCompressed()); - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - assertFalse(bufEnt.isCompressed()); // TODO: This is an implementation quirk. Do I care? - } - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - } - - @Test - public void testFindBytes() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(5, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4, 5))); - } - - try { - memory.findBytes(3, b.range(0x4000, 0x4003), buf(1, 2, 3, 4), buf(-1, -1, -1), - true, TaskMonitor.DUMMY); - } - catch (IllegalArgumentException e) { - // pass - } - - // Degenerate - assertNull( - memory.findBytes(2, b.range(0x4000, 0x4003), buf(), buf(), - true, TaskMonitor.DUMMY)); - - // Too soon - assertNull( - memory.findBytes(2, b.range(0x4000, 0x4003), buf(1, 2, 3, 4), buf(-1, -1, -1, -1), - true, TaskMonitor.DUMMY)); - - // Too small - assertNull( - memory.findBytes(3, b.range(0x4000, 0x4002), buf(1, 2, 3, 4), buf(-1, -1, -1, -1), - true, TaskMonitor.DUMMY)); - - // Too high - assertNull( - memory.findBytes(3, b.range(0x4001, 0x4004), buf(1, 2, 3, 4), buf(-1, -1, -1, -1), - true, TaskMonitor.DUMMY)); - - // Too high, into unknown - assertNull( - memory.findBytes(3, b.range(0x4001, 0x4005), buf(1, 2, 3, 4, 5), - buf(-1, -1, -1, -1, -1), - true, TaskMonitor.DUMMY)); - - // Too low - assertNull( - memory.findBytes(3, b.range(0x3fff, 0x4002), buf(1, 2, 3, 4), buf(-1, -1, -1, -1), - true, TaskMonitor.DUMMY)); - - // Perfect match - assertEquals(b.addr(0x4000), - memory.findBytes(3, b.range(0x4000, 0x4003), buf(1, 2, 3, 4), buf(-1, -1, -1, -1), - true, TaskMonitor.DUMMY)); - - // Make it work for the match - assertEquals(b.addr(0x4000), - memory.findBytes(3, b.range(0x0, -1), buf(1, 2, 3, 4), buf(-1, -1, -1, -1), - true, TaskMonitor.DUMMY)); - - // Make it work for the match - assertEquals(b.addr(0x4000), - memory.findBytes(3, b.range(0x0, -1), buf(1), buf(-1), - true, TaskMonitor.DUMMY)); - - // Sub match - assertEquals(b.addr(0x4001), - memory.findBytes(3, b.range(0x4000, 0x4003), buf(2, 3, 4), buf(-1, -1, -1), - true, TaskMonitor.DUMMY)); - } - - @Test - public void testRemoveBytes() { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(10, - memory.putBytes(2, b.addr(0x47fe), buf(9, 10, 11, 12, 13, 14, 15, 16, 17, 18))); - assertEquals(4, memory.putBytes(4, b.addr(0x4800), buf(5, 6, 7, 8))); - assertEquals(4, memory.putBytes(3, b.addr(0x4802), buf(1, 2, 3, 4))); - assertEquals(3, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - /* 0x fe ff 00 01 02 03 04 05 06 07 - * - * 4: 5 6 7 8 - * 3: 1 2 3 4 - * 2: 9 10 11 12 13 14 15 16 17 18 - */ - - memory.removeBytes(2, b.addr(0x47ff), 14); - /* 0x fe ff 00 01 02 03 04 05 06 07 - * - * 4: 5 6 7 8 - * 3: 1 2 3 4 - * 2: 9 .. .. .. .. .. .. .. .. .. - */ - } - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, memory.getBytes(1, b.addr(0x4802), read)); - assertArrayEquals(arr(0, 0, 0, 0), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(3, b.addr(0x4802), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(4, b.addr(0x4800), read)); - assertArrayEquals(arr(5, 6, 7, 8), read.array()); - - read.position(0); - assertEquals(4, memory.getBytes(4, b.addr(0x4802), read)); - assertArrayEquals(arr(7, 8, 3, 4), read.array()); - - read = ByteBuffer.allocate(14); - assertEquals(14, memory.getBytes(4, b.addr(0x47fc), read)); - assertArrayEquals(arr(0, 0, 9, 0, 5, 6, 7, 8, 3, 4, 0, 0, 0, 0), read.array()); - - // Check overall effect on state - Map expected; - expected = new HashMap<>(); - expected.put(b.srange(2, 0x47fe, 0x47fe), TraceMemoryState.KNOWN); - expected.put(b.srange(4, 0x4800, 0x4803), TraceMemoryState.KNOWN); - expected.put(b.srange(3, 0x4804, 0x4805), TraceMemoryState.KNOWN); - assertEquals(expected, - collectAsMap(memory.getMostRecentStates(6, b.range(0x4700, 0x4900)))); - } - - @Test - public void testSaveAndLoad() throws Exception { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - } - - Path tmp = Files.createTempFile("test", ".db"); - Files.delete(tmp); // saveAs must create the file - b.trace.getDBHandle().saveAs(tmp.toFile(), false, new ConsoleTaskMonitor()); - - DBHandle opened = new DBHandle(tmp.toFile()); - DBTrace restored = null; - try { - restored = new DBTrace(opened, DBOpenMode.UPDATE, new ConsoleTaskMonitor(), this); - - DBTraceMemorySpace rSpace = - restored.getMemoryManager().getMemorySpace(b.language.getDefaultDataSpace(), true); - assertEquals(1, rSpace.bufferStore.getRecordCount()); - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, rSpace.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - } - finally { - if (restored != null) { - restored.release(this); - } - } - } - - @Test - public void testAddButAbortedStillEmpty() throws Exception { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - - tid.abort(); - } - assertEquals(0, getBlockRecordCount()); - assertEquals(0, getBufferRecordCount()); - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(0, 0, 0, 0), read.array()); - } - - @Test - public void testAddThenUndo() throws Exception { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - } - b.trace.undo(); - - assertEquals(0, getBlockRecordCount()); - assertEquals(0, getBufferRecordCount()); - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(0, 0, 0, 0), read.array()); - } - - @Test - public void testAddThenUndoThenRedo() throws Exception { - try (UndoableTransaction tid = b.startTransaction()) { - assertEquals(4, memory.putBytes(3, b.addr(0x4000), buf(1, 2, 3, 4))); - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - } - b.trace.undo(); - - assertEquals(0, getBlockRecordCount()); - assertEquals(0, getBufferRecordCount()); - - // verify the corresponding change in state; - Map expected; - expected = new HashMap<>(); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - - ByteBuffer read = ByteBuffer.allocate(4); - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(0, 0, 0, 0), read.array()); - - b.trace.redo(); - - assertEquals(1, getBlockRecordCount()); - assertEquals(1, getBufferRecordCount()); - - expected = new HashMap<>(); - expected.put(b.srange(3, 0x4000, 0x4003), TraceMemoryState.KNOWN); - assertEquals(expected, collectAsMap(memory.getStates(3, b.range(0x3000, 0x5000)))); - - read.position(0); - assertEquals(4, memory.getBytes(3, b.addr(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - } - - @Test - public void testHighBlockNumbers() throws Exception { - /** - * This test gets down into the block buffer implementation. If the block number exceeds - * 127, if might (accidentally) get treated as negative. Thanks Java! We can create this - * situation by writing to the same b.address at more than 127 different snaps. - */ - - try (UndoableTransaction tid = b.startTransaction()) { - for (int i = 0; i < 300; i++) { - memory.putBytes(i, b.addr(0x4000), buf(1, 2, 3, i % 256)); - } - } - - for (int i = 0; i < 300; i++) { - ByteBuffer buf = ByteBuffer.allocate(4); - memory.getBytes(i, b.addr(0x4000), buf); - assertArrayEquals(arr(1, 2, 3, i % 256), buf.array()); - } - } - - @Test - public void testRegisters() throws Exception { - Register r0 = b.language.getRegister("r0"); - Register r0h = b.language.getRegister("r0h"); - Register r0l = b.language.getRegister("r0l"); - - TraceThread thread; - try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread1", 0); - DBTraceMemorySpace regs = memory.getMemoryRegisterSpace(thread, true); - - regs.setValue(0, new RegisterValue(r0, new BigInteger("0123456789ABCDEF", 16))); - assertEquals(new BigInteger("0123456789ABCDEF", 16), - regs.getValue(0, r0).getUnsignedValue()); - - regs.setValue(0, new RegisterValue(r0h, new BigInteger("76543210", 16))); - assertEquals(new BigInteger("7654321089ABCDEF", 16), - regs.getValue(0, r0).getUnsignedValue()); - assertEquals(new BigInteger("76543210", 16), regs.getValue(0, r0h).getUnsignedValue()); - assertEquals(new BigInteger("89ABCDEF", 16), regs.getValue(0, r0l).getUnsignedValue()); - - regs.setValue(0, new RegisterValue(r0l, new BigInteger("FEDCBA98", 16))); - assertEquals(new BigInteger("76543210FEDCBA98", 16), - regs.getValue(0, r0).getUnsignedValue()); - assertEquals(new BigInteger("76543210", 16), regs.getValue(0, r0h).getUnsignedValue()); - assertEquals(new BigInteger("FEDCBA98", 16), regs.getValue(0, r0l).getUnsignedValue()); - - TraceStack stack = b.trace.getStackManager().getStack(thread, 0, true); - stack.setDepth(2, true); - assertEquals(regs, memory.getMemoryRegisterSpace(stack.getFrame(0, false), false)); - DBTraceMemorySpace frame = - memory.getMemoryRegisterSpace(stack.getFrame(1, false), true); - assertNotEquals(regs, frame); - - frame.setValue(0, new RegisterValue(r0, new BigInteger("1032547698BADCFE", 16))); - assertEquals(new BigInteger("1032547698BADCFE", 16), - frame.getValue(0, r0).getUnsignedValue()); - } - } - - /** - * This has to be called by the sub-class, having created a trace with the Toy:??:32:builder - * language. - */ - protected void runTestRegisterBits() throws Exception { - Register contextreg = b.language.getRegister("contextreg"); - Register fctx = b.language.getRegister("fctx"); - Register nfctx = b.language.getRegister("nfctx"); - Register phase = b.language.getRegister("phase"); - Register counter = b.language.getRegister("counter"); - - TraceThread thread; - try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread1", 0); - DBTraceMemorySpace regs = memory.getMemoryRegisterSpace(thread, true); - - regs.setValue(0, new RegisterValue(fctx, BigInteger.valueOf(0xa))); - assertEquals(BigInteger.valueOf(0xa), regs.getValue(0, fctx).getUnsignedValue()); - - regs.setValue(0, new RegisterValue(nfctx, BigInteger.valueOf(0xb))); - assertEquals(BigInteger.valueOf(0xb), regs.getValue(0, nfctx).getUnsignedValue()); - assertEquals(BigInteger.valueOf(0xa), regs.getValue(0, fctx).getUnsignedValue()); - - regs.setValue(0, new RegisterValue(phase, BigInteger.valueOf(0x3))); - assertEquals(BigInteger.valueOf(0x3), regs.getValue(0, phase).getUnsignedValue()); - assertEquals(BigInteger.valueOf(0), regs.getValue(0, counter).getUnsignedValue()); - - regs.setValue(0, new RegisterValue(counter, BigInteger.valueOf(0xf))); - assertEquals(BigInteger.valueOf(0xf), regs.getValue(0, counter).getUnsignedValue()); - - assertEquals(BigInteger.valueOf(0xabfc).shiftLeft(48), - regs.getValue(0, contextreg).getUnsignedValue()); - - regs.setValue(0, new RegisterValue(fctx, BigInteger.valueOf(0x5))); - assertEquals(BigInteger.valueOf(0x5bfc).shiftLeft(48), - regs.getValue(0, contextreg).getUnsignedValue()); - } - } - - /** - * This test is based on the MWE submitted in GitHub issue #2760. - */ - @Test - public void testManyStateEntries() throws Exception { - Register pc = b.language.getRegister("pc"); - TraceThread thread; - try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread1", 0); - DBTraceMemorySpace regs = memory.getMemoryRegisterSpace(thread, true); - - for (int i = 1; i < 2000; i++) { - //System.err.println("Snap " + i); - regs.setState(i, pc, TraceMemoryState.KNOWN); - //regs.stateMapSpace.checkIntegrity(); - } - } - } - - @Test - public void testOverlaySpaces() throws Exception { - try (UndoableTransaction tid = b.startTransaction()) { - AddressSpace os = memory.createOverlayAddressSpace("test", - b.trace.getBaseAddressFactory().getDefaultAddressSpace()); - DBTraceMemorySpace space = memory.getMemorySpace(os, true); - assertEquals(4, space.putBytes(0, os.getAddress(0x4000), buf(1, 2, 3, 4))); - - ByteBuffer read = ByteBuffer.allocate(4); - // This is from original space, not overlay, so should be 0s - assertEquals(4, memory.getBytes(0, b.addr(0x4000), read)); - assertArrayEquals(arr(0, 0, 0, 0), read.array()); - read.clear(); - - assertEquals(4, space.getBytes(0, os.getAddress(0x4000), read)); - assertArrayEquals(arr(1, 2, 3, 4), read.array()); - } - } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerMemoryBETest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerMemoryBETest.java new file mode 100644 index 0000000000..52be9e8161 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerMemoryBETest.java @@ -0,0 +1,25 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import ghidra.program.model.lang.LanguageID; + +public class DBTraceMemoryManagerMemoryBETest extends AbstractDBTraceMemoryManagerMemoryTest { + @Override + protected LanguageID getLanguageID() { + return new LanguageID("Toy:BE:64:default"); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerMemoryLETest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerMemoryLETest.java new file mode 100644 index 0000000000..dbca101125 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerMemoryLETest.java @@ -0,0 +1,25 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import ghidra.program.model.lang.LanguageID; + +public class DBTraceMemoryManagerMemoryLETest extends AbstractDBTraceMemoryManagerMemoryTest { + @Override + protected LanguageID getLanguageID() { + return new LanguageID("Toy:LE:64:default"); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegionsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegionsTest.java index 44d196e40b..6e93bf26fe 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegionsTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegionsTest.java @@ -28,19 +28,20 @@ public class DBTraceMemoryManagerObjectRegionsTest extends DBTraceMemoryManagerR @Before public void setUpObjectsMode() throws Exception { - ctx = XmlSchemaContext.deserialize("" + // - "" + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - " " + // - ""); + ctx = XmlSchemaContext.deserialize(""" + + + + + + + + + + + + """); try (UndoableTransaction tid = b.startTransaction()) { b.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegistersPerFrameBETest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegistersPerFrameBETest.java new file mode 100644 index 0000000000..ad1036d769 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegistersPerFrameBETest.java @@ -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.trace.database.memory; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.util.PathUtils; +import ghidra.program.model.lang.LanguageID; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.LanguageTestWatcher.TestLanguage; +import ghidra.util.database.UndoableTransaction; + +public class DBTraceMemoryManagerObjectRegistersPerFrameBETest + extends AbstractDBTraceMemoryManagerRegistersTest { + + protected SchemaContext ctx; + + @Before + public void setUpObjectsMode() throws Exception { + ctx = XmlSchemaContext.deserialize(""" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """); + + try (UndoableTransaction tid = b.startTransaction()) { + b.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } + + @Override + protected TraceThread getOrAddThread(String name, long creationSnap) { + TraceThread thread = super.getOrAddThread(name, creationSnap); + TraceObject obj = ((TraceObjectThread) thread).getObject(); + TraceObject objRegs = b.trace.getObjectManager() + .createObject(obj.getCanonicalPath().extend(PathUtils.parse("Stack[0]"))); + objRegs.insert(Range.all(), ConflictResolution.DENY); + return thread; + } + + @Override + protected LanguageID getLanguageID() { + return new LanguageID("Toy:BE:64:default"); + } + + @Override + protected boolean isRegistersPerFrame() { + return true; + } + + @Test + @TestLanguage("Toy:BE:32:builder") + public void testRegisterBits() throws Exception { + runTestRegisterBits(b.host); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegistersPerThreadLETest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegistersPerThreadLETest.java new file mode 100644 index 0000000000..1f5c245fd1 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegistersPerThreadLETest.java @@ -0,0 +1,113 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.program.model.lang.LanguageID; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.LanguageTestWatcher.TestLanguage; +import ghidra.util.database.UndoableTransaction; + +public class DBTraceMemoryManagerObjectRegistersPerThreadLETest + extends AbstractDBTraceMemoryManagerRegistersTest { + + protected SchemaContext ctx; + + @Before + public void setUpObjectsMode() throws Exception { + ctx = XmlSchemaContext.deserialize(""" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """); + + try (UndoableTransaction tid = b.startTransaction()) { + b.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } + + @Override + protected TraceThread getOrAddThread(String name, long creationSnap) { + TraceThread thread = super.getOrAddThread(name, creationSnap); + TraceObject obj = ((TraceObjectThread) thread).getObject(); + TraceObject objRegs = b.trace.getObjectManager() + .createObject(obj.getCanonicalPath().key("Registers")); + objRegs.insert(Range.all(), ConflictResolution.DENY); + return thread; + } + + @Override + protected LanguageID getLanguageID() { + return new LanguageID("Toy:LE:64:default"); + } + + @Override + protected boolean isRegistersPerFrame() { + return false; + } + + @Test + @TestLanguage("Toy:LE:32:builder") + public void testRegisterBits() throws Exception { + runTestRegisterBits(b.host); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerBETest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegistersBETest.java similarity index 82% rename from Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerBETest.java rename to Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegistersBETest.java index 1da55d140d..094d7222fa 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerBETest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegistersBETest.java @@ -20,15 +20,22 @@ import org.junit.Test; import ghidra.program.model.lang.LanguageID; import ghidra.trace.util.LanguageTestWatcher.TestLanguage; -public class DBTraceMemoryManagerBETest extends AbstractDBTraceMemoryManagerTest { +public class DBTraceMemoryManagerRegistersBETest + extends AbstractDBTraceMemoryManagerRegistersTest { + @Override protected LanguageID getLanguageID() { return new LanguageID("Toy:BE:64:default"); } - @TestLanguage("Toy:BE:32:builder") + @Override + protected boolean isRegistersPerFrame() { + return true; + } + @Test + @TestLanguage("Toy:BE:32:builder") public void testRegisterBits() throws Exception { - runTestRegisterBits(); + runTestRegisterBits(b.host); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerLETest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegistersLETest.java similarity index 82% rename from Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerLETest.java rename to Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegistersLETest.java index c508a22125..48e8658742 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerLETest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegistersLETest.java @@ -20,15 +20,22 @@ import org.junit.Test; import ghidra.program.model.lang.LanguageID; import ghidra.trace.util.LanguageTestWatcher.TestLanguage; -public class DBTraceMemoryManagerLETest extends AbstractDBTraceMemoryManagerTest { +public class DBTraceMemoryManagerRegistersLETest + extends AbstractDBTraceMemoryManagerRegistersTest { + @Override protected LanguageID getLanguageID() { return new LanguageID("Toy:LE:64:default"); } - @TestLanguage("Toy:LE:32:builder") + @Override + protected boolean isRegistersPerFrame() { + return true; + } + @Test + @TestLanguage("Toy:LE:32:builder") public void testRegisterBits() throws Exception { - runTestRegisterBits(); + runTestRegisterBits(b.host); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerObjectTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerObjectTest.java index f0aa194104..b212a7c9cc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerObjectTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerObjectTest.java @@ -18,8 +18,8 @@ package ghidra.trace.database.module; import org.junit.Before; import ghidra.dbg.target.schema.SchemaContext; -import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.util.database.UndoableTransaction; public class DBTraceModuleManagerObjectTest extends DBTraceModuleManagerTest { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerObjectTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerObjectTest.java index aa26f98f41..fffb556336 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerObjectTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerObjectTest.java @@ -18,8 +18,8 @@ package ghidra.trace.database.stack; import org.junit.Before; import ghidra.dbg.target.schema.SchemaContext; -import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; import ghidra.util.database.UndoableTransaction; public class DBTraceStackManagerObjectTest extends DBTraceStackManagerTest { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java index 35e89715b7..999c9a97cc 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java @@ -54,6 +54,7 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT + @@ -61,6 +62,27 @@ public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationT + + + + + + + + + + + + + + + + + + + """; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/options/AutoOptionsListener.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/options/AutoOptionsListener.java index ea7bf226db..ad040f0711 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/options/AutoOptionsListener.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/options/AutoOptionsListener.java @@ -21,7 +21,6 @@ import java.util.function.BiFunction; import org.apache.commons.lang3.StringUtils; -import ghidra.framework.options.*; import ghidra.framework.options.AutoOptions.*; import ghidra.framework.options.annotation.AutoOptionConsumed; import ghidra.framework.options.annotation.AutoOptionDefined; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/util/AutoServiceListener.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/util/AutoServiceListener.java index d81e5dfa52..837118e3a7 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/util/AutoServiceListener.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/framework/plugintool/util/AutoServiceListener.java @@ -20,7 +20,6 @@ import java.util.*; import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; -import ghidra.framework.plugintool.util.ServiceListener; import ghidra.util.Msg; public class AutoServiceListener implements ServiceListener { diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java index aaab335aee..7f7263d4b4 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java @@ -22,6 +22,7 @@ import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; import ghidra.program.model.mem.MemBuffer; /** @@ -44,12 +45,18 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { */ public ThreadPcodeExecutorState(PcodeExecutorState sharedState, PcodeExecutorState localState) { + assert Objects.equals(sharedState.getLanguage(), localState.getLanguage()); assert Objects.equals(sharedState.getArithmetic(), localState.getArithmetic()); this.sharedState = sharedState; this.localState = localState; this.arithmetic = sharedState.getArithmetic(); } + @Override + public Language getLanguage() { + return sharedState.getLanguage(); + } + @Override public PcodeArithmetic getArithmetic() { return arithmetic; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java index 1c962ccfe5..199df80b2c 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java @@ -42,8 +42,8 @@ import ghidra.program.model.lang.Language; * guess (or keep up to date) the concrete state piece classes to instantiate. * *

- * The factory itself should be a singleton object. See the Taint Analyzer for a complete solution - * using this interface. + * The factory itself should be a singleton object. See the Taint Analyzer for a complete example + * solution using this interface. * * @param the type of auxiliary values */ diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java index 783c589cc1..b140b94270 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java @@ -127,6 +127,11 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece uniqueSpace = language.getAddressFactory().getUniqueSpace(); } + @Override + public Language getLanguage() { + return language; + } + @Override public PcodeArithmetic getAddressArithmetic() { return addressArithmetic; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java index f3bab1d65d..441297c372 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java @@ -21,6 +21,7 @@ import java.util.Map; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; import ghidra.program.model.mem.MemBuffer; /** @@ -35,6 +36,7 @@ import ghidra.program.model.mem.MemBuffer; */ public class AddressOfPcodeExecutorStatePiece implements PcodeExecutorStatePiece { + private final Language language; private final BytesPcodeArithmetic addressArithmetic; private final Map unique = new HashMap<>(); @@ -43,8 +45,14 @@ public class AddressOfPcodeExecutorStatePiece * * @param isBigEndian true if the control language is big endian */ - public AddressOfPcodeExecutorStatePiece(boolean isBigEndian) { - this.addressArithmetic = BytesPcodeArithmetic.forEndian(isBigEndian); + public AddressOfPcodeExecutorStatePiece(Language language) { + this.language = language; + this.addressArithmetic = BytesPcodeArithmetic.forEndian(language.isBigEndian()); + } + + @Override + public Language getLanguage() { + return language; } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java index 4ad1f72643..97816cae00 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java @@ -554,7 +554,10 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra Type opType = getOperandType(); @SuppressWarnings({ "unchecked", "rawtypes" }) Class> cls = (Class) this.getClass(); - Set methods = CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls)); + Set methods; + synchronized (CACHE_BY_CLASS) { + methods = CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls)); + } for (Method m : methods) { ops.put(m.getName(), AnnotatedPcodeUseropDefinition .create(m.getAnnotation(PcodeUserop.class), this, opType, lookup, m)); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java index 384e4f3299..984f362baa 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java @@ -119,7 +119,7 @@ public class BytesPcodeExecutorStateSpace { return result; } - protected Set getRegs(AddressSet set) { + protected Set getRegs(AddressSetView set) { Set regs = new TreeSet<>(); for (AddressRange rng : set) { Register r = language.getRegister(rng.getMinAddress(), (int) rng.getLength()); @@ -133,7 +133,7 @@ public class BytesPcodeExecutorStateSpace { return regs; } - protected void warnAddressSet(String message, AddressSet set) { + protected void warnAddressSet(String message, AddressSetView set) { Set regs = getRegs(set); if (regs.isEmpty()) { Msg.warn(this, message + ": " + set); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java index 13ddd407f3..346ca0341f 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java @@ -18,6 +18,7 @@ package ghidra.pcode.exec; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; import ghidra.program.model.mem.MemBuffer; /** @@ -45,6 +46,11 @@ public class DefaultPcodeExecutorState implements PcodeExecutorState { this(piece, piece.getArithmetic()); } + @Override + public Language getLanguage() { + return piece.getLanguage(); + } + @Override public T getVar(AddressSpace space, T offset, int size, boolean quantize) { return piece.getVar(space, offset, size, quantize); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java index 6f153bf73b..9721cc00ec 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java @@ -20,6 +20,7 @@ import org.apache.commons.lang3.tuple.Pair; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; import ghidra.program.model.mem.MemBuffer; /** @@ -71,6 +72,11 @@ public class PairedPcodeExecutorState implements PcodeExecutorState(left.getArithmetic(), right.getArithmetic())); } + @Override + public Language getLanguage() { + return piece.getLanguage(); + } + @Override public PcodeArithmetic> getArithmetic() { return arithmetic; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java index 11831a7e52..ec1ee002cf 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java @@ -20,6 +20,7 @@ import org.apache.commons.lang3.tuple.Pair; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; import ghidra.program.model.mem.MemBuffer; /** @@ -70,6 +71,11 @@ public class PairedPcodeExecutorStatePiece new PairedPcodeArithmetic<>(left.getArithmetic(), right.getArithmetic())); } + @Override + public Language getLanguage() { + return left.getLanguage(); + } + @Override public PcodeArithmetic getAddressArithmetic() { return addressArithmetic; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java index 4d8a06958a..3c66f8ff7e 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java @@ -17,6 +17,7 @@ package ghidra.pcode.exec; import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.pcode.Varnode; @@ -55,6 +56,13 @@ public interface PcodeExecutorStatePiece { } } + /** + * Get the language defining the address spaces of this state piece + * + * @return the language + */ + Language getLanguage(); + /** * Get the arithmetic used to manipulate addresses of the type used by this state * diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/mem/MemBufferAdapter.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/mem/MemBufferAdapter.java index 724ceda515..99150d530c 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/mem/MemBufferAdapter.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/mem/MemBufferAdapter.java @@ -21,9 +21,6 @@ import java.nio.ByteOrder; import org.apache.commons.lang3.ArrayUtils; -import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.mem.MemoryAccessException; - public interface MemBufferAdapter extends MemBuffer { int getBytes(ByteBuffer buffer, int addressOffset); diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java index 191b0640df..09f9b56669 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java @@ -17,12 +17,11 @@ package ghidra.pcode.emu.taint; import org.apache.commons.lang3.tuple.Pair; -import ghidra.app.plugin.core.debug.service.emulation.ReadsTargetMemoryPcodeExecutorStatePiece; -import ghidra.app.plugin.core.debug.service.emulation.ReadsTargetRegistersPcodeExecutorStatePiece; +import ghidra.app.plugin.core.debug.service.emulation.RWTargetMemoryPcodeExecutorStatePiece; +import ghidra.app.plugin.core.debug.service.emulation.RWTargetRegistersPcodeExecutorStatePiece; import ghidra.pcode.emu.*; import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; -import ghidra.pcode.emu.taint.full.TaintDebuggerPcodeExecutorState; import ghidra.pcode.emu.taint.plain.TaintPcodeExecutorState; import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorState; import ghidra.pcode.exec.*; @@ -56,7 +55,7 @@ import ghidra.taint.model.TaintVec; *

* * @@ -101,8 +100,8 @@ public enum TaintPartsFactory implements AuxDebuggerEmulatorPartsFactory - * We have no thread-specific userops to add, which means we also have no need to stubs, so here - * we just return the empty library. + * We have no thread-specific userops to add, which means we also have no need for stubs, so + * here we just return the empty library. */ @Override public PcodeUseropLibrary> createLocalUseropStub( @@ -202,16 +201,17 @@ public enum TaintPartsFactory implements AuxDebuggerEmulatorPartsFactory * If you're following the {@link ghidra.taint} package documentation, please finish reading * about the stand-alone and trace-integrated emulators before proceeding to this - * Debugger-integrated part. This part extends the shared state of the trace-integrated emulator - * so that it can also deserialize taint sets from mapped static programs. Since taint is not - * generally a concept understood by a live debugger, we need not retrieve anything (other than - * concrete state) from the target. + * Debugger-integrated part. This part uses the same shared state as the trace-integrated + * emulator but takes a different data access shim, so that it can also deserialize taint sets + * from mapped static programs. Since taint is not generally a concept understood by a live + * debugger, we need not retrieve anything new from the target. Note that the shim is passed to + * us implicitly via the {@code concrete} state. */ @Override public TracePcodeExecutorState> createDebuggerSharedState( AuxDebuggerPcodeEmulator emulator, - ReadsTargetMemoryPcodeExecutorStatePiece concrete) { - return new TaintDebuggerPcodeExecutorState(concrete); + RWTargetMemoryPcodeExecutorStatePiece concrete) { + return new TaintTracePcodeExecutorState(concrete); } /** @@ -221,16 +221,16 @@ public enum TaintPartsFactory implements AuxDebuggerEmulatorPartsFactory> createDebuggerLocalState( AuxDebuggerPcodeEmulator emulator, PcodeThread> emuThread, - ReadsTargetRegistersPcodeExecutorStatePiece concrete) { + RWTargetRegistersPcodeExecutorStatePiece concrete) { return new TaintTracePcodeExecutorState(concrete); } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java index 761015bb48..056c8c59bd 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java @@ -15,14 +15,12 @@ */ package ghidra.pcode.emu.taint.full; -import ghidra.app.services.TraceRecorder; -import ghidra.framework.plugintool.PluginTool; +import ghidra.app.plugin.core.debug.service.emulation.data.*; import ghidra.pcode.emu.taint.TaintPartsFactory; import ghidra.pcode.emu.taint.plain.TaintPcodeEmulator; import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; import ghidra.taint.model.TaintVec; -import ghidra.trace.model.Trace; /** * A Debugger-integrated emulator with taint analysis @@ -31,14 +29,10 @@ public class TaintDebuggerPcodeEmulator extends AuxDebuggerPcodeEmulator create(PluginTool tool, Trace trace, long snap, - TraceRecorder recorder) { - return new TaintDebuggerPcodeEmulator(tool, trace, snap, recorder); + public DebuggerPcodeMachine create(PcodeDebuggerAccess data) { + return new TaintDebuggerPcodeEmulator(data); } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorState.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorState.java deleted file mode 100644 index 4fc2e7441e..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorState.java +++ /dev/null @@ -1,53 +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.emu.taint.full; - -import ghidra.app.plugin.core.debug.service.emulation.ReadsTargetMemoryPcodeExecutorStatePiece; -import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorState; - -/** - * A paired concrete-plus-taint Debugger-integrated state - * - *

- * This contains the emulator's machine state along with the taint markings, just like - * {@link TaintTracePcodeExecutorState}, except that it can also read state from mapped static - * programs. In reality, this just composes concrete and taint state pieces, which actually do all - * the work. - */ -public class TaintDebuggerPcodeExecutorState extends TaintTracePcodeExecutorState { - - /** - * Create a state from the two given pieces - * - * @param concrete the concrete piece - * @param the taint piece - */ - public TaintDebuggerPcodeExecutorState(ReadsTargetMemoryPcodeExecutorStatePiece concrete, - TaintDebuggerPcodeExecutorStatePiece taint) { - super(concrete, taint); - } - - /** - * Create a state from the given concrete piece and an internally constructed taint piece - * - * @param concrete the concrete piece - */ - public TaintDebuggerPcodeExecutorState(ReadsTargetMemoryPcodeExecutorStatePiece concrete) { - super(concrete, new TaintDebuggerPcodeExecutorStatePiece( - concrete.getTool(), concrete.getTrace(), concrete.getSnap(), concrete.getThread(), - concrete.getFrame(), concrete.getRecorder())); - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorStatePiece.java deleted file mode 100644 index 8a85f4dace..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorStatePiece.java +++ /dev/null @@ -1,87 +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.emu.taint.full; - -import ghidra.app.services.TraceRecorder; -import ghidra.framework.plugintool.PluginTool; -import ghidra.pcode.emu.taint.trace.*; -import ghidra.program.model.address.AddressSpace; -import ghidra.trace.model.Trace; -import ghidra.trace.model.property.TracePropertyMapSpace; -import ghidra.trace.model.thread.TraceThread; - -/** - * The Debugger-integrated state piece for holding taint marks - * - *

- * Because we don't require a derivative of this class, it is not split into abstract and - * non-abstract classes (like its super-classes were). This substitutes {@link TaintDebuggerSpace} - * for {@link TaintTraceSpace} and introduces parameters for loading information from mapped static - * programs. We take the recorder more as a matter of form, since we don't really need it. - */ -public class TaintDebuggerPcodeExecutorStatePiece - extends AbstractTaintTracePcodeExecutorStatePiece { - - protected final PluginTool tool; - protected final TraceRecorder recorder; - - /** - * Create the taint piece - * - * @param tool the tool that created the emulator - * @param trace the trace from which to load taint marks - * @param snap the snap from which to load taint marks - * @param thread if a register space, the thread from which to load taint marks - * @param frame if a register space, the frame - * @param recorder if applicable, the recorder for the trace's live target - */ - public TaintDebuggerPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap, - TraceThread thread, int frame, TraceRecorder recorder) { - super(trace, snap, thread, frame); - this.tool = tool; - this.recorder = recorder; - } - - /** - * {@inheritDoc} - * - *

- * Here we create a map that uses {@link TaintDebuggerSpace}s. There is some repeated code with - * {@link TaintTracePcodeExecutorStatePiece#newSpaceMap()}. We could factor that, but I thought - * it a little pedantic. - */ - @Override - protected AbstractSpaceMap newSpaceMap() { - return new CacheingSpaceMap, TaintDebuggerSpace>() { - @Override - protected TracePropertyMapSpace getBacking(AddressSpace space) { - if (map == null) { - return null; - } - if (space.isRegisterSpace()) { - return map.getPropertyMapRegisterSpace(thread, frame, false); - } - return map.getPropertyMapSpace(space, false); - } - - @Override - protected TaintDebuggerSpace newSpace(AddressSpace space, - TracePropertyMapSpace backing) { - return new TaintDebuggerSpace(tool, trace, space, backing, snap); - } - }; - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerSpace.java deleted file mode 100644 index 1074e8619b..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerSpace.java +++ /dev/null @@ -1,90 +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.emu.taint.full; - -import com.google.common.collect.Range; - -import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.framework.plugintool.PluginTool; -import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; -import ghidra.pcode.emu.taint.trace.TaintTraceSpace; -import ghidra.program.model.address.AddressSpace; -import ghidra.program.model.util.StringPropertyMap; -import ghidra.program.util.ProgramLocation; -import ghidra.taint.model.TaintSet; -import ghidra.trace.model.DefaultTraceLocation; -import ghidra.trace.model.Trace; -import ghidra.trace.model.property.TracePropertyMapSpace; - -/** - * The storage space for taint sets in a trace's address space - * - *

- * This adds to {@link TaintTraceSpace} the ability to load taint sets from mapped static programs. - */ -public class TaintDebuggerSpace extends TaintTraceSpace { - protected final PluginTool tool; - protected final Trace trace; - - /** - * Create the space - * - * @param tool the the tool that created the emulator - * @param trace the trace backing this space - * @param space the address space - * @param backing if present, the backing object - * @param snap the source snap - */ - public TaintDebuggerSpace(PluginTool tool, Trace trace, AddressSpace space, - TracePropertyMapSpace backing, long snap) { - super(space, backing, snap); - this.tool = tool; - this.trace = trace; - } - - /** - * {@inheritDoc} - * - *

- * The taint trace space will call this when the cache misses and the trace has no taint set - * stored, allowing us to populate it with a taint set stored in a mapped static program. See - * notes in {@link TaintTraceSpace#whenNull(long)}. - */ - @Override - protected TaintSet whenTraceNull(long offset) { - DebuggerStaticMappingService mappingService = - tool.getService(DebuggerStaticMappingService.class); - ProgramLocation sloc = - mappingService.getOpenMappedLocation(new DefaultTraceLocation(trace, null, - Range.singleton(snap), space.getAddress(offset))); - if (sloc == null) { - return super.whenTraceNull(offset); - } - - // NB. This is stored in the program, not the user data, despite what the name implies - StringPropertyMap map = sloc.getProgram() - .getUsrPropertyManager() - .getStringPropertyMap(TaintTracePcodeExecutorStatePiece.NAME); - if (map == null) { - return super.whenTraceNull(offset); - } - String string = map.getString(sloc.getAddress()); - if (string == null) { - return super.whenTraceNull(offset); - } - return TaintSet.parse(string); - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java index 57422e678f..adaad69561 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java @@ -65,7 +65,7 @@ public class TaintSpace { * This retrieves as many taint sets as there are elements in the given buffer vector. This first * element becomes the taint set at the given offset, then each subsequent element becomes the * taint set at each subsequent offset until the vector is filled. This is analogous to the - * manner in which bytes would be "read" from concrete state, starting at a given ofset, into a + * manner in which bytes would be "read" from concrete state, starting at a given offset, into a * destination array. * * @param offset the offset diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/AbstractTaintTracePcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/AbstractTaintTracePcodeExecutorStatePiece.java deleted file mode 100644 index def021f3c3..0000000000 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/AbstractTaintTracePcodeExecutorStatePiece.java +++ /dev/null @@ -1,102 +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.emu.taint.trace; - -import ghidra.pcode.emu.taint.AbstractTaintPcodeExecutorStatePiece; -import ghidra.pcode.emu.taint.TaintPcodeArithmetic; -import ghidra.pcode.exec.BytesPcodeArithmetic; -import ghidra.pcode.exec.trace.TracePcodeExecutorStatePiece; -import ghidra.program.model.lang.Language; -import ghidra.taint.model.TaintVec; -import ghidra.trace.model.Trace; -import ghidra.trace.model.property.TracePropertyMap; -import ghidra.trace.model.thread.TraceThread; - -/** - * An abstract trace-integrated state piece - * - *

- * See {@link AbstractTaintTracePcodeExecutorStatePiece} for framing. This class must remain - * abstract since we need to derive the Debugger-integrated state piece from it. Thus it tightens - * the bound on {@code } and introduces the parameters necessary to source state from a trace. - * We'll store taint sets in the trace's address property map, which is the recommended scheme for - * auxiliary state. - * - * @param the type of spaces - */ -public abstract class AbstractTaintTracePcodeExecutorStatePiece - extends AbstractTaintPcodeExecutorStatePiece - implements TracePcodeExecutorStatePiece { - public static final String NAME = "Taint"; - - protected final Trace trace; - protected final long snap; - protected final TraceThread thread; - protected final int frame; - protected final TracePropertyMap map; - - /** - * Create a state piece - * - * @param language the emulator's language - * @param trace the trace from which to load taint marks - * @param snap the snap from which to load taint marks - * @param thread if a register space, the thread from which to load taint marks - * @param frame if a register space, the frame - */ - public AbstractTaintTracePcodeExecutorStatePiece(Language language, Trace trace, long snap, - TraceThread thread, int frame) { - super(language, - BytesPcodeArithmetic.forLanguage(language), - TaintPcodeArithmetic.forLanguage(language)); - this.trace = trace; - this.snap = snap; - this.thread = thread; - this.frame = frame; - - this.map = trace.getAddressPropertyManager().getPropertyMap(NAME, String.class); - } - - /** - * Create a state piece - * - * @param trace the trace from which to load taint marks - * @param snap the snap from which to load taint marks - * @param thread if a register space, the thread from which to load taint marks - * @param frame if applicable, the frame - */ - public AbstractTaintTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, - int frame) { - this(trace.getBaseLanguage(), trace, snap, thread, frame); - } - - /** - * {@inheritDoc} - * - *

- * This does the inverse of the lazy loading. Serialize the state and store it back into the - * trace. Technically, it could be a different trace, but it must have identically-named - * threads. - */ - @Override - public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { - TracePropertyMap map = - trace.getAddressPropertyManager().getOrCreatePropertyMap(NAME, String.class); - for (TaintTraceSpace space : spaceMap.values()) { - space.writeDown(map, snap, thread, frame); - } - } -} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java index 34b2d85b41..8a39341046 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java @@ -19,8 +19,9 @@ import ghidra.pcode.emu.taint.TaintPartsFactory; import ghidra.pcode.emu.taint.plain.TaintPcodeEmulator; import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; +import ghidra.pcode.exec.trace.data.*; import ghidra.taint.model.TaintVec; -import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; /** * A trace-integrated emulator with taint analysis @@ -29,11 +30,20 @@ public class TaintTracePcodeEmulator extends AuxTracePcodeEmulator { /** * Create an emulator * - * @param trace the trace the source trace + * @param access the trace access shim + */ + public TaintTracePcodeEmulator(PcodeTraceAccess access) { + super(access); + } + + /** + * Create an emulator + * + * @param platform the platform to emulate * @param snap the source snap */ - public TaintTracePcodeEmulator(Trace trace, long snap) { - super(trace, snap); + public TaintTracePcodeEmulator(TracePlatform platform, long snap) { + super(platform, snap); } /** diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java index dcaf58db51..d31022ac3c 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java @@ -36,7 +36,7 @@ public class TaintTracePcodeExecutorState extends PairedTracePcodeExecutorState< * @param taint the taint piece */ public TaintTracePcodeExecutorState(BytesTracePcodeExecutorStatePiece concrete, - AbstractTaintTracePcodeExecutorStatePiece taint) { + TaintTracePcodeExecutorStatePiece taint) { super(new PairedTracePcodeExecutorStatePiece<>(concrete, taint)); } @@ -44,13 +44,11 @@ public class TaintTracePcodeExecutorState extends PairedTracePcodeExecutorState< * Create a state from the given concrete piece and an internally constructed taint piece * *

- * We take all the parameters needed by the taint piece from the concrete piece. + * We take the data access shim needed by the taint piece from the concrete piece. * * @param concrete the concrete piece */ public TaintTracePcodeExecutorState(BytesTracePcodeExecutorStatePiece concrete) { - this(concrete, - new TaintTracePcodeExecutorStatePiece(concrete.getTrace(), concrete.getSnap(), - concrete.getThread(), concrete.getFrame())); + this(concrete, new TaintTracePcodeExecutorStatePiece(concrete.getData())); } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java index 6c409be0b1..cbda94751b 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java @@ -15,28 +15,47 @@ */ package ghidra.pcode.emu.taint.trace; +import ghidra.pcode.emu.taint.AbstractTaintPcodeExecutorStatePiece; +import ghidra.pcode.emu.taint.TaintPcodeArithmetic; +import ghidra.pcode.exec.BytesPcodeArithmetic; +import ghidra.pcode.exec.trace.TracePcodeExecutorStatePiece; +import ghidra.pcode.exec.trace.data.PcodeTraceDataAccess; +import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; import ghidra.program.model.address.AddressSpace; -import ghidra.trace.model.Trace; +import ghidra.taint.model.TaintVec; import ghidra.trace.model.property.TracePropertyMapSpace; -import ghidra.trace.model.thread.TraceThread; /** * The trace-integrated state piece for holding taint marks + * + *

+ * See {@link AbstractTaintTracePcodeExecutorStatePiece} for framing. We'll store taint sets in the + * trace's address property map, which is the recommended scheme for auxiliary state. */ public class TaintTracePcodeExecutorStatePiece - extends AbstractTaintTracePcodeExecutorStatePiece { + extends AbstractTaintPcodeExecutorStatePiece + implements TracePcodeExecutorStatePiece { + public static final String NAME = "Taint"; + + protected final PcodeTraceDataAccess data; + protected final PcodeTracePropertyAccess property; /** - * Create the taint piece + * Create a state piece * - * @param trace the trace from which to load taint marks - * @param snap the snap from which to load taint marks - * @param thread if a register space, the thread from which to load taint marks - * @param frame if a register space, the frame + * @param data the trace-data access shim */ - public TaintTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, - int frame) { - super(trace, snap, thread, frame); + public TaintTracePcodeExecutorStatePiece(PcodeTraceDataAccess data) { + super(data.getLanguage(), + BytesPcodeArithmetic.forLanguage(data.getLanguage()), + TaintPcodeArithmetic.forLanguage(data.getLanguage())); + this.data = data; + this.property = data.getPropertyAccess(NAME, String.class); + } + + @Override + public PcodeTraceDataAccess getData() { + return data; } /** @@ -52,23 +71,33 @@ public class TaintTracePcodeExecutorStatePiece */ @Override protected AbstractSpaceMap newSpaceMap() { - return new CacheingSpaceMap, TaintTraceSpace>() { + return new CacheingSpaceMap, TaintTraceSpace>() { @Override - protected TracePropertyMapSpace getBacking(AddressSpace space) { - if (map == null) { - return null; - } - if (space.isRegisterSpace()) { - return map.getPropertyMapRegisterSpace(thread, frame, false); - } - return map.getPropertyMapSpace(space, false); + protected PcodeTracePropertyAccess getBacking(AddressSpace space) { + return property; } @Override protected TaintTraceSpace newSpace(AddressSpace space, - TracePropertyMapSpace backing) { - return new TaintTraceSpace(space, backing, snap); + PcodeTracePropertyAccess backing) { + return new TaintTraceSpace(space, property); } }; } + + /** + * {@inheritDoc} + * + *

+ * This does the inverse of the lazy loading. Serialize the state and store it back into the + * trace. Technically, it could be a different trace, but it must have identically-named + * threads. + */ + @Override + public void writeDown(PcodeTraceDataAccess into) { + PcodeTracePropertyAccess property = into.getPropertyAccess(NAME, String.class); + for (TaintTraceSpace space : spaceMap.values()) { + space.writeDown(property); + } + } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java index 6ea84ffa7c..ea0ff125bf 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java @@ -17,18 +17,10 @@ package ghidra.pcode.emu.taint.trace; import java.util.Map.Entry; -import com.google.common.collect.Range; - -import ghidra.pcode.emu.taint.full.TaintDebuggerSpace; import ghidra.pcode.emu.taint.plain.TaintSpace; -import ghidra.pcode.exec.trace.TracePcodeExecutorState; +import ghidra.pcode.exec.trace.data.PcodeTracePropertyAccess; import ghidra.program.model.address.*; import ghidra.taint.model.TaintSet; -import ghidra.trace.database.DBTraceUtils; -import ghidra.trace.model.Trace; -import ghidra.trace.model.property.TracePropertyMap; -import ghidra.trace.model.property.TracePropertyMapSpace; -import ghidra.trace.model.thread.TraceThread; /** * The storage space for taint sets in a trace's address space @@ -39,8 +31,7 @@ import ghidra.trace.model.thread.TraceThread; */ public class TaintTraceSpace extends TaintSpace { protected final AddressSpace space; - protected final TracePropertyMapSpace backing; - protected final long snap; + protected final PcodeTracePropertyAccess property; /** * Create the space @@ -49,10 +40,9 @@ public class TaintTraceSpace extends TaintSpace { * @param backing if present, the backing object * @param snap the source snap */ - public TaintTraceSpace(AddressSpace space, TracePropertyMapSpace backing, long snap) { + public TaintTraceSpace(AddressSpace space, PcodeTracePropertyAccess property) { this.space = space; - this.backing = backing; - this.snap = snap; + this.property = property; } /** @@ -61,73 +51,44 @@ public class TaintTraceSpace extends TaintSpace { *

* The taint space will call this when the cache misses, allowing us to populate it with a taint * set stored in the trace. Note that if the emulator writes to this offset before - * reading it, this will not get called for that offset. Here we simply load the string property - * from the map and parse the taint set. We'll also introduce a second extension point for when - * neither the cache nor the trace have a taint set. + * reading it, this will not get called for that offset. Here we simply get the string property + * and parse the taint set. */ @Override protected TaintSet whenNull(long offset) { - if (backing == null) { - return whenTraceNull(offset); - } - String string = backing.get(snap, space.getAddress(offset)); + String string = property.get(space.getAddress(offset)); if (string == null) { - return whenTraceNull(offset); + return TaintSet.EMPTY; } return TaintSet.parse(string); } - /** - * Extension point: Behavior when there is neither an in-memory nor a trace-stored taint set at - * the given offset - * - *

- * This will be overridden by {@link TaintDebuggerSpace} to implement loading from static mapped - * programs. - * - * @param offset the offset - * @return the taint set to use - */ - protected TaintSet whenTraceNull(long offset) { - return TaintSet.EMPTY; - } - /** * Write this cache back down into a trace * *

- * Here we simply iterate over every entry in this space, serialize the taint, and store it into - * the property map at the entry's offset. Because a backing object may not have existed when - * creating this space, we must re-fetch the backing object, creating it if it does not exist. - * We can safely create such spaces, since the client is required to have an open transaction on - * the destination trace while invoking this method (via - * {@link TracePcodeExecutorState#writeDown(Trace, long, TraceThread, int)}). + * Here we simply iterate over every entry in this space, serialize the taint, and put it into + * the property at the entry's offset. If the taint set is empty, we clear the property rather + * than putting the empty taint set into the property. * * @param map the backing object, which must now exist * @param snap the destination snap * @param thread if a register space, the destination thread * @param frame if a register space, the destination frame */ - public void writeDown(TracePropertyMap map, long snap, TraceThread thread, int frame) { + public void writeDown(PcodeTracePropertyAccess into) { if (space.isUniqueSpace()) { return; } - TracePropertyMapSpace backing; - if (space.isRegisterSpace()) { - backing = map.getPropertyMapRegisterSpace(thread, frame, true); - } - else { - backing = map.getPropertyMapSpace(space, true); - } + for (Entry entry : taints.entrySet()) { TaintSet taint = entry.getValue(); - Range span = DBTraceUtils.atLeastMaybeScratch(snap); Address address = space.getAddress(entry.getKey()); if (taint.isEmpty()) { - backing.clear(span, new AddressRangeImpl(address, address)); + into.clear(new AddressRangeImpl(address, address)); } else { - backing.set(span, address, taint.toString()); + into.put(address, taint.toString()); } } } diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java index 806334cf7b..03a0c5f54d 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java @@ -23,15 +23,19 @@ import ghidra.app.plugin.core.debug.gui.register.RegisterRow; import ghidra.docking.settings.Settings; import ghidra.framework.plugintool.ServiceProvider; import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Register; import ghidra.trace.model.Trace; +import ghidra.trace.model.property.TracePropertyMap; +import ghidra.trace.model.property.TracePropertyMapSpace; /** * A factory for the "Taint" column in the "Registers" panel * *

* For the most part, this is just a matter of accessing the property map and rendering the value on - * screen. As a cheap shortcut, we'll just instantiate a taint state piece at the panel's - * coordinates and use it to retrieve the actual taint marks, then render that for display. + * screen. */ public class TaintDebuggerRegisterColumnFactory implements DebuggerRegisterColumnFactory { protected static final String PROP_NAME = TaintTracePcodeExecutorStatePiece.NAME; @@ -46,7 +50,7 @@ public class TaintDebuggerRegisterColumnFactory implements DebuggerRegisterColum } @Override - public String getValue(RegisterRow rowObject, Settings settings, Void data, + public String getValue(RegisterRow rowObject, Settings settings, Void dataSource, ServiceProvider serviceProvider) throws IllegalArgumentException { DebuggerCoordinates current = rowObject.getCurrent(); Trace trace = current.getTrace(); @@ -54,11 +58,39 @@ public class TaintDebuggerRegisterColumnFactory implements DebuggerRegisterColum return ""; } - TaintTracePcodeExecutorStatePiece piece = - new TaintTracePcodeExecutorStatePiece(current.getTrace(), current.getViewSnap(), - current.getThread(), current.getFrame()); + TracePropertyMap taintMap = current.getTrace() + .getAddressPropertyManager() + .getPropertyMap(PROP_NAME, String.class); - return piece.getVar(rowObject.getRegister()).toDisplay(); + if (taintMap == null) { + return ""; + } + + Register register = rowObject.getRegister(); + TracePropertyMapSpace taintSpace; + AddressSpace addressSpace = register.getAddressSpace(); + if (addressSpace.isRegisterSpace()) { + taintSpace = taintMap.getPropertyMapRegisterSpace(current.getThread(), + current.getFrame(), false); + } + else { + taintSpace = taintMap.getPropertyMapSpace(addressSpace, false); + } + if (taintSpace == null) { + return ""; + } + + // Cheat the deserialization/reserialization here + StringBuffer vec = new StringBuffer(); + int count = register.getNumBytes(); + Address start = register.getAddress(); + for (int i = 0; i < count; i++) { + vec.append('['); + String taint = taintSpace.get(current.getViewSnap(), start.addWrap(i)); + vec.append(taint == null ? "" : taint); + vec.append(']'); + } + return vec.toString(); } }; } diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java index db4f43cd1e..7383bbba20 100644 --- a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java @@ -121,9 +121,8 @@ public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebugger progTaintMap.add(tb.addr(0x00400800), "test_0"); Assembler asm = Assemblers.getAssembler(program); - // TODO: I should be able to make this use a RIP-relative address - asm.assemble(tb.addr(0x00400000), - "MOV RAX, [0x55550800]"); // was [0x00400800], but fixed address is a problem. + // NOTE: qword ptr [0x00400800] is RIP-relative + asm.assemble(tb.addr(0x00400000), "MOV RAX, qword ptr [0x00400800]"); } TraceSchedule time = TraceSchedule.parse("0:t0-1"); diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java index 48f235176f..dd97b2c7ca 100644 --- a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java @@ -18,6 +18,8 @@ package ghidra.pcode.emu.taint.trace; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import java.math.BigInteger; +import java.nio.ByteBuffer; import java.util.*; import java.util.stream.Collectors; @@ -26,15 +28,27 @@ import org.junit.Test; import com.google.common.collect.Range; +import ghidra.app.plugin.assembler.*; +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.trace.AbstractTracePcodeEmulatorTest; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; import ghidra.taint.model.*; import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.database.target.DBTraceObjectManager; +import ghidra.trace.database.target.DBTraceObjectManagerTest; import ghidra.trace.model.*; +import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.memory.TraceMemoryManager; +import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.property.TracePropertyMap; import ghidra.trace.model.property.TracePropertyMapSpace; +import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.trace.model.target.TraceObject.ConflictResolution; import ghidra.trace.model.thread.TraceThread; import ghidra.util.database.UndoableTransaction; @@ -71,7 +85,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest taintMap.set(Range.atLeast(0L), tb.range(0x00400000, 0x00400003), "test_0"); } - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getExecutor().executeSleigh("RAX = *0x00400000:8;"); @@ -101,7 +115,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest mapSpace.set(Range.atLeast(0L), regEBX, "test_0"); } - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getExecutor().executeSleigh("RAX = RBX;"); @@ -121,7 +135,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { initTrace(tb, "", List.of()); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); TaintVec taintVal = TaintVec.empties(8); TaintSet testTaint = TaintSet.of(new TaintMark("test_0", Set.of())); for (int i = 0; i < 4; i++) { @@ -132,7 +146,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest Pair.of(tb.arr(0, 0, 0, 0, 0, 0, 0, 0), taintVal)); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 0); + emu.writeDown(tb.host, 1, 0); } TracePropertyMap taintMap = tb.trace.getAddressPropertyManager().getPropertyMap("Taint", String.class); @@ -148,7 +162,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest AddressSpace rs = tb.language.getAddressFactory().getRegisterSpace(); TraceThread thread = initTrace(tb, "", List.of()); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); PcodeThread> emuThread = emu.newThread(thread.getPath()); TaintVec taintVal = TaintVec.empties(8); TaintSet testTaint = TaintSet.of(new TaintMark("test_0", Set.of())); @@ -158,7 +172,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.getState().setVar(tb.reg("EAX"), Pair.of(tb.arr(0, 0, 0, 0), taintVal)); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 0); + emu.writeDown(tb.host, 1, 0); } TracePropertyMap taintMap = tb.trace.getAddressPropertyManager().getPropertyMap("Taint", String.class); @@ -183,7 +197,7 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest "MOV qword ptr [0x00600000], RAX", "MOV qword ptr [0x00600000], RBX")); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getState() .setVar(tb.reg("RAX"), Pair.of( @@ -192,11 +206,11 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 0); + emu.writeDown(tb.host, 1, 0); } emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 2, 0); + emu.writeDown(tb.host, 2, 0); } TracePropertyMap taintMap = @@ -220,16 +234,19 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "XOR RAX, RAX")); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getState() .setVar(tb.reg("RAX"), Pair.of( tb.arr(1, 2, 3, 4, 5, 6, 7, 8), TaintVec.copies(TaintSet.parse("test_0"), 8))); + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(tb.host, 0, 0); + } emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 0); + emu.writeDown(tb.host, 1, 0); } TracePropertyMap taintMap = @@ -251,16 +268,19 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest List.of( "XOR EAX, EAX")); - TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.host, 0); PcodeThread> emuThread = emu.newThread(thread.getPath()); emuThread.getState() .setVar(tb.reg("RAX"), Pair.of( tb.arr(1, 2, 3, 4, 5, 6, 7, 8), TaintVec.copies(TaintSet.parse("test_0"), 8))); + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(tb.host, 0, 0); + } emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 0); + emu.writeDown(tb.host, 1, 0); } TracePropertyMap taintMap = @@ -272,4 +292,64 @@ public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest Set.copyOf(mapSpace.getEntries(Range.singleton(1L), tb.reg("RAX")))); } } + + @Test + public void testGuestEmptyTaintClears() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "DATA:BE:64:default")) { + TraceMemoryManager mm = tb.trace.getMemoryManager(); + AddressSpace ram = tb.language.getAddressFactory().getDefaultAddressSpace(); + TraceThread thread; + TraceGuestPlatform x64; + try (UndoableTransaction tid = tb.startTransaction()) { + SchemaContext ctx = XmlSchemaContext.deserialize(DBTraceObjectManagerTest.XML_CTX); + DBTraceObjectManager objects = tb.trace.getObjectManager(); + objects.createRootObject(ctx.getSchema(new SchemaName("Session"))); + thread = tb.getOrAddThread("Targets[0].Threads[0]", 0); + + x64 = tb.trace.getPlatformManager() + .addGuestPlatform(getSLEIGH_X86_64_LANGUAGE().getDefaultCompilerSpec()); + x64.addMappedRegisterRange(); + x64.addMappedRange(tb.addr(0x00000000), tb.addr(x64, 0x00400000), 0x10000); + x64.addMappedRange(tb.addr(0x20000000), tb.addr(x64, 0x00600000), 0x10000); + objects.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0].Registers")) + .insert(Range.atLeast(0L), ConflictResolution.DENY); + // TODO: Make Sleigh work in the guest platform + TraceMemorySpace regs = mm.getMemoryRegisterSpace(thread, 0, true); + regs.setValue(x64, 0, + new RegisterValue(tb.reg(x64, "RIP"), BigInteger.valueOf(0x00400000))); + + Assembler asm = Assemblers.getAssembler(x64.getLanguage()); + AssemblyBuffer buf = new AssemblyBuffer(asm, tb.addr(x64, 0x00400000)); + buf.assemble("MOV qword ptr [0x00600000], RAX"); + buf.assemble("MOV qword ptr [0x00600000], RBX"); + mm.putBytes(0, tb.addr(0x00000000), ByteBuffer.wrap(buf.getBytes())); + } + + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(x64, 0); + PcodeThread> emuThread = emu.newThread(thread.getPath()); + emuThread.getState() + .setVar(tb.reg(x64, "RAX"), Pair.of( + tb.arr(0, 0, 0, 0, 0, 0, 0, 0), + TaintVec.copies(TaintSet.parse("test_0"), 8))); + + emuThread.stepInstruction(); + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(x64, 1, 0); + } + emuThread.stepInstruction(); + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(x64, 2, 0); + } + + TracePropertyMap taintMap = + tb.trace.getAddressPropertyManager().getPropertyMap("Taint", String.class); + + assertEquals(makeTaintEntries(tb.trace, Range.singleton(1L), ram, Set.of( + 0x20000000L, 0x20000001L, 0x20000002L, 0x20000003L, + 0x20000004L, 0x20000005L, 0x20000006L, 0x20000007L), + "test_0"), + Set.copyOf(taintMap.getEntries( + Range.singleton(1L), tb.range(0x20000000, 0x20000007)))); + } + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java index 5936f0cce8..d298f027ec 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java @@ -1542,4 +1542,9 @@ public class SleighLanguage implements Language { return registerManager.getSortedVectorRegisters(); } + @Override + public AddressSetView getRegisterAddresses() { + return registerManager.getRegisterAddresses(); + } + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramAddressFactory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramAddressFactory.java index 153dfbe57b..9bfbcc0fb8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramAddressFactory.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/ProgramAddressFactory.java @@ -102,6 +102,16 @@ public class ProgramAddressFactory extends DefaultAddressFactory { addAddressSpace(ovSpace); } + /** + * Determine whether the given space can have an overlay + * + * @param originalSpace the original space + * @return true to allow, false to prohibit + */ + protected boolean validateOriginalSpace(AddressSpace originalSpace) { + return originalSpace.isMemorySpace() && !originalSpace.isOverlaySpace(); + } + /** * Create a new OverlayAddressSpace based upon the given overlay blockName and base AddressSpace * @@ -120,7 +130,7 @@ public class ProgramAddressFactory extends DefaultAddressFactory { protected OverlayAddressSpace addOverlayAddressSpace(String name, boolean preserveName, AddressSpace originalSpace, long minOffset, long maxOffset) { - if (!originalSpace.isMemorySpace() || originalSpace.isOverlaySpace()) { + if (!validateOriginalSpace(originalSpace)) { throw new IllegalArgumentException( "Invalid address space for overlay: " + originalSpace.getName()); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Language.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Language.java index 2eb334492d..eda905691e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Language.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Language.java @@ -418,4 +418,10 @@ public interface Language { */ public List getSortedVectorRegisters(); + /** + * Returns address set of all registers. + * @return the address set. + */ + public AddressSetView getRegisterAddresses(); + } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Register.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Register.java index 7b5f3326a4..fab3b05c2e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Register.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Register.java @@ -209,7 +209,7 @@ public class Register implements java.io.Serializable, Comparable { * Returns the number of bytes spanned by this Register. * *

- * Compare to {{@link #getMinimumByteSize()}: Suppose a 5-bit register spans 2 bytes: 1 bit in + * Compare to {@link #getMinimumByteSize()}: Suppose a 5-bit register spans 2 bytes: 1 bit in * the first byte, and the remaining 4 in the following byte. Its value can still be stored in 1 * byte, which is what {@link #getMinimumByteSize()} returns; however, its storage still spans 2 * bytes of the base register, which is what this method returns. diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/RegisterManager.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/RegisterManager.java index 8beac43323..679204ebdf 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/RegisterManager.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/RegisterManager.java @@ -31,6 +31,8 @@ public class RegisterManager { private Map sizeMap = new HashMap(); private Map> registerAddressMap = new HashMap>(); + private AddressSet registerAddresses = new AddressSet(); + private AddressSetView registerAddressesView = new AddressSetViewAdapter(registerAddresses); /** List of vector registers, sorted first by size and then by offset **/ private List sortedVectorRegisters; @@ -114,6 +116,7 @@ public class RegisterManager { registerAddressMap.put(addr, list); } list.add(reg); + addRegisterAddresses(reg); if (reg.isProcessorContext()) { continue; } @@ -139,6 +142,11 @@ public class RegisterManager { registerNames = Collections.unmodifiableList(registerNameList); } + private void addRegisterAddresses(Register reg) { + Address min = reg.getAddress(); + registerAddresses.add(min, min.add(reg.getNumBytes()-1)); + } + private void populateSizeMapBigEndian(Register reg) { int regSize = reg.getMinimumByteSize(); for (int i = 1; i <= regSize; i++) { @@ -278,6 +286,15 @@ public class RegisterManager { return sortedVectorRegisters; } + /** + * Get the set of addresses contained in registers + * + * @return the address set + */ + public AddressSetView getRegisterAddresses() { + return registerAddressesView; + } + /** * Compares two vector registers, first by size (descending) and then by offset (ascending). * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/OldLanguage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/OldLanguage.java index 688c0ca6ec..b0191476f4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/OldLanguage.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/OldLanguage.java @@ -751,4 +751,9 @@ class OldLanguage implements Language { throw new UnsupportedOperationException( "Language for upgrade use only (getSortedVectorRegisters)"); } + + @Override + public AddressSetView getRegisterAddresses() { + return registerMgr.getRegisterAddresses(); + } }