From 55b07206e38749291f64dd7f16308e4798ddb507 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:50:39 -0500 Subject: [PATCH] GP-4374: 'Pure' Emulation uses Object-based trace. --- .../api/emulation/PcodeDebuggerAccess.java | 2 +- .../api/tracemgr/DebuggerCoordinates.java | 10 +- .../debug/disassemble/DisassemblyInject.java | 39 ++-- .../disassemble/DisassemblyInjectInfo.java | 14 +- .../LoadEmulatorAutoReadMemorySpec.java | 5 + .../gui/action/NoneAutoReadMemorySpec.java | 5 + .../gui/action/VisibleAutoReadMemorySpec.java | 5 + .../VisibleROOnceAutoReadMemorySpec.java | 5 + .../AbstractDebuggerPlatformMapper.java | 20 ++- ....java => HostDebuggerPlatformOpinion.java} | 44 ++--- .../platform/arm/ArmDisassemblyInject.java | 72 ++++---- .../dbgeng/DbgengDebuggerPlatformOpinion.java | 3 +- .../dbgeng/DbgengX64DisassemblyInject.java | 17 +- .../emulation/ProgramEmulationUtils.java | 89 +++++++++- .../core/debug/stack/StackUnwinder.java | 16 +- .../disassemble/DebuggerDisassemblyTest.java | 166 ++++++++++++++++-- .../mapping/TestDebuggerPlatformOpinion.java | 2 +- .../DebuggerEmulationServiceTest.java | 78 +++++--- .../debug/flatapi/FlatDebuggerAPITest.java | 40 +++-- .../database/target/DBTraceObjectManager.java | 7 +- .../trace/database/ToyDBTraceBuilder.java | 4 +- 21 files changed, 463 insertions(+), 180 deletions(-) rename Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/{legacy/LegacyDebuggerPlatformOpinion.java => HostDebuggerPlatformOpinion.java} (55%) diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/PcodeDebuggerAccess.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/PcodeDebuggerAccess.java index 7936ffcdf7..fb865c4fc8 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/PcodeDebuggerAccess.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/emulation/PcodeDebuggerAccess.java @@ -24,7 +24,7 @@ import ghidra.trace.model.thread.TraceThread; * *

* 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 + * encapsulates the tool controlling a session and the session's target. 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}. diff --git a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java index d5785d009d..76b2e8bef2 100644 --- a/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java +++ b/Ghidra/Debug/Debugger-api/src/main/java/ghidra/debug/api/tracemgr/DebuggerCoordinates.java @@ -310,8 +310,14 @@ public class DebuggerCoordinates { if (frameLevel == null) { return objThread.getCanonicalPath(); } - TraceStack stack = - thread.getTrace().getStackManager().getStack(thread, time.getSnap(), false); + TraceStack stack; + try { + stack = thread.getTrace().getStackManager().getStack(thread, time.getSnap(), false); + } + catch (IllegalStateException e) { + // Schema does not specify a stack + return objThread.getCanonicalPath(); + } if (stack == null) { return objThread.getCanonicalPath(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DisassemblyInject.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DisassemblyInject.java index b58f3e9ad3..c0b40f3904 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DisassemblyInject.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DisassemblyInject.java @@ -15,11 +15,11 @@ */ package ghidra.app.plugin.core.debug.disassemble; -import ghidra.app.plugin.core.debug.disassemble.DisassemblyInjectInfo.CompilerInfo; +import ghidra.app.plugin.core.debug.disassemble.DisassemblyInjectInfo.PlatformInfo; +import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSetView; -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.util.Msg; import ghidra.util.classfinder.ExtensionPoint; @@ -38,7 +38,7 @@ import ghidra.util.classfinder.ExtensionPoint; * configure the disassembler (namely seeding its context), the one invoked last will have "the last * word." As such, each inject should avoid unnecessarily erasing existing context. */ -@DisassemblyInjectInfo(compilers = {}) // Use as default +@DisassemblyInjectInfo(platforms = {}) // Use as default public interface DisassemblyInject extends ExtensionPoint { /** * If present, get the information annotation on this inject @@ -56,16 +56,16 @@ public interface DisassemblyInject extends ExtensionPoint { } /** - * Check if this inject applies to the given trace + * Check if this inject applies to the given trace platform * - * @param trace the trace to check + * @param platform the platform to check * @return true if applicable, false otherwise */ - default boolean isApplicable(Trace trace) { - for (CompilerInfo info : getInfo().compilers()) { - if (info.langID().equals(trace.getBaseLanguage().getLanguageID().toString())) { + default boolean isApplicable(TracePlatform platform) { + for (PlatformInfo info : getInfo().platforms()) { + if (info.langID().equals(platform.getLanguage().getLanguageID().toString())) { if (info.compilerID().isBlank() || info.compilerID() - .equals(trace.getBaseCompilerSpec().getCompilerSpecID().toString())) { + .equals(platform.getCompilerSpec().getCompilerSpecID().toString())) { return true; } } @@ -86,36 +86,35 @@ public interface DisassemblyInject extends ExtensionPoint { * A pre-auto disassembly hook * *

- * This hook is invoked by the {@link DisassembleAtPcDebuggerBot} before disassembly actually + * This hook is invoked by the {@link DebuggerPlatformMapper} before disassembly actually * begins. The callback occurs within the command's background thread. In general, the inject * should limit its operation to inspecting the trace database and configuring the command. * * @param tool the tool that will execute the command * @param command the command to be configured, which is about to execute - * @param trace the trace whose bytes to disassemble - * @param language the language for the disassembler + * @param platform the trace platform for the disassembler * @param snap the snap the snap at which to disassemble * @param thread the thread whose PC is being disassembled * @param startSet the starting address set, usually just the PC * @param restricted the set of disassemblable addresses */ - default void pre(PluginTool tool, TraceDisassembleCommand command, Trace trace, - Language language, long snap, TraceThread thread, AddressSetView startSet, - AddressSetView restricted) { + default void pre(PluginTool tool, TraceDisassembleCommand command, TracePlatform platform, + long snap, TraceThread thread, AddressSetView startSet, AddressSetView restricted) { } /** * A post-auto disassembly hook * *

