From 46a620f68733706f26343a93a76d7151931b39c5 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 8 Feb 2023 16:40:13 -0500 Subject: [PATCH] GP-2970: Add 'Invalidate Emulator Cache' action. --- .../core/debug/gui/DebuggerResources.java | 59 --------- .../DebuggerEmulationServicePlugin.java | 115 +++++++++++++++++- .../DebuggerTraceManagerServicePlugin.java | 13 +- .../services/DebuggerEmulationService.java | 15 ++- .../DebuggerEmulationServiceTest.java | 82 +++++++++++++ .../certification.manifest | 1 + .../data/tracemodeling.theme.properties | 7 ++ .../java/ghidra/trace/database/DBTrace.java | 11 ++ .../trace/database/DBTraceContentHandler.java | 3 +- .../trace/database/time/DBTraceSnapshot.java | 43 +++++-- .../main/java/ghidra/trace/model/Trace.java | 21 +++- .../trace/model/time/TraceSnapshot.java | 16 +++ .../trace/util/DefaultTraceChangeType.java | 6 + 13 files changed, 309 insertions(+), 83 deletions(-) create mode 100644 Ghidra/Debug/Framework-TraceModeling/data/tracemodeling.theme.properties diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 1a44a2e5c5..ea8470624e 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -475,65 +475,6 @@ public interface DebuggerResources { } } - interface EmulateProgramAction { - String NAME = "Emulate Program in new Trace"; - String DESCRIPTION = "Emulate the current program in a new trace starting at the cursor"; - Icon ICON = ICON_EMULATE; - String GROUP = GROUP_GENERAL; - String HELP_ANCHOR = "emulate_program"; - - static ActionBuilder builder(Plugin owner) { - String ownerName = owner.getName(); - return new ActionBuilder(NAME, ownerName) - .description(DESCRIPTION) - .toolBarIcon(ICON) - .toolBarGroup(GROUP) - .menuPath(DebuggerPluginPackage.NAME, NAME) - .menuIcon(ICON) - .menuGroup(GROUP) - .popupMenuPath(NAME) - .popupMenuIcon(ICON) - .popupMenuGroup(GROUP) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); - } - } - - interface EmulateAddThreadAction { - String NAME = "Add Emulated Thread to Trace"; - String DESCRIPTION = "Add an emulated thread to the current trace starting here"; - Icon ICON = ICON_THREAD; - String GROUP = GROUP_GENERAL; - String HELP_ANCHOR = "add_emulated_thread"; - - static ActionBuilder builder(Plugin owner) { - String ownerName = owner.getName(); - return new ActionBuilder(NAME, ownerName) - .description(DESCRIPTION) - .menuPath(DebuggerPluginPackage.NAME, NAME) - .menuIcon(ICON) - .menuGroup(GROUP) - .popupMenuPath(NAME) - .popupMenuIcon(ICON) - .popupMenuGroup(GROUP) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); - } - } - - interface ConfigureEmulatorAction { - String NAME = "Configure Emulator"; - String DESCRIPTION = "Choose and configure the current emulator"; - String GROUP = GROUP_MAINTENANCE; - String HELP_ANCHOR = "configure_emulator"; - - static ToggleActionBuilder builder(Plugin owner) { - String ownerName = owner.getName(); - return new ToggleActionBuilder(NAME, ownerName) - .description(DESCRIPTION) - .menuGroup(GROUP) - .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); - } - } - abstract class AbstractQuickLaunchAction extends DockingAction { public static final String NAME = "Quick Launch"; public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon? 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 76b29cdcd5..98b7b5b5ac 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 @@ -21,13 +21,17 @@ import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import javax.swing.Icon; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.commons.lang3.exception.ExceptionUtils; +import docking.ActionContext; import docking.action.DockingAction; import docking.action.ToggleDockingAction; +import docking.action.builder.ActionBuilder; +import docking.action.builder.ToggleActionBuilder; import ghidra.app.context.ProgramLocationActionContext; import ghidra.app.events.ProgramActivatedPluginEvent; import ghidra.app.events.ProgramClosedPluginEvent; @@ -35,13 +39,14 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; +import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.*; import ghidra.async.AsyncLazyMap; import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.framework.plugintool.util.PluginStatus; -import ghidra.pcode.emu.PcodeMachine.*; +import ghidra.pcode.emu.PcodeMachine.AccessKind; +import ghidra.pcode.emu.PcodeMachine.SwiMode; import ghidra.pcode.exec.InjectionErrorPcodeExecutionException; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; @@ -54,6 +59,7 @@ import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.*; import ghidra.trace.model.time.schedule.Scheduler.RunResult; +import ghidra.util.HelpLocation; import ghidra.util.Msg; import ghidra.util.classfinder.ClassSearcher; import ghidra.util.database.UndoableTransaction; @@ -83,6 +89,82 @@ import ghidra.util.task.TaskMonitor; public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService { protected static final int MAX_CACHE_SIZE = 5; + interface EmulateProgramAction { + String NAME = "Emulate Program in new Trace"; + String DESCRIPTION = "Emulate the current program in a new trace starting at the cursor"; + Icon ICON = DebuggerResources.ICON_EMULATE; + String GROUP = DebuggerResources.GROUP_GENERAL; + String HELP_ANCHOR = "emulate_program"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .toolBarIcon(ICON) + .toolBarGroup(GROUP) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .menuIcon(ICON) + .menuGroup(GROUP) + .popupMenuPath(NAME) + .popupMenuIcon(ICON) + .popupMenuGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface EmulateAddThreadAction { + String NAME = "Add Emulated Thread to Trace"; + String DESCRIPTION = "Add an emulated thread to the current trace starting here"; + Icon ICON = DebuggerResources.ICON_THREAD; + String GROUP = DebuggerResources.GROUP_GENERAL; + String HELP_ANCHOR = "add_emulated_thread"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(DebuggerPluginPackage.NAME, NAME) + .menuIcon(ICON) + .menuGroup(GROUP) + .popupMenuPath(NAME) + .popupMenuIcon(ICON) + .popupMenuGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface ConfigureEmulatorAction { + String NAME = "Configure Emulator"; + String DESCRIPTION = "Choose and configure the current emulator"; + String GROUP = DebuggerResources.GROUP_GENERAL; + String HELP_ANCHOR = "configure_emulator"; + + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + + interface InvalidateEmulatorCacheAction { + String NAME = "Invalidate Emulator Cache"; + String DESCRIPTION = + "Prevent the emulation service from using cached snapshots from the current trace"; + String GROUP = DebuggerResources.GROUP_MAINTENANCE; + String HELP_ANCHOR = "invalidate_cache"; + + static ActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuPath(DebuggerPluginPackage.NAME, ConfigureEmulatorAction.NAME, NAME) + .menuGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + protected static class CacheKey implements Comparable { // TODO: Should key on platform, not trace protected final Trace trace; @@ -273,6 +355,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm DockingAction actionEmulateProgram; DockingAction actionEmulateAddThread; + DockingAction actionInvalidateCache; Map, ToggleDockingAction> // actionsChooseEmulatorFactory = new HashMap<>(); @@ -302,6 +385,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm .popupWhen(this::emulateAddThreadEnabled) .onAction(this::emulateAddThreadActivated) .buildAndInstall(tool); + actionInvalidateCache = InvalidateEmulatorCacheAction.builder(this) + .enabledWhen(this::invalidateCacheEnabled) + .onAction(this::invalidateCacheActivated) + .buildAndInstall(tool); ClassSearcher.addChangeListener(classChangeListener); updateConfigureEmulatorStates(); } @@ -312,7 +399,8 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm private ToggleDockingAction createActionChooseEmulator(DebuggerPcodeEmulatorFactory factory) { ToggleDockingAction action = ConfigureEmulatorAction.builder(this) - .menuPath(DebuggerPluginPackage.NAME, "Configure Emulator", factory.getTitle()) + .menuPath(DebuggerPluginPackage.NAME, ConfigureEmulatorAction.NAME, + factory.getTitle()) .onAction(ctx -> configureEmulatorActivated(factory)) .buildAndInstall(tool); String[] path = action.getMenuBarData().getMenuPath(); @@ -460,6 +548,23 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } } + private boolean invalidateCacheEnabled(ActionContext ignored) { + return traceManager.getCurrentTrace() != null; + } + + private void invalidateCacheActivated(ActionContext ignored) { + DebuggerCoordinates current = traceManager.getCurrent(); + Trace trace = current.getTrace(); + long version = trace.getEmulatorCacheVersion(); + try (UndoableTransaction tid = + UndoableTransaction.start(trace, "Invalidate Emulator Cache")) { + trace.setEmulatorCacheVersion(version + 1); + } + // NB. Success should already display on screen, since it's current. + // Failure should be reported by tool's task manager. + traceManager.materialize(current); + } + private void configureEmulatorActivated(DebuggerPcodeEmulatorFactory factory) { // TODO: Pull up config page. Tool Options? Program/Trace Options? setEmulatorFactory(factory); @@ -493,7 +598,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm protected Map.Entry findNearestPrefix(CacheKey key) { synchronized (cache) { Map.Entry candidate = cache.floorEntry(key); - if (candidate == null) { + if (candidate == null || !candidate.getValue().isValid()) { return null; } if (!candidate.getKey().compareKey(key).related) { @@ -725,7 +830,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm public DebuggerPcodeMachine getCachedEmulator(Trace trace, TraceSchedule time) { CachedEmulator ce = cache.get(new CacheKey(trace.getPlatformManager().getHostPlatform(), time)); - return ce == null ? null : ce.emulator(); + return ce == null || !ce.isValid() ? null : ce.emulator(); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java index afe6991016..8ba1a2c617 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServicePlugin.java @@ -772,12 +772,13 @@ public class DebuggerTraceManagerServicePlugin extends Plugin if (coordinates.getTime().isSnapOnly()) { return coordinates.getSnap(); } - Collection suitable = coordinates.getTrace() - .getTimeManager() - .getSnapshotsWithSchedule(coordinates.getTime()); - if (!suitable.isEmpty()) { - TraceSnapshot found = suitable.iterator().next(); - return found.getKey(); + Trace trace = coordinates.getTrace(); + long version = trace.getEmulatorCacheVersion(); + for (TraceSnapshot snapshot : trace.getTimeManager() + .getSnapshotsWithSchedule(coordinates.getTime())) { + if (snapshot.getVersion() >= version) { + return snapshot.getKey(); + } } return null; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java index 249d8fb16d..d1d5384606 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java @@ -58,7 +58,11 @@ public interface DebuggerEmulationService { /** * An emulator managed by this service */ - record CachedEmulator(Trace trace, DebuggerPcodeMachine emulator) { + record CachedEmulator(Trace trace, DebuggerPcodeMachine emulator, long version) { + public CachedEmulator(Trace trace, DebuggerPcodeMachine emulator) { + this(trace, emulator, trace.getEmulatorCacheVersion()); + } + /** * Get the trace to which the emulator is bound * @@ -83,6 +87,15 @@ public interface DebuggerEmulationService { public DebuggerPcodeMachine emulator() { return emulator; } + + /** + * Check if this cached emulator is still valid + * + * @return true if valid + */ + public boolean isValid() { + return version >= trace.getEmulatorCacheVersion(); + } } /** 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 b332c9508a..0b9bb4e53a 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 @@ -30,11 +30,13 @@ import generic.Unique; import generic.test.category.NightlyCategory; import ghidra.app.plugin.assembler.*; import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformMapper; import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOpinion; import ghidra.app.plugin.core.debug.service.platform.DebuggerPlatformServicePlugin; import ghidra.app.services.DebuggerEmulationService.EmulationResult; +import ghidra.app.services.DebuggerTraceManagerService.ActivationCause; import ghidra.app.services.DebuggerStaticMappingService; import ghidra.pcode.exec.InterruptPcodeExecutionException; import ghidra.pcode.utils.Utils; @@ -592,4 +594,84 @@ public class DebuggerEmulationServiceTest extends AbstractGhidraHeadedDebuggerGU assertEquals(new BigInteger("0", 16), regs.getViewValue(scratch, regR2).getUnsignedValue()); } + + @Test + public void testCacheInvalidation() throws Throwable { + createProgram(); + intoProject(program); + Assembler asm = Assemblers.getAssembler(program); + Memory memory = program.getMemory(); + Address addrText = addr(program, 0x00400000); + Register regR0 = program.getRegister("r0"); + Register regR2 = program.getRegister("r2"); + Address addrI2; + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { + MemoryBlock blockText = memory.createInitializedBlock(".text", addrText, 0x1000, + (byte) 0, TaskMonitor.DUMMY, false); + blockText.setExecute(true); + InstructionIterator ii = asm.assemble(addrText, + "mov r1, r0", + "mov r2, r1"); + ii.next(); + addrI2 = ii.next().getMinAddress(); + program.getProgramContext() + .setValue(regR0, addrText, addrText, new BigInteger("1234", 16)); + } + + programManager.openProgram(program); + waitForSwing(); + codeBrowser.goTo(new ProgramLocation(program, addrText)); + waitForSwing(); + + performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionEmulateProgram, true); + + DebuggerCoordinates current = traceManager.getCurrent(); + Trace trace = current.getTrace(); + assertNotNull(trace); + + TraceThread thread = Unique.assertOne(trace.getThreadManager().getAllThreads()); + 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, false)); + waitForSwing(); + waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-2")), + ActivationCause.USER, false)); + waitForSwing(); + long scratch = traceManager.getCurrentView().getSnap(); + + // Sanity check + assertEquals(new BigInteger("1234", 16), + regs.getViewValue(scratch, regR2).getUnsignedValue()); + + // Inject some logic that would require a cache refresh to materialize + try (UndoableTransaction tid = UndoableTransaction.start(trace, "Add breakpoint")) { + TraceBreakpoint tb = trace.getBreakpointManager() + .addBreakpoint("Breakpoints[0]", Lifespan.nowOn(0), addrI2, Set.of(thread), + Set.of(TraceBreakpointKind.SW_EXECUTE), true, "test"); + tb.setEmuSleigh(""" + r1 = 0x5678; + emu_exec_decoded(); + """); + } + + // Check the cache is still valid + waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-1")), + ActivationCause.USER, false)); + waitForSwing(); + waitOn(traceManager.activateAndNotify(current.time(TraceSchedule.parse("0:t0-2")), + ActivationCause.USER, false)); + waitForSwing(); + assertEquals(scratch, traceManager.getCurrentView().getSnap()); + assertEquals(new BigInteger("1234", 16), + regs.getViewValue(scratch, regR2).getUnsignedValue()); + + // Invalidate the cache. View should update immediately + performEnabledAction(codeBrowser.getProvider(), emulationPlugin.actionInvalidateCache, + true); + waitForTasks(); + assertEquals(new BigInteger("5678", 16), + regs.getViewValue(scratch, regR2).getUnsignedValue()); + } } diff --git a/Ghidra/Debug/Framework-TraceModeling/certification.manifest b/Ghidra/Debug/Framework-TraceModeling/certification.manifest index 895ac4b698..f59b605fe9 100644 --- a/Ghidra/Debug/Framework-TraceModeling/certification.manifest +++ b/Ghidra/Debug/Framework-TraceModeling/certification.manifest @@ -1,2 +1,3 @@ ##VERSION: 2.0 Module.manifest||GHIDRA||||END| +data/tracemodeling.theme.properties||GHIDRA||||END| diff --git a/Ghidra/Debug/Framework-TraceModeling/data/tracemodeling.theme.properties b/Ghidra/Debug/Framework-TraceModeling/data/tracemodeling.theme.properties new file mode 100644 index 0000000000..348adf499d --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/data/tracemodeling.theme.properties @@ -0,0 +1,7 @@ + +[Defaults] + +icon.content.handler.trace = video-x-generic16.png + +[Dark Defaults] + 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 28557181d7..cc9d003153 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 @@ -80,6 +80,7 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace protected static final String BASE_COMPILER = "Base Compiler"; protected static final String PLATFORM = "Platform"; protected static final String EXECUTABLE_PATH = "Executable Location"; + protected static final String EMU_CACHE_VERSION = "Emulator Cache Version"; protected static final int DB_TIME_INTERVAL = 500; protected static final int DB_BUFFER_SIZE = 1000; @@ -754,6 +755,16 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace return getOptions(TRACE_INFO).getDate(DATE_CREATED, new Date(0)); } + @Override + public void setEmulatorCacheVersion(long version) { + getOptions(TRACE_INFO).setLong(EMU_CACHE_VERSION, version); + } + + @Override + public long getEmulatorCacheVersion() { + return getOptions(TRACE_INFO).getLong(EMU_CACHE_VERSION, 0); + } + @Override public Collection getAllProgramViews() { /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceContentHandler.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceContentHandler.java index f3018fbae1..e56ef664c7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceContentHandler.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceContentHandler.java @@ -18,7 +18,6 @@ package ghidra.trace.database; import java.io.IOException; import javax.swing.Icon; -import javax.swing.ImageIcon; import db.DBHandle; import db.buffers.BufferFile; @@ -39,7 +38,7 @@ import ghidra.util.task.TaskMonitor; public class DBTraceContentHandler extends DBWithUserDataContentHandler { public static final String TRACE_CONTENT_TYPE = "Trace"; - public static ImageIcon TRACE_ICON = Trace.TRACE_ICON; + public static final Icon TRACE_ICON = Trace.TRACE_ICON; static final Class TRACE_DOMAIN_OBJECT_CLASS = DBTrace.class; static final String TRACE_CONTENT_DEFAULT_TOOL = "Debugger"; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java index e2d1fb3c00..479963e416 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/time/DBTraceSnapshot.java @@ -19,11 +19,9 @@ import java.io.IOException; import db.DBRecord; import ghidra.trace.model.Trace; -import ghidra.trace.model.Trace.TraceSnapshotChangeType; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.TraceSchedule; -import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; import ghidra.util.Msg; import ghidra.util.database.*; @@ -35,6 +33,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot protected static final String REAL_TIME_COLUMN_NAME = "RealTime"; protected static final String SCHEDULE_COLUMN_NAME = "Schedule"; + protected static final String VERSION_COLUMN_NAME = "Version"; protected static final String DESCRIPTION_COLUMN_NAME = "Description"; protected static final String THREAD_COLUMN_NAME = "Thread"; @@ -42,6 +41,8 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot static DBObjectColumn REAL_TIME_COLUMN; @DBAnnotatedColumn(SCHEDULE_COLUMN_NAME) static DBObjectColumn SCHEDULE_COLUMN; + @DBAnnotatedColumn(VERSION_COLUMN_NAME) + static DBObjectColumn VERSION_COLUMN; @DBAnnotatedColumn(DESCRIPTION_COLUMN_NAME) static DBObjectColumn DESCRIPTION_COLUMN; @DBAnnotatedColumn(THREAD_COLUMN_NAME) @@ -51,6 +52,8 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot long realTime; // milliseconds @DBAnnotatedField(column = SCHEDULE_COLUMN_NAME, indexed = true) String scheduleStr = ""; + @DBAnnotatedField(column = VERSION_COLUMN_NAME) + long version; @DBAnnotatedField(column = DESCRIPTION_COLUMN_NAME) String description; @DBAnnotatedField(column = THREAD_COLUMN_NAME) @@ -107,7 +110,9 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot @Override public long getRealTime() { - return realTime; + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + return realTime; + } } @Override @@ -121,7 +126,9 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot @Override public String getDescription() { - return description; + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + return description; + } } @Override @@ -135,7 +142,9 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot @Override public TraceThread getEventThread() { - return eventThread; + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + return eventThread; + } } @Override @@ -156,12 +165,16 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot @Override public TraceSchedule getSchedule() { - return schedule; + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + return schedule; + } } @Override public String getScheduleString() { - return scheduleStr; + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + return scheduleStr; + } } @Override @@ -174,6 +187,22 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot } } + @Override + public long getVersion() { + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + return version; + } + } + + @Override + public void setVersion(long version) { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + this.version = version; + update(VERSION_COLUMN); + manager.notifySnapshotChanged(this); + } + } + @Override public void delete() { manager.deleteSnapshot(this); 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 e79a37e6d0..1ac6f84f16 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 @@ -17,8 +17,9 @@ package ghidra.trace.model; import java.util.*; -import javax.swing.ImageIcon; +import javax.swing.Icon; +import generic.theme.GIcon; import ghidra.program.model.address.*; import ghidra.program.model.data.*; import ghidra.program.model.lang.CompilerSpec; @@ -48,10 +49,20 @@ import ghidra.trace.model.time.TraceTimeManager; import ghidra.trace.util.DefaultTraceChangeType; import ghidra.util.LockHold; import ghidra.util.UniversalID; -import resources.ResourceManager; +/** + * An indexed record of observations over the course of a target's execution + * + *

+ * Conceptually, this is the same as a {@link Program}, but multiplied by a concrete dimension of + * time and organized into {@link TraceSnapshot snapshots}. This also includes information about + * other objects not ordinarily of concern for static analysis, for example, {@link TraceThread + * threads}, {@link TraceModule modules}, and {@link TraceBreakpoint breakpoints}. To view a + * specific snapshot and/or manipulate the trace as if it were a program, use + * {@link #getProgramView()}. + */ public interface Trace extends DataTypeManagerDomainObject { - ImageIcon TRACE_ICON = ResourceManager.loadImage("images/video-x-generic16.png"); + Icon TRACE_ICON = new GIcon("icon.content.handler.trace"); /** * TEMPORARY: An a/b switch while both table- (legacy) and object-mode traces are supported @@ -411,6 +422,10 @@ public interface Trace extends DataTypeManagerDomainObject { CompilerSpec getBaseCompilerSpec(); + void setEmulatorCacheVersion(long version); + + long getEmulatorCacheVersion(); + AddressFactory getBaseAddressFactory(); TraceAddressPropertyManager getAddressPropertyManager(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceSnapshot.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceSnapshot.java index 16fa548a00..5befec010f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceSnapshot.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/TraceSnapshot.java @@ -115,6 +115,22 @@ public interface TraceSnapshot { */ void setSchedule(TraceSchedule schedule); + /** + * Get the snapshot's version, esp., when it represents a cache entry + * + * @see Trace#getEmulatorCacheVersion() + * @return the version + */ + long getVersion(); + + /** + * Set the snapshot's version, esp., when it represents a cache entry + * + * @see Trace#getEmulatorCacheVersion() + * @param version the version + */ + void setVersion(long version); + /** * Delete this snapshot * diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceChangeType.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceChangeType.java index fd135c2c8a..df1c889e21 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceChangeType.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceChangeType.java @@ -21,6 +21,12 @@ import java.util.*; import ghidra.framework.model.DomainObjectChangeRecord; +/** + * A trace change type, assigned a number at runtime + * + * @param the type of object changed + * @param the type of the object's attribute that changed + */ public class DefaultTraceChangeType implements TraceChangeType { private static int nextType = 0x3ACE; // Stay far away from manually-assigned types // But not too far, since it makes the bit set for events gigantic.