- * This hook is invoked by the {@link DisassembleAtPcDebuggerBot} after disassembly completes. - * The callback occurs within the command's background thread. + * This hook is invoked by the {@link DebuggerPlatformMapper} after disassembly completes. The + * callback occurs within the command's background thread. * * @param tool the tool that just executed the disassembly command - * @param trace the trace whose bytes were disassembled + * @param platform the trace platform for the disassembler * @param snap the snap the snap at which disassembly was performed * @param disassembled the addresses that were actually disassembled */ - default void post(PluginTool tool, Trace trace, long snap, AddressSetView disassembled) { + default void post(PluginTool tool, TracePlatform platform, long snap, + AddressSetView disassembled) { } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DisassemblyInjectInfo.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DisassemblyInjectInfo.java index 1724219ecf..1b4cc32fb3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DisassemblyInjectInfo.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/disassemble/DisassemblyInjectInfo.java @@ -17,6 +17,8 @@ package ghidra.app.plugin.core.debug.disassemble; import java.lang.annotation.*; +import ghidra.trace.model.guest.TracePlatform; + /** * Information about the applicability of a disassembly inject */ @@ -24,9 +26,9 @@ import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) public @interface DisassemblyInjectInfo { /** - * A language-compiler-ID pair + * A language-compiler-ID pair identifying a trace platform */ - public @interface CompilerInfo { + public @interface PlatformInfo { /** * The language ID, e.g., "x86:64:LE:default" * @@ -46,12 +48,12 @@ public @interface DisassemblyInjectInfo { } /** - * A list of language-compiler-ID pairs for which this inject applies + * A list of platforms for which this inject applies * - * @see DisassemblyInject#isApplicable(ghidra.trace.model.Trace) - * @return the language-compiler-ID pairs + * @see DisassemblyInject#isApplicable(TracePlatform) + * @return the platforms */ - CompilerInfo[] compilers(); + PlatformInfo[] platforms(); /** * The "position" of this inject's invocation diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java index 891d373b47..e230e09747 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/LoadEmulatorAutoReadMemorySpec.java @@ -38,6 +38,11 @@ import ghidra.trace.model.memory.TraceMemoryState; public class LoadEmulatorAutoReadMemorySpec implements AutoReadMemorySpec { public static final String CONFIG_NAME = "2_LOAD_EMULATOR"; + @Override + public boolean equals(Object obj) { + return this.getClass() == obj.getClass(); + } + @Override public String getConfigName() { return CONFIG_NAME; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoReadMemorySpec.java index d603e40ae5..3a20cee074 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/NoneAutoReadMemorySpec.java @@ -27,6 +27,11 @@ import ghidra.program.model.address.AddressSetView; public class NoneAutoReadMemorySpec implements AutoReadMemorySpec { public static final String CONFIG_NAME = "0_READ_NONE"; + @Override + public boolean equals(Object obj) { + return this.getClass() == obj.getClass(); + } + @Override public String getConfigName() { return CONFIG_NAME; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java index 3cde8958e3..0c5b29f110 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleAutoReadMemorySpec.java @@ -32,6 +32,11 @@ import ghidra.util.task.TaskMonitor; public class VisibleAutoReadMemorySpec implements AutoReadMemorySpec { public static final String CONFIG_NAME = "1_READ_VISIBLE"; + @Override + public boolean equals(Object obj) { + return this.getClass() == obj.getClass(); + } + @Override public String getConfigName() { return CONFIG_NAME; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java index 922fccd948..dd481f886c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/VisibleROOnceAutoReadMemorySpec.java @@ -33,6 +33,11 @@ import ghidra.util.task.TaskMonitor; public class VisibleROOnceAutoReadMemorySpec implements AutoReadMemorySpec { public static final String CONFIG_NAME = "1_READ_VIS_RO_ONCE"; + @Override + public boolean equals(Object obj) { + return this.getClass() == obj.getClass(); + } + @Override public String getConfigName() { return CONFIG_NAME; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java index 0eb12f1736..b6874585ee 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/AbstractDebuggerPlatformMapper.java @@ -16,7 +16,8 @@ package ghidra.app.plugin.core.debug.mapping; import java.util.Collection; -import java.util.Set; +import java.util.Comparator; +import java.util.stream.Collectors; import ghidra.app.plugin.core.debug.disassemble.DisassemblyInject; import ghidra.app.plugin.core.debug.disassemble.TraceDisassembleCommand; @@ -25,11 +26,11 @@ import ghidra.debug.api.platform.DisassemblyResult; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; import ghidra.program.model.lang.Endian; -import ghidra.program.model.lang.Language; import ghidra.trace.model.Trace; import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.target.TraceObject; import ghidra.trace.model.thread.TraceThread; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.task.TaskMonitor; public abstract class AbstractDebuggerPlatformMapper implements DebuggerPlatformMapper { @@ -63,8 +64,12 @@ public abstract class AbstractDebuggerPlatformMapper implements DebuggerPlatform return trace.getCodeManager().instructions().getAt(snap, start) != null; } - protected Collection getDisassemblyInjections(TraceObject object) { - return Set.of(); + protected Collection getDisassemblyInjections(TracePlatform platform) { + return ClassSearcher.getInstances(DisassemblyInject.class) + .stream() + .filter(i -> i.isApplicable(platform)) + .sorted(Comparator.comparing(i -> i.getPriority())) + .collect(Collectors.toList()); } @Override @@ -75,12 +80,11 @@ public abstract class AbstractDebuggerPlatformMapper implements DebuggerPlatform } TracePlatform platform = trace.getPlatformManager().getPlatform(getCompilerSpec(object)); - Collection injects = getDisassemblyInjections(object); + Collection injects = getDisassemblyInjections(platform); TraceDisassembleCommand dis = new TraceDisassembleCommand(platform, start, restricted); - Language language = platform.getLanguage(); AddressSet startSet = new AddressSet(start); for (DisassemblyInject i : injects) { - i.pre(tool, dis, trace, language, snap, thread, startSet, restricted); + i.pre(tool, dis, platform, snap, thread, startSet, restricted); } boolean result = dis.applyToTyped(trace.getFixedProgramView(snap), monitor); if (!result) { @@ -88,7 +92,7 @@ public abstract class AbstractDebuggerPlatformMapper implements DebuggerPlatform } AddressSetView actualSet = dis.getDisassembledAddressSet(); for (DisassemblyInject i : injects) { - i.post(tool, trace, snap, actualSet); + i.post(tool, platform, snap, actualSet); } return DisassemblyResult.success(actualSet != null && !actualSet.isEmpty()); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/HostDebuggerPlatformOpinion.java similarity index 55% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/HostDebuggerPlatformOpinion.java index 88d187007c..3c5802b4d7 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/legacy/LegacyDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/mapping/HostDebuggerPlatformOpinion.java @@ -13,30 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.app.plugin.core.debug.mapping.legacy; +package ghidra.app.plugin.core.debug.mapping; -import java.util.*; -import java.util.stream.Collectors; +import java.util.Set; -import ghidra.app.plugin.core.debug.disassemble.DisassemblyInject; -import ghidra.app.plugin.core.debug.mapping.*; import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.framework.plugintool.PluginTool; -import ghidra.lifecycle.Transitional; import ghidra.program.model.lang.CompilerSpec; import ghidra.trace.model.Trace; import ghidra.trace.model.target.TraceObject; -import ghidra.util.classfinder.ClassSearcher; /** - * An opinion which retains the front-end functionality when using the mapped recorder, i.e., when - * displaying non-object-based traces. + * An opinion which just uses the trace's "host" platform, i.e., because the target created the + * trace with the correct host language. Other mappers assume the trace language is DATA, and that + * the real language must be mapped as a guest platform. */ -@Transitional -public class LegacyDebuggerPlatformOpinion implements DebuggerPlatformOpinion { +public class HostDebuggerPlatformOpinion implements DebuggerPlatformOpinion { - protected static class LegacyDebuggerPlatformMapper extends AbstractDebuggerPlatformMapper { - public LegacyDebuggerPlatformMapper(PluginTool tool, Trace trace) { + protected static class HostDebuggerPlatformMapper extends AbstractDebuggerPlatformMapper { + public HostDebuggerPlatformMapper(PluginTool tool, Trace trace) { super(tool, trace); } @@ -54,23 +49,13 @@ public class LegacyDebuggerPlatformOpinion implements DebuggerPlatformOpinion { public boolean canInterpret(TraceObject newFocus, long snap) { return true; } - - @Override - protected Collection getDisassemblyInjections(TraceObject object) { - // Track an injects set using a listener instead? - return ClassSearcher.getInstances(DisassemblyInject.class) - .stream() - .filter(i -> i.isApplicable(trace)) - .sorted(Comparator.comparing(i -> i.getPriority())) - .collect(Collectors.toList()); - } } enum Offers implements DebuggerPlatformOffer { - LEGACY { + HOST { @Override public String getDescription() { - return "Legacy (Already mapped by recorder)"; + return "Host/base (Language already chosen by target)"; } @Override @@ -85,12 +70,12 @@ public class LegacyDebuggerPlatformOpinion implements DebuggerPlatformOpinion { @Override public DebuggerPlatformMapper take(PluginTool tool, Trace trace) { - return new LegacyDebuggerPlatformMapper(tool, trace); + return new HostDebuggerPlatformMapper(tool, trace); } @Override public boolean isCreatorOf(DebuggerPlatformMapper mapper) { - return mapper.getClass() == LegacyDebuggerPlatformMapper.class; + return mapper.getClass() == HostDebuggerPlatformMapper.class; } }; } @@ -98,9 +83,6 @@ public class LegacyDebuggerPlatformOpinion implements DebuggerPlatformOpinion { @Override public Set getOffers(Trace trace, TraceObject focus, long snap, boolean includeOverrides) { - if (trace.getObjectManager().getRootObject() != null) { - return Set.of(); - } - return Set.of(Offers.LEGACY); + return Set.of(Offers.HOST); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/arm/ArmDisassemblyInject.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/arm/ArmDisassemblyInject.java index efed76639c..6321e9a2d8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/arm/ArmDisassemblyInject.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/arm/ArmDisassemblyInject.java @@ -18,38 +18,38 @@ package ghidra.app.plugin.core.debug.platform.arm; import java.math.BigInteger; import ghidra.app.plugin.core.debug.disassemble.*; -import ghidra.app.plugin.core.debug.disassemble.DisassemblyInjectInfo.CompilerInfo; +import ghidra.app.plugin.core.debug.disassemble.DisassemblyInjectInfo.PlatformInfo; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.AddressSetView; 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.thread.TraceThread; import ghidra.util.Msg; @DisassemblyInjectInfo( - compilers = { - @CompilerInfo(langID = "ARM:LE:32:v8"), - @CompilerInfo(langID = "ARM:LE:32:v8T"), - @CompilerInfo(langID = "ARM:LEBE:32:v8LEInstruction"), - @CompilerInfo(langID = "ARM:BE:32:v8"), - @CompilerInfo(langID = "ARM:BE:32:v8T"), - @CompilerInfo(langID = "ARM:LE:32:v7"), - @CompilerInfo(langID = "ARM:LEBE:32:v7LEInstruction"), - @CompilerInfo(langID = "ARM:BE:32:v7"), - @CompilerInfo(langID = "ARM:LE:32:Cortex"), - @CompilerInfo(langID = "ARM:BE:32:Cortex"), - @CompilerInfo(langID = "ARM:LE:32:v6"), - @CompilerInfo(langID = "ARM:BE:32:v6"), - @CompilerInfo(langID = "ARM:LE:32:v5t"), - @CompilerInfo(langID = "ARM:BE:32:v5t"), - @CompilerInfo(langID = "ARM:LE:32:v5"), - @CompilerInfo(langID = "ARM:BE:32:v5"), - @CompilerInfo(langID = "ARM:LE:32:v4t"), - @CompilerInfo(langID = "ARM:BE:32:v4t"), - @CompilerInfo(langID = "ARM:LE:32:v4"), - @CompilerInfo(langID = "ARM:BE:32:v4"), + platforms = { + @PlatformInfo(langID = "ARM:LE:32:v8"), + @PlatformInfo(langID = "ARM:LE:32:v8T"), + @PlatformInfo(langID = "ARM:LEBE:32:v8LEInstruction"), + @PlatformInfo(langID = "ARM:BE:32:v8"), + @PlatformInfo(langID = "ARM:BE:32:v8T"), + @PlatformInfo(langID = "ARM:LE:32:v7"), + @PlatformInfo(langID = "ARM:LEBE:32:v7LEInstruction"), + @PlatformInfo(langID = "ARM:BE:32:v7"), + @PlatformInfo(langID = "ARM:LE:32:Cortex"), + @PlatformInfo(langID = "ARM:BE:32:Cortex"), + @PlatformInfo(langID = "ARM:LE:32:v6"), + @PlatformInfo(langID = "ARM:BE:32:v6"), + @PlatformInfo(langID = "ARM:LE:32:v5t"), + @PlatformInfo(langID = "ARM:BE:32:v5t"), + @PlatformInfo(langID = "ARM:LE:32:v5"), + @PlatformInfo(langID = "ARM:BE:32:v5"), + @PlatformInfo(langID = "ARM:LE:32:v4t"), + @PlatformInfo(langID = "ARM:BE:32:v4t"), + @PlatformInfo(langID = "ARM:LE:32:v4"), + @PlatformInfo(langID = "ARM:BE:32:v4"), }) public class ArmDisassemblyInject implements DisassemblyInject { protected static final long THUMB_BIT = 0x20; @@ -59,15 +59,15 @@ public class ArmDisassemblyInject implements DisassemblyInject { } @Override - public void pre(PluginTool tool, TraceDisassembleCommand command, Trace trace, - Language language, long snap, TraceThread thread, AddressSetView startSet, - AddressSetView restricted) { + public void pre(PluginTool tool, TraceDisassembleCommand command, TracePlatform platform, + long snap, TraceThread thread, AddressSetView startSet, AddressSetView restricted) { /** * TODO: There are probably several avenues to figure the TMode. The most important, I think - * is the cpsr register, when it's available. For auto-pc, the trace recorder ought to have - * recorded cpsr at the recorded tick. + * is the cpsr register, when it's available. For auto-pc, the target ought to have recorded + * cpsr at the current snapshot. */ + Language language = platform.getLanguage(); Register cpsrReg = language.getRegister("cpsr"); Register tModeReg = language.getRegister("TMode"); @@ -77,24 +77,22 @@ public class ArmDisassemblyInject implements DisassemblyInject { return; } - TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false); + TraceMemorySpace regs = + platform.getTrace().getMemoryManager().getMemoryRegisterSpace(thread, false); /** * Some variants (particularly Cortex-M) are missing cpsr. This seems to indicate it only * supports THUMB. There is an epsr (xpsr in gdb), but we don't have it in our models, and * its TMode bit must be set, or it will fault. + * + * TODO: If registers are recorded as generic objects, then we can find epsr/xpsr whether or + * not its in the Sleigh model. */ - if (regs == null || regs.getState(snap, cpsrReg) != TraceMemoryState.KNOWN) { + if (regs == null || regs.getState(platform, snap, cpsrReg) != TraceMemoryState.KNOWN) { command.setInitialContext(new RegisterValue(tModeReg, BigInteger.ONE)); return; } - /** - * TODO: Once we have register mapping figured out for object-based traces, we need to have - * this check the cpsr register there, instead. Better yet, regarding epsr and xpsr, we can - * actually check them, even though they don't exist in the slaspec, because we have access - * to the raw recorded register objects. - */ - RegisterValue cpsrVal = regs.getValue(snap, cpsrReg); + RegisterValue cpsrVal = regs.getValue(platform, snap, cpsrReg); if (isThumbMode(cpsrVal)) { command.setInitialContext(new RegisterValue(tModeReg, BigInteger.ONE)); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java index b9c3d56f02..e56248a251 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengDebuggerPlatformOpinion.java @@ -24,6 +24,7 @@ import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.lang.*; import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.target.TraceObject; public class DbgengDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion { @@ -39,7 +40,7 @@ public class DbgengDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpini // TODO: Map registers: efl,rfl,rflags->eflags @Override - protected Collection getDisassemblyInjections(TraceObject object) { + protected Collection getDisassemblyInjections(TracePlatform platform) { return INJECTS; } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java index 3c6b3960c6..6e9d4d774f 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/platform/dbgeng/DbgengX64DisassemblyInject.java @@ -22,7 +22,7 @@ import java.util.Set; import java.util.stream.Collectors; import ghidra.app.plugin.core.debug.disassemble.*; -import ghidra.app.plugin.core.debug.disassemble.DisassemblyInjectInfo.CompilerInfo; +import ghidra.app.plugin.core.debug.disassemble.DisassemblyInjectInfo.PlatformInfo; import ghidra.app.services.DebuggerTargetService; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.bin.MemBufferByteProvider; @@ -35,6 +35,7 @@ import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemBuffer; import ghidra.program.util.ProgramContextImpl; import ghidra.trace.model.Trace; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.modules.TraceModule; import ghidra.trace.model.thread.TraceThread; import ghidra.util.Msg; @@ -42,9 +43,9 @@ import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @DisassemblyInjectInfo( - compilers = { - @CompilerInfo(langID = "x86:LE:64:default", compilerID = "windows"), - @CompilerInfo(langID = "x86:LE:64:default", compilerID = "clangwindows"), + platforms = { + @PlatformInfo(langID = "x86:LE:64:default", compilerID = "windows"), + @PlatformInfo(langID = "x86:LE:64:default", compilerID = "clangwindows"), }) public class DbgengX64DisassemblyInject implements DisassemblyInject { @@ -53,13 +54,13 @@ public class DbgengX64DisassemblyInject implements DisassemblyInject { } @Override - public void pre(PluginTool tool, TraceDisassembleCommand command, Trace trace, - Language language, long snap, TraceThread thread, AddressSetView startSet, - AddressSetView restricted) { + public void pre(PluginTool tool, TraceDisassembleCommand command, TracePlatform platform, + long snap, TraceThread thread, AddressSetView startSet, AddressSetView restricted) { AddressRange first = startSet.getFirstRange(); if (first == null) { return; } + Trace trace = platform.getTrace(); DebuggerTargetService targetService = tool.getService(DebuggerTargetService.class); Target target = targetService == null ? null : targetService.getTarget(trace); Collection modules = @@ -75,6 +76,8 @@ public class DbgengX64DisassemblyInject implements DisassemblyInject { return; } Mode mode = modes.iterator().next(); + + Language language = platform.getLanguage(); Register longModeReg = language.getRegister("longMode"); Register addrsizeReg = language.getRegister("addrsize"); Register opsizeReg = language.getRegister("opsize"); 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 9f9c9f4e01..0fad550f77 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 @@ -21,12 +21,15 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.jdom.JDOMException; + import db.Transaction; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingUtils.Extrema; import ghidra.app.services.DebuggerEmulationService; import ghidra.dbg.target.*; -import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.dbg.target.schema.*; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; import ghidra.dbg.util.*; import ghidra.framework.model.DomainFile; import ghidra.program.model.address.*; @@ -57,6 +60,70 @@ public class ProgramEmulationUtils { private ProgramEmulationUtils() { } + public static final String EMU_CTX_XML = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + """; + public static final SchemaContext EMU_CTX; + public static final TargetObjectSchema EMU_SESSION_SCHEMA; + static { + try { + EMU_CTX = XmlSchemaContext.deserialize(EMU_CTX_XML); + } + catch (JDOMException e) { + throw new AssertionError(e); + } + EMU_SESSION_SCHEMA = EMU_CTX.getSchema(new SchemaName("EmuSession")); + } + public static final String BLOCK_NAME_STACK = "STACK"; /** @@ -193,11 +260,8 @@ public class ProgramEmulationUtils { // N.B. Bytes will be loaded lazily } - public static PathPattern computePattern(Trace trace, Class iface) { - TargetObjectSchema root = trace.getObjectManager().getRootSchema(); - if (root == null) { - return new PathPattern(PathUtils.parse("Memory[]")); - } + public static PathPattern computePattern(TargetObjectSchema root, Trace trace, + Class iface) { PathMatcher matcher = root.searchFor(iface, true); PathPattern pattern = matcher.getSingletonPattern(); if (pattern == null || pattern.countWildcards() != 1) { @@ -208,11 +272,19 @@ public class ProgramEmulationUtils { } public static PathPattern computePatternRegion(Trace trace) { - return computePattern(trace, TargetMemoryRegion.class); + TargetObjectSchema root = trace.getObjectManager().getRootSchema(); + if (root == null) { + return new PathPattern(PathUtils.parse("Memory[]")); + } + return computePattern(root, trace, TargetMemoryRegion.class); } public static PathPattern computePatternThread(Trace trace) { - return computePattern(trace, TargetThread.class); + TargetObjectSchema root = trace.getObjectManager().getRootSchema(); + if (root == null) { + return new PathPattern(PathUtils.parse("Threads[]")); + } + return computePattern(root, trace, TargetThread.class); } /** @@ -429,6 +501,7 @@ public class ProgramEmulationUtils { try { trace = new DBTrace(getTraceName(program), program.getCompilerSpec(), consumer); try (Transaction tx = trace.openTransaction("Emulate")) { + trace.getObjectManager().createRootObject(EMU_SESSION_SCHEMA); TraceSnapshot initial = trace.getTimeManager().createSnapshot(EMULATION_STARTED_AT + pc); long snap = initial.getKey(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java index 72b164ca50..b5065a5f3a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/stack/StackUnwinder.java @@ -178,13 +178,19 @@ public class StackUnwinder { Address pcVal = null; TraceThread thread = coordinates.getThread(); long viewSnap = coordinates.getViewSnap(); - TraceStack stack = trace.getStackManager().getLatestStack(thread, viewSnap); - if (stack != null) { - TraceStackFrame frame = stack.getFrame(level, false); - if (frame != null) { - pcVal = frame.getProgramCounter(viewSnap); + try { + TraceStack stack = trace.getStackManager().getLatestStack(thread, viewSnap); + if (stack != null) { + TraceStackFrame frame = stack.getFrame(level, false); + if (frame != null) { + pcVal = frame.getProgramCounter(viewSnap); + } } } + catch (IllegalStateException e) { + // Schema does not specify a stack + // leave pcVal = null, so we'll get it from registers + } TraceMemorySpace regs = Objects.requireNonNull( trace.getMemoryManager().getMemoryRegisterSpace(thread, level, false), "Frame must have a register bank"); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblyTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblyTest.java index 54dcabbd05..84c4f3be3b 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblyTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/disassemble/DebuggerDisassemblyTest.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.core.debug.disassemble; import static org.junit.Assert.*; +import java.io.ByteArrayInputStream; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Objects; @@ -24,7 +25,8 @@ import java.util.Set; import java.util.function.Predicate; import java.util.function.Supplier; -import org.junit.*; +import org.junit.Before; +import org.junit.Test; import db.Transaction; import docking.action.DockingActionIf; @@ -32,11 +34,13 @@ import generic.Unique; import ghidra.app.context.ListingActionContext; import ghidra.app.plugin.core.assembler.AssemblerPluginTestHelper; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; +import ghidra.app.plugin.core.debug.gui.action.LoadEmulatorAutoReadMemorySpec; import ghidra.app.plugin.core.debug.gui.listing.*; import ghidra.app.plugin.core.debug.service.control.DebuggerControlServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlugin; -import ghidra.app.services.DebuggerControlService; -import ghidra.app.services.DebuggerPlatformService; +import ghidra.app.services.*; import ghidra.dbg.target.TargetEnvironment; import ghidra.dbg.target.schema.SchemaContext; import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; @@ -57,6 +61,7 @@ import ghidra.trace.database.target.DBTraceObject; import ghidra.trace.database.target.DBTraceObjectManager; import ghidra.trace.model.Lifespan; import ghidra.trace.model.guest.TraceGuestPlatform; +import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.memory.TraceObjectMemoryRegion; import ghidra.trace.model.program.TraceProgramView; @@ -65,6 +70,7 @@ import ghidra.trace.model.target.TraceObject.ConflictResolution; import ghidra.trace.model.target.TraceObjectKeyPath; import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.task.TaskMonitor; public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { @@ -149,6 +155,10 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { listingProvider.setAutoDisassemble(true); } + protected void enableLoadEmulator() throws Throwable { + runSwing(() -> listingProvider.setAutoReadMemorySpec(new LoadEmulatorAutoReadMemorySpec())); + } + protected DebuggerListingActionContext createActionContext(Address start, int len) { TraceProgramView view = tb.trace.getProgramView(); ProgramSelection sel = new ProgramSelection(start, start.addWrap(len - 1)); @@ -220,7 +230,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { return thread; } - protected void setLegacyProgramCounter(long offset, TraceThread thread, long snap) { + protected void setLegacyProgramCounterInStack(long offset, TraceThread thread, long snap) { try (Transaction tx = tb.startTransaction()) { DBTraceStackManager manager = tb.trace.getStackManager(); TraceStack stack = manager.getStack(thread, snap, true); @@ -229,6 +239,15 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { } } + protected void setLegacyProgramCounterInRegs(long offset, TraceThread thread, long snap) { + try (Transaction tx = tb.startTransaction()) { + DBTraceMemoryManager memory = tb.trace.getMemoryManager(); + DBTraceMemorySpace regs = memory.getMemoryRegisterSpace(thread, true); + Register pc = tb.language.getProgramCounter(); + regs.setValue(0, new RegisterValue(pc, BigInteger.valueOf(offset))); + } + } + protected void createLegacyTrace(String langID, long offset, Supplier byteSupplier) throws Throwable { createAndOpenTrace(langID); @@ -259,7 +278,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { } @Test - public void testAutoDisasembleReDisasembleOffcut() throws Throwable { + public void testAutoDisasembleReDisasembleX8664Offcut() throws Throwable { enableAutoDisassembly(); createLegacyTrace("x86:LE:64:default", 0x00400000, () -> tb.buf(0xeb, 0xff, 0xc0)); @@ -268,7 +287,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { thread = tb.getOrAddThread("Thread 1", 0); } - setLegacyProgramCounter(0x00400000, thread, 0); + setLegacyProgramCounterInStack(0x00400000, thread, 0); waitForPass(() -> { DBTraceInstructionsMemoryView instructions = tb.trace.getCodeManager().instructions(); @@ -281,7 +300,7 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { }); // The jump will advance one byte. Just simulate that by updating the stack and/or regs - setLegacyProgramCounter(0x00400001, thread, 1); + setLegacyProgramCounterInStack(0x00400001, thread, 1); traceManager.activateSnap(1); waitForPass(() -> { @@ -292,6 +311,93 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { }); } + @Test + public void testAutoDisassembleReDisassembleX8664OffcutByEmulation() throws Throwable { + DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + enableAutoDisassembly(); + createLegacyTrace("x86:LE:64:default", 0x00400000, () -> tb.buf(0xeb, 0xff, 0xc0)); + + TraceThread thread; + try (Transaction tx = tb.startTransaction()) { + thread = tb.getOrAddThread("Thread 1", 0); + } + + setLegacyProgramCounterInRegs(0x00400000, thread, 0); + + waitForPass(() -> { + DBTraceInstructionsMemoryView instructions = tb.trace.getCodeManager().instructions(); + assertMnemonic("JMP", instructions.getAt(0, tb.addr(0x00400000))); + /** + * Depending on preference for branch or fall-through, the disassembler may or may not + * proceed to the following instructions. I don't really care, since the test is the the + * JMP gets deleted after the update to PC. + */ + }); + + TraceSchedule schedule = TraceSchedule.snap(0).steppedForward(thread, 1); + // Pre-load the cache, so I don't have to wait for background async emulation + long viewSnap = emuService.emulate(tb.trace, schedule, monitor); + traceManager.activateTime(schedule); + waitForSwing(); + assertEquals(viewSnap, traceManager.getCurrentView().getSnap()); + + waitForPass(() -> { + DBTraceInstructionsMemoryView instructions = tb.trace.getCodeManager().instructions(); + assertNull(instructions.getAt(viewSnap, tb.addr(0x00400000))); + assertMnemonic("INC", instructions.getAt(viewSnap, tb.addr(0x00400001))); + assertNull(instructions.getAt(viewSnap, tb.addr(0x00400003))); + }); + } + + @Test + public void testAutoDisassembleReDisassembleX8664OffcutByProgEmu() throws Throwable { + DebuggerEmulationService emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + + createProgram(getSLEIGH_X86_64_LANGUAGE()); + Address start; + try (Transaction tx = program.openTransaction("Load")) { + start = program.getAddressFactory().getDefaultAddressSpace().getAddress(0x00400000); + program.getMemory() + .createInitializedBlock(".text", start, + new ByteArrayInputStream(arr("ebffc0")), 3, monitor, false); + } + intoProject(program); + + useTrace(ProgramEmulationUtils.launchEmulationTrace(program, start, this)); + tb.trace.release(this); + TraceThread thread = Unique.assertOne(tb.trace.getThreadManager().getAllThreads()); + + traceManager.openTrace(tb.trace); + traceManager.activateThread(thread); + + enableLoadEmulator(); + enableAutoDisassembly(); + + waitForPass(() -> { + DBTraceInstructionsMemoryView instructions = tb.trace.getCodeManager().instructions(); + assertMnemonic("JMP", instructions.getAt(0, tb.addr(0x00400000))); + /** + * Depending on preference for branch or fall-through, the disassembler may or may not + * proceed to the following instructions. I don't really care, since the test is the the + * JMP gets deleted after the update to PC. + */ + }); + + TraceSchedule schedule = TraceSchedule.snap(0).steppedForward(thread, 1); + // Pre-load the cache, so I don't have to wait for background async emulation + long viewSnap = emuService.emulate(tb.trace, schedule, monitor); + traceManager.activateTime(schedule); + waitForSwing(); + assertEquals(viewSnap, traceManager.getCurrentView().getSnap()); + + waitForPass(() -> { + DBTraceInstructionsMemoryView instructions = tb.trace.getCodeManager().instructions(); + assertNull(instructions.getAt(viewSnap, tb.addr(0x00400000))); + assertMnemonic("INC", instructions.getAt(viewSnap, tb.addr(0x00400001))); + assertNull(instructions.getAt(viewSnap, tb.addr(0x00400003))); + }); + } + @Test public void testAutoDisassembleGuestX8664WithPcInRegs() throws Throwable { enableAutoDisassembly(); @@ -374,15 +480,28 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { public void testCurrentDisassembleActionGuestArm() throws Throwable { TraceObjectThread thread = createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf(0x1e, 0xff, 0x2f, 0xe1)); - - // Set up registers so injects will select ARM - // TODO - - Address start = tb.addr(0x00400000); + traceManager.activateThread(thread); + waitForSwing(); // Ensure the mapper is added to the trace assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0)); + TracePlatform arm = Unique.assertOne(tb.trace.getPlatformManager().getGuestPlatforms()); + // If cpsr is UNKNOWN, inject will assume, e.g., Cortex-M, and set THUMB mode. + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager() + .createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Stack[0].Registers")) + .insert(Lifespan.nowOn(0), ConflictResolution.DENY); + DBTraceMemorySpace regs = Objects.requireNonNull( + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true)); + Register cpsr = arm.getLanguage().getRegister("cpsr"); + regs.setValue(arm, 0, new RegisterValue(cpsr, BigInteger.ZERO)); + } + waitForDomainObject(tb.trace); + + Address start = tb.addr(0x00400000); + ListingActionContext actionContext = createActionContext(start, 4); performAction(disassemblerPlugin.actionDisassemble, actionContext, true); waitForTasks(); @@ -394,19 +513,30 @@ public class DebuggerDisassemblyTest extends AbstractGhidraHeadedDebuggerTest { } @Test - @Ignore("TODO") public void testCurrentDisassembleActionGuestThumb() throws Throwable { TraceObjectThread thread = createPolyglotTrace("armv8le", 0x00400000, () -> tb.buf(0x70, 0x47)); - - // Set up registers so injects will select THUMB - // TODO - - Address start = tb.addr(0x00400000); + traceManager.activateThread(thread); + waitForSwing(); // Ensure the mapper is added to the trace assertNotNull(platformService.getMapper(tb.trace, thread.getObject(), 0)); + TracePlatform arm = Unique.assertOne(tb.trace.getPlatformManager().getGuestPlatforms()); + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager() + .createObject( + TraceObjectKeyPath.parse("Targets[0].Threads[0].Stack[0].Registers")) + .insert(Lifespan.nowOn(0), ConflictResolution.DENY); + DBTraceMemorySpace regs = Objects.requireNonNull( + tb.trace.getMemoryManager().getMemoryRegisterSpace(thread, true)); + Register cpsr = arm.getLanguage().getRegister("cpsr"); + regs.setValue(arm, 0, new RegisterValue(cpsr, BigInteger.ONE.shiftLeft(5))); + } + waitForDomainObject(tb.trace); + + Address start = tb.addr(0x00400000); + ListingActionContext actionContext = createActionContext(start, 4); performAction(disassemblerPlugin.actionDisassemble, actionContext, true); waitForTasks(); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java index 2909b43bd3..b7ebeba866 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/mapping/TestDebuggerPlatformOpinion.java @@ -50,7 +50,7 @@ public class TestDebuggerPlatformOpinion extends AbstractDebuggerPlatformOpinion @Override public int getConfidence() { - return 1; + return HostDebuggerPlatformOpinion.Offers.HOST.getConfidence() + 1; } @Override 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 8a48851d4c..a8ab65ddc9 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 @@ -33,12 +33,14 @@ import ghidra.app.plugin.assembler.*; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerTest; import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOpinion; +import ghidra.app.plugin.core.debug.service.emulation.data.DefaultPcodeDebuggerAccess; import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlugin; import ghidra.app.services.DebuggerEmulationService.EmulationResult; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.debug.api.platform.DebuggerPlatformMapper; import ghidra.debug.api.tracemgr.DebuggerCoordinates; +import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.DecodePcodeExecutionException; import ghidra.pcode.exec.InterruptPcodeExecutionException; import ghidra.pcode.utils.Utils; @@ -57,6 +59,7 @@ import ghidra.trace.model.guest.TracePlatform; import ghidra.trace.model.memory.TraceMemoryManager; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.Scheduler; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.task.TaskMonitor; @@ -114,8 +117,8 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe assertEquals(new BigInteger("0000", 16), regs.getViewValue(0, regR0).getUnsignedValue()); assertEquals(new BigInteger("1234", 16), regs.getViewValue(0, regR1).getUnsignedValue()); - long scratch = - emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); + long scratch = emulationPlugin.emulate(trace, + TraceSchedule.snap(0).steppedForward(thread, 1), TaskMonitor.DUMMY); assertEquals(new BigInteger("00400002", 16), regs.getViewValue(scratch, regPC).getUnsignedValue()); @@ -171,8 +174,8 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe assertEquals(new BigInteger("00000400", 16), regs.getViewValue(0, regR1).getUnsignedValue()); - long scratch = - emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); + long scratch = emulationPlugin.emulate(trace, + TraceSchedule.snap(0).steppedForward(thread, 1), TaskMonitor.DUMMY); assertEquals(new BigInteger("00000402", 16), regs.getViewValue(scratch, regPC).getUnsignedValue()); @@ -220,6 +223,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe Trace trace = traceManager.getCurrentTrace(); assertNotNull(trace); + TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads()); TraceMemoryManager mem = trace.getMemoryManager(); assertEquals(new BigInteger("000100", 16), @@ -229,8 +233,8 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe assertEquals(new BigInteger("0800", 16), mem.getViewValue(0, regW1).getUnsignedValue()); - long scratch = - emulationPlugin.emulate(trace, TraceSchedule.parse("0:t0-1"), TaskMonitor.DUMMY); + long scratch = emulationPlugin.emulate(trace, + TraceSchedule.snap(0).steppedForward(thread, 1), TaskMonitor.DUMMY); assertEquals(new BigInteger("000102", 16), mem.getViewValue(scratch, regPC).getUnsignedValue()); @@ -377,7 +381,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread)); - assertEquals(TraceSchedule.parse("0:t0-1"), result.schedule()); + assertEquals(TraceSchedule.snap(0).steppedForward(thread, 1), result.schedule()); assertTrue(result.error() instanceof DecodePcodeExecutionException); long scratch = result.snapshot(); @@ -432,7 +436,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread)); - assertEquals(TraceSchedule.parse("0:t0-1"), result.schedule()); + assertEquals(TraceSchedule.snap(0).steppedForward(thread, 1), result.schedule()); assertTrue(result.error() instanceof InterruptPcodeExecutionException); long scratch = result.snapshot(); @@ -496,13 +500,13 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe // This is already testing if the one set at the entry is ignored EmulationResult result1 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), TraceSchedule.snap(0), monitor, Scheduler.oneThread(thread)); - assertEquals(TraceSchedule.parse("0:t0-1"), result1.schedule()); + assertEquals(TraceSchedule.snap(0).steppedForward(thread, 1), result1.schedule()); assertTrue(result1.error() instanceof InterruptPcodeExecutionException); // This will test if the one just hit gets ignored EmulationResult result2 = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), result1.schedule(), monitor, Scheduler.oneThread(thread)); - assertEquals(TraceSchedule.parse("0:t0-2"), result2.schedule()); + assertEquals(TraceSchedule.snap(0).steppedForward(thread, 2), result2.schedule()); assertTrue(result1.error() instanceof InterruptPcodeExecutionException); } @@ -558,7 +562,8 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread)); - assertEquals(TraceSchedule.parse("0:t0-1.t0-2"), result.schedule()); + assertEquals(TraceSchedule.snap(0).steppedForward(thread, 1).steppedPcodeForward(thread, 2), + result.schedule()); assertTrue(result.error() instanceof InterruptPcodeExecutionException); long scratch = result.snapshot(); @@ -619,7 +624,7 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe EmulationResult result = emulationPlugin.run(trace.getPlatformManager().getHostPlatform(), TraceSchedule.snap(0), TaskMonitor.DUMMY, Scheduler.oneThread(thread)); - assertEquals(TraceSchedule.parse("0:t0-1"), result.schedule()); + assertEquals(TraceSchedule.snap(0).steppedForward(thread, 1), result.schedule()); assertTrue(result.error() instanceof InterruptPcodeExecutionException); long scratch = result.snapshot(); @@ -675,11 +680,11 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false); // Step as written to fill the cache - waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-1")), - ActivationCause.USER)); + waitOn(traceManager.activateAndNotify( + current.time(TraceSchedule.snap(0).steppedForward(thread, 1)), ActivationCause.USER)); waitForSwing(); - waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-2")), - ActivationCause.USER)); + waitOn(traceManager.activateAndNotify( + current.time(TraceSchedule.snap(0).steppedForward(thread, 2)), ActivationCause.USER)); waitForSwing(); long scratch = traceManager.getCurrentView().getSnap(); @@ -699,11 +704,11 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe } // Check the cache is still valid - waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-1")), - ActivationCause.USER)); + waitOn(traceManager.activateAndNotify( + current.time(TraceSchedule.snap(0).steppedForward(thread, 1)), ActivationCause.USER)); waitForSwing(); - waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-2")), - ActivationCause.USER)); + waitOn(traceManager.activateAndNotify( + current.time(TraceSchedule.snap(0).steppedForward(thread, 2)), ActivationCause.USER)); waitForSwing(); assertEquals(scratch, traceManager.getCurrentView().getSnap()); assertEquals(new BigInteger("1234", 16), @@ -746,4 +751,37 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerTe TraceMemorySpace regs = trace.getMemoryManager().getMemoryRegisterSpace(thread, false); assertEquals(new BigInteger("2234", 16), regs.getViewValue(0, regSP).getUnsignedValue()); } + + @Test + public void testNewThreadAfterLoadTrace() throws Exception { + createAndOpenTrace(); + createProgramFromTrace(); + intoProject(program); + + long restartEmuSnap = 3; + try (Transaction tx = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(ProgramEmulationUtils.EMU_SESSION_SCHEMA); + tb.trace.getTimeManager().getSnapshot(restartEmuSnap, true); + } + traceManager.activateTrace(tb.trace); + traceManager.activateSnap(restartEmuSnap); + + try (Transaction tx = tb.startTransaction()) { + TracePlatform host = tb.trace.getPlatformManager().getHostPlatform(); + DefaultPcodeDebuggerAccess access = + new DefaultPcodeDebuggerAccess(tool, null, host, restartEmuSnap); + BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(access); + + TraceSnapshot snapshot = + tb.trace.getTimeManager().createSnapshot("created new emulator thread"); + long newSnap = snapshot.getKey(); + emulator.writeDown(host, newSnap, newSnap); + + TraceThread newTraceThread = ProgramEmulationUtils.doLaunchEmulationThread(tb.trace, + newSnap, program, tb.addr(0x00400000), addr(program, 0x00400000)); + newTraceThread.setName("MyThread"); + + PcodeThread newEmuThread = emulator.newThread(newTraceThread.getPath()); + } + } } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java index 937f0f89d1..4898e6ed2e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/debug/flatapi/FlatDebuggerAPITest.java @@ -497,9 +497,11 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerTest { Address entry = createEmulatableProgram(); flat.emulateLaunch(entry); - flat.emulate(TraceSchedule.parse("0:t0-1"), monitor); + TraceSchedule schedule = + traceManager.getCurrent().getTime().steppedForward(traceManager.getCurrentThread(), 1); + flat.emulate(schedule, monitor); - assertEquals(TraceSchedule.parse("0:t0-1"), traceManager.getCurrent().getTime()); + assertEquals(schedule, traceManager.getCurrent().getTime()); } @Test @@ -507,12 +509,14 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerTest { Address entry = createEmulatableProgram(); flat.emulateLaunch(entry); + TraceSchedule schedule = + traceManager.getCurrent().getTime().steppedForward(traceManager.getCurrentThread(), 1); flat.stepEmuInstruction(1, monitor); - assertEquals(TraceSchedule.parse("0:t0-1"), traceManager.getCurrent().getTime()); + assertEquals(schedule, traceManager.getCurrent().getTime()); flat.stepEmuInstruction(-1, monitor); - assertEquals(TraceSchedule.parse("0"), traceManager.getCurrent().getTime()); + assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime()); } @Test @@ -520,12 +524,15 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerTest { Address entry = createEmulatableProgram(); flat.emulateLaunch(entry); + TraceSchedule schedule = traceManager.getCurrent() + .getTime() + .steppedPcodeForward(traceManager.getCurrentThread(), 1); flat.stepEmuPcodeOp(1, monitor); - assertEquals(TraceSchedule.parse("0:.t0-1"), traceManager.getCurrent().getTime()); + assertEquals(schedule, traceManager.getCurrent().getTime()); flat.stepEmuPcodeOp(-1, monitor); - assertEquals(TraceSchedule.parse("0"), traceManager.getCurrent().getTime()); + assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime()); } @Test @@ -533,12 +540,14 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerTest { Address entry = createEmulatableProgram(); flat.emulateLaunch(entry); + TraceSchedule schedule = + traceManager.getCurrent().getTime().skippedForward(traceManager.getCurrentThread(), 1); flat.skipEmuInstruction(1, monitor); - assertEquals(TraceSchedule.parse("0:t0-s1"), traceManager.getCurrent().getTime()); + assertEquals(schedule, traceManager.getCurrent().getTime()); flat.skipEmuInstruction(-1, monitor); - assertEquals(TraceSchedule.parse("0"), traceManager.getCurrent().getTime()); + assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime()); } @Test @@ -546,12 +555,15 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerTest { Address entry = createEmulatableProgram(); flat.emulateLaunch(entry); + TraceSchedule schedule = traceManager.getCurrent() + .getTime() + .skippedPcodeForward(traceManager.getCurrentThread(), 1); flat.skipEmuPcodeOp(1, monitor); - assertEquals(TraceSchedule.parse("0:.t0-s1"), traceManager.getCurrent().getTime()); + assertEquals(schedule, traceManager.getCurrent().getTime()); flat.skipEmuPcodeOp(-1, monitor); - assertEquals(TraceSchedule.parse("0"), traceManager.getCurrent().getTime()); + assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime()); } @Test @@ -559,12 +571,16 @@ public class FlatDebuggerAPITest extends AbstractGhidraHeadedDebuggerTest { Address entry = createEmulatableProgram(); flat.emulateLaunch(entry); + TraceSchedule schedule = traceManager.getCurrent() + .getTime() + .patched(traceManager.getCurrentThread(), + traceManager.getCurrentPlatform().getLanguage(), "r0=0x321"); flat.patchEmu("r0=0x321", monitor); - assertEquals(TraceSchedule.parse("0:t0-{r0=0x321}"), traceManager.getCurrent().getTime()); + assertEquals(schedule, traceManager.getCurrent().getTime()); flat.stepEmuInstruction(-1, monitor); - assertEquals(TraceSchedule.parse("0"), traceManager.getCurrent().getTime()); + assertEquals(TraceSchedule.ZERO, traceManager.getCurrent().getTime()); } @Test 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 5c54750deb..b1067cd7ba 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 @@ -573,7 +573,12 @@ public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager public I getObjectByPath(long snap, String path, Class iface) { try (LockHold hold = trace.lockRead()) { - return getObjectsByPath(Lifespan.at(snap), TraceObjectKeyPath.parse(path)).findAny() + TraceObjectKeyPath parsed = TraceObjectKeyPath.parse(path); + DBTraceObject object = getObjectByCanonicalPath(parsed); + if (object != null) { + return object.queryInterface(iface); + } + return getObjectsByPath(Lifespan.at(snap), parsed).findAny() .map(o -> o.queryInterface(iface)) .orElse(null); } 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 72a429dc5a..7ac6e113cd 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 @@ -803,8 +803,8 @@ public class ToyDBTraceBuilder implements AutoCloseable { * @param path the path pattern * @return the object or null */ - public TraceObject objAny(String pat) { - return objAny(pat, Lifespan.at(0)); + public TraceObject objAny(String path) { + return objAny(path, Lifespan.at(0)); } public TraceObject objAny(String path, Lifespan span) {