From 675eabdd6e4b961cf0bc3fd8115eede0e0b05542 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 4 Jan 2022 10:42:31 -0500 Subject: [PATCH] GP-1386: A generic object store in DBTrace for recording the object model tree --- ...AbstractModelForDbgengBreakpointsTest.java | 12 +- ...ractModelForDbgengFrameActivationTest.java | 2 +- ...ctModelForDbgengProcessActivationTest.java | 2 +- ...actModelForDbgengThreadActivationTest.java | 4 +- .../AbstractModelForGdbBreakpointsTest.java | 12 +- ...bstractModelForGdbFrameActivationTest.java | 2 +- ...ractModelForGdbInferiorActivationTest.java | 2 +- ...stractModelForGdbThreadActivationTest.java | 2 +- .../AbstractModelForLldbBreakpointsTest.java | 12 +- ...stractModelForLldbFrameActivationTest.java | 4 +- ...ractModelForLldbProcessActivationTest.java | 2 +- ...ractModelForLldbSessionActivationTest.java | 2 +- ...tractModelForLldbThreadActivationTest.java | 2 +- .../gui/breakpoint/BreakpointLocationRow.java | 3 +- .../gui/console/DebuggerConsoleProvider.java | 4 +- .../debug/gui/copying/DebuggerCopyPlan.java | 2 +- .../gui/modules/DebuggerModulesProvider.java | 10 + .../core/debug/gui/thread/ThreadRow.java | 9 +- .../breakpoint/LogicalBreakpointInternal.java | 2 +- .../model/DefaultBreakpointRecorder.java | 2 +- .../DebuggerBreakpointsPluginScreenShots.java | 2 +- ...ebuggerTraceViewDiffPluginScreenShots.java | 4 +- .../DebuggerMemviewPluginScreenShots.java | 3 +- .../AbstractGhidraHeadedDebuggerGUITest.java | 18 +- .../DebuggerBreakpointMarkerPluginTest.java | 2 +- ...DebuggerBreakpointsProviderObjectTest.java | 98 ++ .../DebuggerBreakpointsProviderTest.java | 15 +- .../DebuggerCopyActionsPluginTest.java | 5 +- .../listing/DebuggerListingProviderTest.java | 14 +- .../DebuggerMemoryBytesProviderTest.java | 22 +- .../DebuggerRegionsProviderObjectTest.java | 59 + .../memory/DebuggerRegionsProviderTest.java | 32 +- .../DebuggerModulesProviderObjectTest.java | 95 ++ .../modules/DebuggerModulesProviderTest.java | 56 +- .../DebuggerRegistersProviderTest.java | 2 +- .../DebuggerStackProviderObjectTest.java | 96 ++ .../gui/stack/DebuggerStackProviderTest.java | 37 +- .../DebuggerThreadsProviderObjectTest.java | 79 ++ .../thread/DebuggerThreadsProviderTest.java | 10 +- .../watch/DebuggerWatchesProviderTest.java | 4 +- ...gerLogicalBreakpointServiceObjectTest.java | 99 ++ .../DebuggerLogicalBreakpointServiceTest.java | 7 +- .../model/DebuggerModelServiceTest.java | 30 +- .../model/DefaultTraceRecorderTest.java | 12 +- .../DebuggerTraceManagerServiceTest.java | 9 +- .../exec/TraceRecorderAsyncPcodeExecTest.java | 4 +- .../dbg/target/schema/TargetObjectSchema.java | 11 + .../java/ghidra/dbg/util/PathMatcher.java | 38 +- .../java/ghidra/dbg/util/PathPattern.java | 64 +- .../java/ghidra/dbg/util/PathPredicates.java | 69 +- .../Framework-TraceModeling/build.gradle | 1 + .../java/ghidra/trace/database/DBTrace.java | 38 +- .../ghidra/trace/database/DBTraceUtils.java | 167 ++- .../address/DBTraceOverlaySpaceAdapter.java | 51 +- .../database/bookmark/DBTraceBookmark.java | 4 +- .../bookmark/DBTraceBookmarkManager.java | 5 +- .../DBTraceBookmarkRegisterSpace.java | 8 +- .../bookmark/DBTraceBookmarkSpace.java | 4 +- .../breakpoint/DBTraceBreakpoint.java | 8 +- .../breakpoint/DBTraceBreakpointManager.java | 40 +- .../breakpoint/DBTraceBreakpointSpace.java | 4 +- .../DBTraceObjectBreakpointLocation.java | 285 +++++ .../DBTraceObjectBreakpointSpec.java | 231 ++++ .../DBTraceRegisterContextManager.java | 3 +- .../DBTraceRegisterContextRegisterSpace.java | 8 +- .../context/DBTraceRegisterContextSpace.java | 6 +- .../data/DBTraceDataSettingsAdapter.java | 5 +- .../database/listing/DBTraceCodeManager.java | 5 +- .../listing/DBTraceCodeRegisterSpace.java | 8 +- .../database/listing/DBTraceCodeSpace.java | 4 +- .../listing/DBTraceCodeUnitAdapter.java | 4 +- .../listing/DBTraceCodeUnitsRegisterView.java | 4 +- .../listing/DBTraceDataRegisterView.java | 4 +- .../DBTraceInstructionsRegisterView.java | 4 +- .../listing/DBTraceInstructionsView.java | 1 - .../listing/UndefinedDBTraceData.java | 8 +- .../DBTraceAddressSnapRangePropertyMap.java | 3 +- ...ressSnapRangePropertyMapRegisterSpace.java | 8 +- ...TraceAddressSnapRangePropertyMapSpace.java | 4 +- .../database/memory/DBTraceMemoryManager.java | 56 +- .../database/memory/DBTraceMemoryRegion.java | 33 +- .../memory/DBTraceMemoryRegisterSpace.java | 8 +- .../database/memory/DBTraceMemorySpace.java | 84 +- .../memory/DBTraceObjectMemoryRegion.java | 312 +++++ .../memory/DBTraceObjectRegister.java | 105 ++ .../database/module/DBTraceModuleManager.java | 99 +- .../database/module/DBTraceObjectModule.java | 242 ++++ .../database/module/DBTraceObjectSection.java | 132 +++ .../database/module/TraceObjectSection.java | 34 + .../AbstractDBTraceProgramViewListing.java | 8 +- .../AbstractDBTraceProgramViewMemory.java | 13 +- .../database/program/DBTraceProgramView.java | 17 +- .../program/DBTraceProgramViewFragment.java | 25 +- .../program/DBTraceProgramViewMemory.java | 28 +- .../DBTraceProgramViewMemoryRegionBlock.java | 8 +- .../DBTraceProgramViewRegisterListing.java | 6 +- .../program/DBTraceProgramViewRegisters.java | 3 +- ...eProgramViewRegistersReferenceManager.java | 6 +- .../AbstractDBTraceSpaceBasedManager.java | 15 +- .../trace/database/space/DBTraceSpaceKey.java | 13 +- .../database/stack/DBTraceObjectStack.java | 234 ++++ .../stack/DBTraceObjectStackFrame.java | 147 +++ .../trace/database/stack/DBTraceStack.java | 25 +- .../database/stack/DBTraceStackManager.java | 101 +- .../symbol/AbstractDBTraceSymbol.java | 4 +- ...TraceSymbolSingleTypeWithLocationView.java | 15 +- .../database/symbol/DBTraceEquateManager.java | 4 +- .../symbol/DBTraceEquateRegisterSpace.java | 8 +- .../database/symbol/DBTraceEquateSpace.java | 4 +- .../database/symbol/DBTraceLabelSymbol.java | 8 +- .../symbol/DBTraceLabelSymbolView.java | 10 +- .../database/symbol/DBTraceReference.java | 4 +- .../symbol/DBTraceReferenceManager.java | 3 +- .../symbol/DBTraceReferenceRegisterSpace.java | 8 +- .../symbol/DBTraceReferenceSpace.java | 4 +- .../database/symbol/DBTraceSymbolManager.java | 14 +- ...ceSymbolMultipleTypesWithLocationView.java | 8 +- .../trace/database/target/DBTraceObject.java | 893 ++++++++++++++ .../DBTraceObjectAddressRangeValue.java | 204 ++++ .../target/DBTraceObjectInterface.java | 141 +++ .../database/target/DBTraceObjectManager.java | 652 ++++++++++ .../database/target/DBTraceObjectValPath.java | 120 ++ .../database/target/DBTraceObjectValue.java | 420 +++++++ .../target/InternalTraceObjectValue.java | 173 +++ .../database/target/LifespanCorrector.java | 222 ++++ .../database/thread/DBTraceObjectThread.java | 166 +++ .../database/thread/DBTraceThreadManager.java | 62 +- .../trace/database/time/DBTraceSnapshot.java | 3 +- .../model/ImmutableTraceAddressSnapRange.java | 6 + .../main/java/ghidra/trace/model/Trace.java | 44 + ...raceObject.java => TraceUniqueObject.java} | 9 +- .../model/breakpoint/TraceBreakpoint.java | 8 +- .../TraceObjectBreakpointLocation.java | 46 + .../breakpoint/TraceObjectBreakpointSpec.java | 46 + .../trace/model/memory/TraceMemoryFlag.java | 24 + .../trace/model/memory/TraceMemoryRegion.java | 4 +- .../model/memory/TraceObjectMemoryRegion.java | 45 + .../model/memory/TraceObjectRegister.java | 52 + .../trace/model/modules/TraceModule.java | 4 +- .../model/modules/TraceObjectModule.java | 39 + .../trace/model/modules/TraceSection.java | 10 +- .../model/modules/TraceStaticMapping.java | 4 +- .../trace/model/program/TraceProgramView.java | 2 +- .../trace/model/stack/TraceObjectStack.java | 31 + .../model/stack/TraceObjectStackFrame.java | 31 + .../ghidra/trace/model/stack/TraceStack.java | 3 +- .../model/target/DuplicateKeyException.java | 22 + .../trace/model/target/TraceObject.java | 444 +++++++ .../model/target/TraceObjectInterface.java | 20 + .../model/target/TraceObjectKeyPath.java | 115 ++ .../model/target/TraceObjectManager.java | 163 +++ .../model/target/TraceObjectValPath.java | 36 + .../trace/model/target/TraceObjectValue.java | 167 +++ .../model/target/annot/TraceObjectInfo.java | 49 + .../annot/TraceObjectInterfaceUtils.java | 86 ++ .../trace/model/thread/TraceObjectThread.java | 32 + .../trace/model/thread/TraceThread.java | 4 +- .../model/thread/TraceThreadManager.java | 6 +- .../trace/util/DefaultTraceTimeViewport.java | 3 + .../ghidra/trace/util/TraceAddressSpace.java | 9 + .../trace/database/ToyDBTraceBuilder.java | 13 +- .../DBTraceBreakpointManagerObjectTest.java | 64 + .../DBTraceBreakpointManagerTest.java | 58 +- .../listing/DBTraceCodeManagerTest.java | 16 +- .../database/listing/DBTraceCodeUnitTest.java | 3 +- ...stractDBTraceMemoryManagerRegionsTest.java | 173 +++ .../AbstractDBTraceMemoryManagerTest.java | 12 +- ...DBTraceMemoryManagerObjectRegionsTest.java | 49 + .../DBTraceMemoryManagerRegionsTest.java | 26 + .../DBTraceModuleManagerObjectTest.java | 57 + .../module/DBTraceModuleManagerTest.java | 138 ++- .../stack/DBTraceStackManagerObjectTest.java | 66 ++ .../stack/DBTraceStackManagerTest.java | 126 +- .../symbol/DBTraceSymbolManagerTest.java | 14 +- .../target/DBTraceObjectManagerTest.java | 1047 +++++++++++++++++ .../DBTraceThreadManagerObjectTest.java | 49 + .../thread/DBTraceThreadManagerTest.java | 78 +- .../src/main/java/generic/Unique.java | 14 + .../database/DBCachedObjectStoreFactory.java | 284 ++++- 179 files changed, 9971 insertions(+), 797 deletions(-) create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderObjectTest.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderObjectTest.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderObjectTest.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderObjectTest.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderObjectTest.java create mode 100644 Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceObjectTest.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectRegister.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectSection.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/TraceObjectSection.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStack.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStackFrame.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/LifespanCorrector.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java rename Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/{TraceObject.java => TraceUniqueObject.java} (85%) create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectMemoryRegion.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceObjectModule.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceObjectStack.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceObjectStackFrame.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/DuplicateKeyException.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectInterface.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValue.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInfo.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceObjectThread.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerObjectTest.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegionsTest.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegionsTest.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegionsTest.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerObjectTest.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerObjectTest.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerObjectTest.java diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengBreakpointsTest.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengBreakpointsTest.java index 98558afa99..942ee7928b 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengBreakpointsTest.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengBreakpointsTest.java @@ -121,28 +121,28 @@ public abstract class AbstractModelForDbgengBreakpointsTest @Override protected void disableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(t.getPath()).get(BREAK_ID_POS); waitOn(interpreter.execute("bd " + bpId)); } @Override protected void enableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(t.getPath()).get(BREAK_ID_POS); waitOn(interpreter.execute("be " + bpId)); } @Override protected void deleteViaInterpreter(TargetDeletable d, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(d.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(d.getPath()).get(BREAK_ID_POS); waitOn(interpreter.execute("bc " + bpId)); } @Override protected void assertLocCoversViaInterpreter(AddressRange range, TargetBreakpointKind kind, TargetBreakpointLocation loc, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(loc.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(loc.getPath()).get(BREAK_ID_POS); String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim(); assertFalse(line.contains("\n")); // NB. WinDbg numbers breakpoints in base 10, by default @@ -153,7 +153,7 @@ public abstract class AbstractModelForDbgengBreakpointsTest @Override protected void assertEnabledViaInterpreter(TargetTogglable t, boolean enabled, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(t.getPath()).get(BREAK_ID_POS); String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim(); assertFalse(line.contains("\n")); assertTrue(line.startsWith(bpId)); @@ -164,7 +164,7 @@ public abstract class AbstractModelForDbgengBreakpointsTest @Override protected void assertDeletedViaInterpreter(TargetDeletable d, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(d.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(d.getPath()).get(BREAK_ID_POS); String line = waitOn(interpreter.executeCapture("bl " + bpId)).trim(); assertEquals("", line); } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengFrameActivationTest.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengFrameActivationTest.java index 8c01b7e371..6984821673 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengFrameActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengFrameActivationTest.java @@ -72,7 +72,7 @@ public abstract class AbstractModelForDbgengFrameActivationTest String line = waitOn(interpreter.executeCapture(".frame")).trim(); assertFalse(line.contains("\n")); int frameId = Integer.parseInt(line.split("\\s+")[0], 16); - int expId = Integer.decode(getStackPattern().matchIndices(expected.getPath()).get(2)); + int expId = Integer.decode(getStackPattern().matchKeys(expected.getPath()).get(2)); assertEquals(expId, frameId); } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengProcessActivationTest.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengProcessActivationTest.java index 17aef69fbb..811cb3811d 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengProcessActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengProcessActivationTest.java @@ -63,7 +63,7 @@ public abstract class AbstractModelForDbgengProcessActivationTest @Override protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter) throws Throwable { - String id = Unique.assertOne(getProcessPattern().matchIndices(obj.getPath())); + String id = Unique.assertOne(getProcessPattern().matchKeys(obj.getPath())); waitOn(interpreter.execute("|" + id + " s")); } diff --git a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengThreadActivationTest.java b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengThreadActivationTest.java index 76e5429ddf..b41286b216 100644 --- a/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengThreadActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-dbgeng/src/test/java/agent/dbgeng/model/AbstractModelForDbgengThreadActivationTest.java @@ -63,7 +63,7 @@ public abstract class AbstractModelForDbgengThreadActivationTest @Override protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter) throws Throwable { - String threadId = getThreadPattern().matchIndices(obj.getPath()).get(1); + String threadId = getThreadPattern().matchKeys(obj.getPath()).get(1); // TODO: This test is imperfect, since processes are activated as well waitOn(interpreter.execute("~" + threadId + " s")); } @@ -78,7 +78,7 @@ public abstract class AbstractModelForDbgengThreadActivationTest .filter(l -> l.trim().startsWith(".")) .collect(Collectors.toList())).trim(); String threadId = getIdFromCapture(line); - String expId = getThreadPattern().matchIndices(expected.getPath()).get(1); + String expId = getThreadPattern().matchKeys(expected.getPath()).get(1); assertEquals(expId, threadId); } } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbBreakpointsTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbBreakpointsTest.java index 3dd9fb92eb..9834e6db24 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbBreakpointsTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbBreakpointsTest.java @@ -119,7 +119,7 @@ public abstract class AbstractModelForGdbBreakpointsTest protected void disableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter) throws Throwable { assert t instanceof TargetBreakpointSpec; // TODO: or Location - String index = Unique.assertOne(BREAK_PATTERN.matchIndices(t.getPath())); + String index = Unique.assertOne(BREAK_PATTERN.matchKeys(t.getPath())); waitOn(interpreter.execute("disable " + index)); } @@ -127,7 +127,7 @@ public abstract class AbstractModelForGdbBreakpointsTest protected void enableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter) throws Throwable { assert t instanceof TargetBreakpointSpec; // TODO: or Location - String index = Unique.assertOne(BREAK_PATTERN.matchIndices(t.getPath())); + String index = Unique.assertOne(BREAK_PATTERN.matchKeys(t.getPath())); waitOn(interpreter.execute("enable " + index)); } @@ -135,7 +135,7 @@ public abstract class AbstractModelForGdbBreakpointsTest protected void deleteViaInterpreter(TargetDeletable d, TargetInterpreter interpreter) throws Throwable { assert d instanceof TargetBreakpointSpec; // TODO: or Location - String index = Unique.assertOne(BREAK_PATTERN.matchIndices(d.getPath())); + String index = Unique.assertOne(BREAK_PATTERN.matchKeys(d.getPath())); waitOn(interpreter.execute("delete " + index)); } @@ -143,7 +143,7 @@ public abstract class AbstractModelForGdbBreakpointsTest protected void assertLocCoversViaInterpreter(AddressRange range, TargetBreakpointKind kind, TargetBreakpointLocation loc, TargetInterpreter interpreter) throws Throwable { String index = - Unique.assertOne(BREAK_PATTERN.matchIndices(loc.getSpecification().getPath())); + Unique.assertOne(BREAK_PATTERN.matchKeys(loc.getSpecification().getPath())); String output = waitOn(interpreter.executeCapture("info break " + index)); String line = Unique.assertOne(Stream.of(output.split("\n")) .filter(l -> !l.trim().startsWith("Num")) @@ -156,7 +156,7 @@ public abstract class AbstractModelForGdbBreakpointsTest protected void assertEnabledViaInterpreter(TargetTogglable t, boolean enabled, TargetInterpreter interpreter) throws Throwable { assert t instanceof TargetBreakpointSpec; // TODO: or Location - String index = Unique.assertOne(BREAK_PATTERN.matchIndices(t.getPath())); + String index = Unique.assertOne(BREAK_PATTERN.matchKeys(t.getPath())); String output = waitOn(interpreter.executeCapture("info break " + index)); String line = Unique.assertOne(Stream.of(output.split("\n")) .filter(l -> !l.trim().startsWith("Num")) @@ -170,7 +170,7 @@ public abstract class AbstractModelForGdbBreakpointsTest protected void assertDeletedViaInterpreter(TargetDeletable d, TargetInterpreter interpreter) throws Throwable { assert d instanceof TargetBreakpointSpec; // TODO: or Location - String index = Unique.assertOne(BREAK_PATTERN.matchIndices(d.getPath())); + String index = Unique.assertOne(BREAK_PATTERN.matchKeys(d.getPath())); String output = waitOn(interpreter.executeCapture("info break " + index)); assertTrue(output.contains("No breakpoint")); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbFrameActivationTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbFrameActivationTest.java index cdab85769c..e4268b80dc 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbFrameActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbFrameActivationTest.java @@ -72,7 +72,7 @@ public abstract class AbstractModelForGdbFrameActivationTest @Override protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter) throws Throwable { - String index = Unique.assertOne(STACK_PATTERN.matchIndices(obj.getPath())); + String index = Unique.assertOne(STACK_PATTERN.matchKeys(obj.getPath())); waitOn(interpreter.execute("frame " + index)); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbInferiorActivationTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbInferiorActivationTest.java index 14029917fe..a2222b338d 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbInferiorActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbInferiorActivationTest.java @@ -62,7 +62,7 @@ public abstract class AbstractModelForGdbInferiorActivationTest @Override protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter) throws Throwable { - String index = Unique.assertOne(INF_PATTERN.matchIndices(obj.getPath())); + String index = Unique.assertOne(INF_PATTERN.matchKeys(obj.getPath())); waitOn(interpreter.execute("inferior " + index)); } diff --git a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbThreadActivationTest.java b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbThreadActivationTest.java index f739e8dbe9..82e8ad3bd7 100644 --- a/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbThreadActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-gdb/src/test/java/agent/gdb/model/AbstractModelForGdbThreadActivationTest.java @@ -73,7 +73,7 @@ public abstract class AbstractModelForGdbThreadActivationTest @Override protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter) throws Throwable { - String index = Unique.assertOne(Set.copyOf(THREAD_PATTERN.matchIndices(obj.getPath()))); + String index = Unique.assertOne(Set.copyOf(THREAD_PATTERN.matchKeys(obj.getPath()))); // TODO: This test is imperfect, since inferiors are activated as well waitOn(interpreter.execute("thread " + index + ".1")); } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbBreakpointsTest.java b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbBreakpointsTest.java index d64d249946..a466e2d28f 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbBreakpointsTest.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbBreakpointsTest.java @@ -145,7 +145,7 @@ public abstract class AbstractModelForLldbBreakpointsTest @Override protected void disableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(t.getPath()).get(BREAK_ID_POS); String type = getTypeFromSpec(t); waitOn(interpreter.execute(getCommand("disable", type, bpId))); } @@ -153,7 +153,7 @@ public abstract class AbstractModelForLldbBreakpointsTest @Override protected void enableViaInterpreter(TargetTogglable t, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(t.getPath()).get(BREAK_ID_POS); String type = getTypeFromSpec(t); waitOn(interpreter.execute(getCommand("enable", type, bpId))); } @@ -161,7 +161,7 @@ public abstract class AbstractModelForLldbBreakpointsTest @Override protected void deleteViaInterpreter(TargetDeletable d, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(d.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(d.getPath()).get(BREAK_ID_POS); String type = getTypeFromSpec(d); waitOn(interpreter.execute(getCommand("delete", type, bpId))); } @@ -169,7 +169,7 @@ public abstract class AbstractModelForLldbBreakpointsTest @Override protected void assertLocCoversViaInterpreter(AddressRange range, TargetBreakpointKind kind, TargetBreakpointLocation loc, TargetInterpreter interpreter) throws Throwable { - List matchIndices = getBreakPattern().matchIndices(loc.getSpecification().getPath()); + List matchIndices = getBreakPattern().matchKeys(loc.getSpecification().getPath()); String bpId = matchIndices.get(BREAK_ID_POS); String type = getTypeFromKind(kind); String line = waitOn(interpreter.executeCapture(getCommand("list", type, bpId))).trim(); @@ -182,7 +182,7 @@ public abstract class AbstractModelForLldbBreakpointsTest @Override protected void assertEnabledViaInterpreter(TargetTogglable t, boolean enabled, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(t.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(t.getPath()).get(BREAK_ID_POS); String type = getTypeFromSpec(t); String line = waitOn(interpreter.executeCapture(getCommand("list", type, bpId))).trim(); assertTrue(line.contains(bpId.substring(1)+":")); @@ -192,7 +192,7 @@ public abstract class AbstractModelForLldbBreakpointsTest @Override protected void assertDeletedViaInterpreter(TargetDeletable d, TargetInterpreter interpreter) throws Throwable { - String bpId = getBreakPattern().matchIndices(d.getPath()).get(BREAK_ID_POS); + String bpId = getBreakPattern().matchKeys(d.getPath()).get(BREAK_ID_POS); String type = getTypeFromSpec(d); String line = waitOn(interpreter.executeCapture(type + " list ")).trim(); assertFalse(line.contains(bpId+":")); diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbFrameActivationTest.java b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbFrameActivationTest.java index cc21843b68..2bbb51e663 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbFrameActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbFrameActivationTest.java @@ -57,7 +57,7 @@ public abstract class AbstractModelForLldbFrameActivationTest protected void activateViaInterpreter(TargetObject obj, TargetInterpreter interpreter) throws Throwable { - String index = getStackPattern().matchIndices(obj.getPath()).get(3); + String index = getStackPattern().matchKeys(obj.getPath()).get(3); waitOn(interpreter.execute("frame select " + index)); } @@ -70,7 +70,7 @@ public abstract class AbstractModelForLldbFrameActivationTest assertFalse(line.contains("\n")); String id = getIdFromCapture(line); int frameId = Integer.parseInt(id, 10); - int expId = Integer.decode(getStackPattern().matchIndices(expected.getPath()).get(3)); + int expId = Integer.decode(getStackPattern().matchKeys(expected.getPath()).get(3)); assertEquals(expId, frameId); } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbProcessActivationTest.java b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbProcessActivationTest.java index 4f4f897ebf..19cc796e2e 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbProcessActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbProcessActivationTest.java @@ -94,7 +94,7 @@ public abstract class AbstractModelForLldbProcessActivationTest .filter(l -> l.trim().startsWith("*")) .collect(Collectors.toList())).trim(); String procId = getIdFromCapture(line); - String expId = getProcessPattern().matchIndices(expected.getPath()).get(1); + String expId = getProcessPattern().matchKeys(expected.getPath()).get(1); assertEquals(Long.parseLong(expId, 16), Long.parseLong(procId)); } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbSessionActivationTest.java b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbSessionActivationTest.java index 7a3e6ec838..52c04d311c 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbSessionActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbSessionActivationTest.java @@ -97,7 +97,7 @@ public abstract class AbstractModelForLldbSessionActivationTest .filter(l -> l.trim().startsWith("*")) .collect(Collectors.toList())).trim(); String procId = getIdFromCapture(line); - String expId = getSessionPattern().matchIndices(expected.getPath()).get(0); + String expId = getSessionPattern().matchKeys(expected.getPath()).get(0); assertEquals(Long.parseLong(expId, 16), Long.parseLong(procId)); } diff --git a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbThreadActivationTest.java b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbThreadActivationTest.java index bf86a63feb..96405e2ad0 100644 --- a/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbThreadActivationTest.java +++ b/Ghidra/Debug/Debugger-agent-lldb/src/test/java/agent/lldb/model/AbstractModelForLldbThreadActivationTest.java @@ -80,7 +80,7 @@ public abstract class AbstractModelForLldbThreadActivationTest .filter(l -> l.trim().startsWith("*")) .collect(Collectors.toList())).trim(); String threadId = getIdFromCapture(line); - String expId = getThreadPattern().matchIndices(expected.getPath()).get(2); + String expId = getThreadPattern().matchKeys(expected.getPath()).get(2); assertEquals(expId, threadId); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java index 301f7d70c5..b83dc0a1c3 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/BreakpointLocationRow.java @@ -38,7 +38,8 @@ public class BreakpointLocationRow { } public boolean isEnabled() { - return loc.isEnabled(); + TraceRecorder recorder = provider.modelService.getRecorder(loc.getTrace()); + return recorder != null && loc.isEnabled(recorder.getSnap()); } public void setEnabled(boolean enabled) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java index c1a7927e85..9413d84566 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/console/DebuggerConsoleProvider.java @@ -210,8 +210,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter } @Override - public java.util.List defaultSortOrder() { - return java.util.List.of(LogTableColumns.ACTIONS, LogTableColumns.TIME); + public List defaultSortOrder() { + return List.of(LogTableColumns.ACTIONS, LogTableColumns.TIME); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java index 4c30b0aa37..2ca068f20c 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyPlan.java @@ -228,7 +228,7 @@ public class DebuggerCopyPlan { Address dest = intoAddress.add(off); ProgramBreakpoint pb = new ProgramBreakpoint(into, dest, bpt.getLength(), bpt.getKinds()); - if (bpt.isEnabled()) { + if (bpt.isEnabled(from.getSnap())) { pb.enable(); } else { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java index b53c6a17c7..02289d3ceb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProvider.java @@ -210,6 +210,11 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { public ModuleTableModel() { super("Modules", ModuleTableColumns.class, TraceModule::getObjectKey, ModuleRow::new); } + + @Override + public List defaultSortOrder() { + return List.of(ModuleTableColumns.BASE); + } } protected static class SectionTableModel @@ -220,6 +225,11 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { super("Sections", SectionTableColumns.class, TraceSection::getObjectKey, SectionRow::new); } + + @Override + public List defaultSortOrder() { + return List.of(SectionTableColumns.START); + } } protected static Set getSelectedModulesFromModuleContext( diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadRow.java index 6fc9c739a8..e52fc64df9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/ThreadRow.java @@ -22,6 +22,7 @@ import ghidra.app.services.TraceRecorder; import ghidra.dbg.target.TargetExecutionStateful.TargetExecutionState; import ghidra.trace.model.Trace; import ghidra.trace.model.thread.TraceThread; +import ghidra.util.Msg; import ghidra.util.database.UndoableTransaction; public class ThreadRow { @@ -109,6 +110,12 @@ public class ThreadRow { @Override public String toString() { - return getName(); + try { + return getName(); + } + catch (Exception e) { + Msg.error(this, "Error rendering as string: " + e); + return ""; + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java index ea2fc17f73..7146335029 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/breakpoint/LogicalBreakpointInternal.java @@ -288,7 +288,7 @@ public interface LogicalBreakpointInternal extends LogicalBreakpoint { public TraceEnablement computeEnablement() { TraceEnablement en = TraceEnablement.MISSING; for (IDHashed bpt : breakpoints) { - en = en.combine(TraceEnablement.fromBool(bpt.obj.isEnabled())); + en = en.combine(TraceEnablement.fromBool(bpt.obj.isEnabled(recorder.getSnap()))); if (en == TraceEnablement.MIXED) { return en; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java index 7ca0187453..485799e991 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DefaultBreakpointRecorder.java @@ -167,7 +167,7 @@ public class DefaultBreakpointRecorder implements ManagedBreakpointRecorder { traceBpt.setClearedSnap(snap - 1); } breakpointManager.placeBreakpoint(path, snap, range, - traceBpt.getThreads(), traceBpt.getKinds(), traceBpt.isEnabled(), + traceBpt.getThreads(), traceBpt.getKinds(), traceBpt.isEnabled(snap), traceBpt.getComment()); } catch (DuplicateNameException e) { diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java index ab7125c811..5dba4864d7 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsPluginScreenShots.java @@ -173,7 +173,7 @@ public class DebuggerBreakpointsPluginScreenShots extends GhidraScreenShotGenera assertEquals(3, allBreakpoints.size()); }); waitForPass(() -> { - assertFalse(bpt.isEnabled()); + assertFalse(bpt.isEnabled(0)); }); /** * TODO: Might be necessary to debounce and wait for service callbacks to settle. Sometimes, diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginScreenShots.java index 0ccc5377a1..d96adf78c3 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/diff/DebuggerTraceViewDiffPluginScreenShots.java @@ -31,9 +31,9 @@ import ghidra.async.AsyncTestUtils; import ghidra.test.ToyProgramBuilder; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.memory.DBTraceMemoryManager; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.time.DBTraceTimeManager; import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.Swing; import ghidra.util.database.UndoableTransaction; @@ -105,7 +105,7 @@ public class DebuggerTraceViewDiffPluginScreenShots extends GhidraScreenShotGene @Test public void testCaptureDebuggerTimeSelectionDialog() throws Throwable { - DBTraceThread thread; + TraceThread thread; try (UndoableTransaction tid = tb.startTransaction()) { DBTraceTimeManager tm = tb.trace.getTimeManager(); thread = tb.getOrAddThread("main", 0); diff --git a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewPluginScreenShots.java b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewPluginScreenShots.java index 706d6e72f2..2428c159e7 100644 --- a/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewPluginScreenShots.java +++ b/Ghidra/Debug/Debugger/src/screen/java/ghidra/app/plugin/core/debug/gui/memview/DebuggerMemviewPluginScreenShots.java @@ -30,7 +30,6 @@ import ghidra.framework.model.DomainFolder; import ghidra.program.model.listing.Program; import ghidra.test.ToyProgramBuilder; import ghidra.trace.database.ToyDBTraceBuilder; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.breakpoint.TraceBreakpointKind; import ghidra.trace.model.memory.TraceMemoryFlag; import ghidra.trace.model.thread.TraceThread; @@ -80,7 +79,7 @@ public class DebuggerMemviewPluginScreenShots extends GhidraScreenShotGenerator private void populateTraceAndPrograms() throws Exception { DomainFolder root = tool.getProject().getProjectData().getRootFolder(); - DBTraceThread thread1; + TraceThread thread1; try (UndoableTransaction tid = tb.startTransaction()) { thread1 = tb.trace.getThreadManager().addThread("[0]", Range.openClosed(0L, 40L)); tb.trace.getThreadManager().addThread("[1]", Range.openClosed(3L, 50L)); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java index fc6b48d048..ce25b16c7f 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/AbstractGhidraHeadedDebuggerGUITest.java @@ -43,8 +43,7 @@ import docking.widgets.tree.GTreeNode; import generic.Unique; import ghidra.app.plugin.core.debug.gui.action.*; import ghidra.app.plugin.core.debug.mapping.*; -import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceInternal; -import ghidra.app.plugin.core.debug.service.model.DebuggerModelServiceProxyPlugin; +import ghidra.app.plugin.core.debug.service.model.*; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.services.*; import ghidra.app.util.viewer.listingpanel.ListingPanel; @@ -521,6 +520,8 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest @After public void tearDown() { + runSwing(() -> traceManager.setSaveTracesByDefault(false)); + if (tb != null) { if (traceManager != null && traceManager.getOpenTraces().contains(tb.trace)) { traceManager.closeTrace(tb.trace); @@ -531,8 +532,6 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest if (mb != null) { if (mb.testModel != null) { modelService.removeModel(mb.testModel); - - runSwing(() -> traceManager.setSaveTracesByDefault(false)); for (TraceRecorder recorder : modelService.getTraceRecorders()) { recorder.stopRecording(); } @@ -588,6 +587,17 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest tb = new ToyDBTraceBuilder(trace); } + protected DebuggerTargetTraceMapper createTargetTraceMapper(TargetObject target) + throws Exception { + return new TestDebuggerTargetTraceMapper(target) { + @Override + public TraceRecorder startRecording(DebuggerModelServicePlugin service, Trace trace) { + useTrace(trace); + return super.startRecording(service, trace); + } + }; + } + protected void createAndOpenTrace(String langID) throws IOException { createTrace(langID); traceManager.openTrace(tb.trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java index b89d3618ac..34092d421e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointMarkerPluginTest.java @@ -137,7 +137,7 @@ public class DebuggerBreakpointMarkerPluginTest extends AbstractGhidraHeadedDebu createTestModel(); mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); createProgramFromTrace(trace); intoProject(trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderObjectTest.java new file mode 100644 index 0000000000..18a8209827 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderObjectTest.java @@ -0,0 +1,98 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.breakpoint; + +import java.io.IOException; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.trace.model.Trace; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerBreakpointsProviderObjectTest extends DebuggerBreakpointsProviderTest { + + protected SchemaContext ctx; + + @Override + protected void createTrace(String langID) throws IOException { + super.createTrace(langID); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + @Override + protected void useTrace(Trace trace) { + super.useTrace(trace); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + public void activateObjectsMode() throws Exception { + // NOTE the use of index='...' allowing object-based managers to ID unique path + // TODO: I guess this'll burn down if the naming scheme changes.... + int index = tb.trace.getName().startsWith("[3]") ? 3 : 1; + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // <---- NOTE HERE + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java index 3f105fab3a..232b3e0607 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProviderTest.java @@ -121,7 +121,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge createTestModel(); mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); addLiveMemoryAndBreakpoint(mb.testProcess1, recorder); @@ -145,7 +145,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge createTestModel(); mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); addLiveMemoryAndBreakpoint(mb.testProcess1, recorder); @@ -210,7 +210,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge createTestModel(); mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); createProgramFromTrace(trace); intoProject(trace); @@ -471,7 +471,7 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge createTestModel(); mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); createProgramFromTrace(trace); intoProject(trace); @@ -512,12 +512,15 @@ public class DebuggerBreakpointsProviderTest extends AbstractGhidraHeadedDebugge public void testActionFilters() throws Exception { createTestModel(); mb.createTestProcessesAndThreads(); + TraceRecorder recorder1 = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace1 = recorder1.getTrace(); + TraceRecorder recorder3 = modelService.recordTarget(mb.testProcess3, - new TestDebuggerTargetTraceMapper(mb.testProcess3)); + createTargetTraceMapper(mb.testProcess3)); Trace trace3 = recorder3.getTrace(); + createProgramFromTrace(trace1); intoProject(trace1); intoProject(trace3); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java index 7506b6b3f3..00050590ee 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPluginTest.java @@ -37,7 +37,6 @@ import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingProvider; import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.app.services.TraceRecorder; import ghidra.dbg.DebuggerModelListener; import ghidra.dbg.target.TargetObject; import ghidra.program.model.address.*; @@ -457,9 +456,7 @@ public class DebuggerCopyActionsPluginTest extends AbstractGhidraHeadedDebuggerG mb.testModel.addModelListener(listener); mb.createTestProcessesAndThreads(); - TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); - useTrace(recorder.getTrace()); + modelService.recordTarget(mb.testProcess1, createTargetTraceMapper(mb.testProcess1)); mb.testProcess1.memory.addRegion(".text", mb.rng(0x55550000, 0x5555ffff), "rx"); mb.testProcess1.memory.setMemory(mb.addr(0x55550000), mb.arr(1, 2, 3, 4, 5, 6, 7, 8)); waitForPass(() -> { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java index ed9bc6f32f..7e5d17ff4e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/listing/DebuggerListingProviderTest.java @@ -53,11 +53,11 @@ import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.memory.DBTraceMemoryManager; -import ghidra.trace.database.stack.DBTraceStack; import ghidra.trace.database.stack.DBTraceStackManager; import ghidra.trace.model.*; import ghidra.trace.model.memory.*; import ghidra.trace.model.modules.TraceModule; +import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.util.database.UndoableTransaction; @@ -632,7 +632,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); @@ -960,7 +960,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x555500ff), "rx"); @@ -1256,7 +1256,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); thread = tb.getOrAddThread("Thread 1", 0); DBTraceStackManager sm = tb.trace.getStackManager(); - DBTraceStack stack = sm.getStack(thread, 0, true); + TraceStack stack = sm.getStack(thread, 0, true); stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); stack.getFrame(1, true).setProgramCounter(tb.addr(0x00404321)); } @@ -1318,7 +1318,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16))); DBTraceStackManager sm = tb.trace.getStackManager(); - DBTraceStack stack = sm.getStack(thread, 0, true); + TraceStack stack = sm.getStack(thread, 0, true); stack.getFrame(0, true); } waitForDomainObject(tb.trace); @@ -1348,7 +1348,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); thread = tb.getOrAddThread("Thread 1", 0); - DBTraceStack stack = sm.getStack(thread, 0, true); + TraceStack stack = sm.getStack(thread, 0, true); stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); } waitForDomainObject(tb.trace); @@ -1358,7 +1358,7 @@ public class DebuggerListingProviderTest extends AbstractGhidraHeadedDebuggerGUI assertEquals(tb.addr(0x00401234), listingProvider.getLocation().getAddress()); try (UndoableTransaction tid = tb.startTransaction()) { - DBTraceStack stack = sm.getStack(thread, 0, true); + TraceStack stack = sm.getStack(thread, 0, true); stack.getFrame(0, true).setProgramCounter(tb.addr(0x00404321)); } waitForDomainObject(tb.trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java index 30612df880..40b1462194 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerMemoryBytesProviderTest.java @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.core.debug.gui.memory; -import static ghidra.lifecycle.Unfinished.*; +import static ghidra.lifecycle.Unfinished.TODO; import static org.junit.Assert.*; import java.awt.*; @@ -55,11 +55,11 @@ import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.memory.DBTraceMemoryManager; -import ghidra.trace.database.stack.DBTraceStack; import ghidra.trace.database.stack.DBTraceStackManager; import ghidra.trace.model.Trace; import ghidra.trace.model.memory.*; import ghidra.trace.model.modules.TraceModule; +import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.time.TraceSnapshot; import ghidra.util.database.UndoableTransaction; @@ -468,7 +468,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); @@ -762,7 +762,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x555500ff), "rx"); @@ -957,7 +957,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); thread = tb.getOrAddThread("Thread 1", 0); DBTraceStackManager sm = tb.trace.getStackManager(); - DBTraceStack stack = sm.getStack(thread, 0, true); + TraceStack stack = sm.getStack(thread, 0, true); stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); stack.getFrame(1, true).setProgramCounter(tb.addr(0x00404321)); } @@ -1019,7 +1019,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge regs.setValue(0, new RegisterValue(pc, new BigInteger("00401234", 16))); DBTraceStackManager sm = tb.trace.getStackManager(); - DBTraceStack stack = sm.getStack(thread, 0, true); + TraceStack stack = sm.getStack(thread, 0, true); stack.getFrame(0, true); } waitForDomainObject(tb.trace); @@ -1049,7 +1049,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge mm.addRegion("exe:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); thread = tb.getOrAddThread("Thread 1", 0); - DBTraceStack stack = sm.getStack(thread, 0, true); + TraceStack stack = sm.getStack(thread, 0, true); stack.getFrame(0, true).setProgramCounter(tb.addr(0x00401234)); } waitForDomainObject(tb.trace); @@ -1059,7 +1059,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge assertEquals(tb.addr(0x00401234), memBytesProvider.getLocation().getAddress()); try (UndoableTransaction tid = tb.startTransaction()) { - DBTraceStack stack = sm.getStack(thread, 0, true); + TraceStack stack = sm.getStack(thread, 0, true); stack.getFrame(0, true).setProgramCounter(tb.addr(0x00404321)); } waitForDomainObject(tb.trace); @@ -1074,7 +1074,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); @@ -1106,7 +1106,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); @@ -1150,7 +1150,7 @@ public class DebuggerMemoryBytesProviderTest extends AbstractGhidraHeadedDebugge mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); mb.testProcess1.addRegion("exe:.text", mb.rng(0x55550000, 0x5555ffff), "rx"); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderObjectTest.java new file mode 100644 index 0000000000..3f24e80448 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderObjectTest.java @@ -0,0 +1,59 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.memory; + +import java.io.IOException; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerRegionsProviderObjectTest extends DebuggerRegionsProviderTest { + + protected SchemaContext ctx; + + @Override + protected void createTrace(String langID) throws IOException { + super.createTrace(langID); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + public void activateObjectsMode() throws Exception { + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java index 362bf01776..02600cdd09 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProviderTest.java @@ -62,13 +62,13 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI protected void addRegions() throws Exception { TraceMemoryManager mm = tb.trace.getMemoryManager(); try (UndoableTransaction tid = tb.startTransaction()) { - regionExeText = mm.createRegion("Regions[/bin/echo 0x55550000]", 0, + regionExeText = mm.createRegion("Memory[/bin/echo 0x55550000]", 0, tb.range(0x55550000, 0x555500ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); - regionExeData = mm.createRegion("Regions[/bin/echo 0x55750000]", 0, + regionExeData = mm.createRegion("Memory[/bin/echo 0x55750000]", 0, tb.range(0x55750000, 0x5575007f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); - regionLibText = mm.createRegion("Regions[/lib/libc.so 0x7f000000]", 0, + regionLibText = mm.createRegion("Memory[/lib/libc.so 0x7f000000]", 0, tb.range(0x7f000000, 0x7f0003ff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); - regionLibData = mm.createRegion("Regions[/lib/libc.so 0x7f100000]", 0, + regionLibData = mm.createRegion("Memory[/lib/libc.so 0x7f100000]", 0, tb.range(0x7f100000, 0x7f10003f), TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); } } @@ -104,7 +104,8 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryRegion region; try (UndoableTransaction tid = tb.startTransaction()) { TraceMemoryManager mm = tb.trace.getMemoryManager(); - region = mm.addRegion("bin:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + region = mm.addRegion("Memory[bin:.text]", Range.atLeast(0L), + tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); } @@ -114,7 +115,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI RegionRow row = Unique.assertOne(provider.regionTableModel.getModelData()); assertEquals(region, row.getRegion()); - assertEquals("bin:.text", row.getName()); + assertEquals("Memory[bin:.text]", row.getName()); assertEquals(tb.addr(0x00400000), row.getMinAddress()); assertEquals(tb.addr(0x0040ffff), row.getMaxAddress()); assertEquals(tb.range(0x00400000, 0x0040ffff), row.getRange()); @@ -132,7 +133,8 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryRegion region; try (UndoableTransaction tid = tb.startTransaction()) { TraceMemoryManager mm = tb.trace.getMemoryManager(); - region = mm.addRegion("bin:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + region = mm.addRegion("Memory[bin:.text]", Range.atLeast(0L), + tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); } @@ -149,7 +151,8 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryRegion region; try (UndoableTransaction tid = tb.startTransaction()) { TraceMemoryManager mm = tb.trace.getMemoryManager(); - region = mm.addRegion("bin:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + region = mm.addRegion("Memory[bin:.text]", Range.atLeast(0L), + tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); } @@ -174,7 +177,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI try (UndoableTransaction tid = tb.startTransaction()) { TraceMemoryManager mm = tb.trace.getMemoryManager(); - mm.addRegion("bin:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + mm.addRegion("Memory[bin:.text]", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); } @@ -197,7 +200,7 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI try (UndoableTransaction tid = tb.startTransaction()) { TraceMemoryManager mm = tb.trace.getMemoryManager(); - mm.addRegion("bin:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + mm.addRegion("Memory[bin:.text]", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); waitForDomainObject(tb.trace); @@ -218,7 +221,8 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryRegion region; try (UndoableTransaction tid = tb.startTransaction()) { TraceMemoryManager mm = tb.trace.getMemoryManager(); - region = mm.addRegion("bin:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + region = mm.addRegion("Memory[bin:.text]", Range.atLeast(0L), + tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); } @@ -333,7 +337,8 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryRegion region; try (UndoableTransaction tid = tb.startTransaction()) { TraceMemoryManager mm = tb.trace.getMemoryManager(); - region = mm.addRegion("bin:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + region = mm.addRegion("Memory[bin:.text]", Range.atLeast(0L), + tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); } @@ -365,7 +370,8 @@ public class DebuggerRegionsProviderTest extends AbstractGhidraHeadedDebuggerGUI TraceMemoryRegion region; try (UndoableTransaction tid = tb.startTransaction()) { TraceMemoryManager mm = tb.trace.getMemoryManager(); - region = mm.addRegion("bin:.text", Range.atLeast(0L), tb.range(0x00400000, 0x0040ffff), + region = mm.addRegion("Memory[bin:.text]", Range.atLeast(0L), + tb.range(0x00400000, 0x0040ffff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); } diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderObjectTest.java new file mode 100644 index 0000000000..cfcb07b198 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderObjectTest.java @@ -0,0 +1,95 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.modules; + +import java.io.IOException; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.trace.model.Trace; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerModulesProviderObjectTest extends DebuggerModulesProviderTest { + + protected SchemaContext ctx; + + @Override + protected void createTrace(String langID) throws IOException { + super.createTrace(langID); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + @Override + protected void useTrace(Trace trace) { + super.useTrace(trace); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + public void activateObjectsMode() throws Exception { + // NOTE the use of index='1' allowing object-based managers to ID unique path + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // <---- NOTE HERE + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java index c3cfed4c8c..6beddac12a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModulesProviderTest.java @@ -100,7 +100,8 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI else { throw new AssertionError(); } - manager.addRegion(module.getName() + ":" + section.getName(), + manager.addRegion( + "Processes[1].Memory[" + module.getName() + ":" + section.getName() + "]", module.getLifespan(), section.getRange(), flags); } } @@ -110,19 +111,19 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI protected void addModules() throws Exception { TraceModuleManager manager = tb.trace.getModuleManager(); try (UndoableTransaction tid = tb.startTransaction()) { - modExe = manager.addLoadedModule("first_proc", "first_proc", + modExe = manager.addLoadedModule("Processes[1].Modules[first_proc]", "first_proc", tb.range(0x55550000, 0x5575007f), 0); - secExeText = - modExe.addSection("first_proc[.text]", ".text", tb.range(0x55550000, 0x555500ff)); - secExeData = - modExe.addSection("first_proc[.data]", ".data", tb.range(0x55750000, 0x5575007f)); + secExeText = modExe.addSection("Processes[1].Modules[first_proc].Sections[.text]", + ".text", tb.range(0x55550000, 0x555500ff)); + secExeData = modExe.addSection("Processes[1].Modules[first_proc].Sections[.data]", + ".data", tb.range(0x55750000, 0x5575007f)); - modLib = manager.addLoadedModule("some_lib", "some_lib", + modLib = manager.addLoadedModule("Processes[1].Modules[some_lib]", "some_lib", tb.range(0x7f000000, 0x7f10003f), 0); - secLibText = - modLib.addSection("some_lib[.text]", ".text", tb.range(0x7f000000, 0x7f0003ff)); - secLibData = - modLib.addSection("some_lib[.data]", ".data", tb.range(0x7f100000, 0x7f10003f)); + secLibText = modLib.addSection("Processes[1].Modules[some_lib].Sections[.text]", + ".text", tb.range(0x7f000000, 0x7f0003ff)); + secLibData = modLib.addSection("Processes[1].Modules[some_lib].Sections[.data]", + ".data", tb.range(0x7f100000, 0x7f10003f)); } } @@ -144,8 +145,10 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI } protected void assertProviderPopulated() { - List modulesDisplayed = modulesProvider.moduleTableModel.getModelData(); - // I should be able to assume this is sorted by base address + List modulesDisplayed = + new ArrayList<>(modulesProvider.moduleTableModel.getModelData()); + modulesDisplayed.sort(Comparator.comparing(r -> r.getBase())); + // I should be able to assume this is sorted by base address. It's the default sort column. assertEquals(2, modulesDisplayed.size()); ModuleRow execRow = modulesDisplayed.get(0); @@ -156,7 +159,9 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI ModuleRow libRow = modulesDisplayed.get(1); assertEquals(tb.addr(0x7f000000), libRow.getBase()); - List sectionsDisplayed = modulesProvider.sectionTableModel.getModelData(); + List sectionsDisplayed = + new ArrayList<>(modulesProvider.sectionTableModel.getModelData()); + sectionsDisplayed.sort(Comparator.comparing(r -> r.getStart())); assertEquals(4, sectionsDisplayed.size()); SectionRow execTextRow = sectionsDisplayed.get(0); @@ -261,13 +266,17 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI } waitForDomainObject(tb.trace); - List modulesDisplayed = modulesProvider.moduleTableModel.getModelData(); + List modulesDisplayed = + new ArrayList<>(modulesProvider.moduleTableModel.getModelData()); + modulesDisplayed.sort(Comparator.comparing(r -> r.getBase())); assertEquals(1, modulesDisplayed.size()); ModuleRow libRow = modulesDisplayed.get(0); assertEquals("some_lib", libRow.getName()); - List sectionsDisplayed = modulesProvider.sectionTableModel.getModelData(); + List sectionsDisplayed = + new ArrayList<>(modulesProvider.sectionTableModel.getModelData()); + sectionsDisplayed.sort(Comparator.comparing(r -> r.getStart())); assertEquals(2, sectionsDisplayed.size()); SectionRow libTextRow = sectionsDisplayed.get(0); @@ -543,13 +552,15 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); // TODO: A region should not be required first. Just to get a memMapper? - mb.testProcess1.addRegion("first_proc:.text", mb.rng(0x55550000, 0x555500ff), "rx"); + mb.testProcess1.addRegion("Memory[first_proc:.text]", mb.rng(0x55550000, 0x555500ff), + "rx"); TestTargetModule module = - mb.testProcess1.modules.addModule("first_proc", mb.rng(0x55550000, 0x555500ff)); + mb.testProcess1.modules.addModule("Modules[first_proc]", + mb.rng(0x55550000, 0x555500ff)); // NOTE: A section should not be required at this point. TestTargetTypedefDataType typedef = module.types.addTypedefDataType("myInt", new DefaultTargetPrimitiveDataType(PrimitiveKind.SINT, 4)); @@ -577,7 +588,7 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI conv.convertTargetDataType(typedef).get(DEFAULT_WAIT_TIMEOUT, TimeUnit.MILLISECONDS); // TODO: Some heuristic or convention to extract the module name, if applicable waitForPass(() -> { - DataType actType = dtm.getDataType("/Processes[1].Modules[first_proc].Types/myInt"); + DataType actType = dtm.getDataType("/Modules[first_proc].Types/myInt"); assertTypeEquals(expType, actType); }); @@ -596,11 +607,12 @@ public class DebuggerModulesProviderTest extends AbstractGhidraHeadedDebuggerGUI mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); // TODO: A region should not be required first. Just to get a memMapper? - mb.testProcess1.addRegion("first_proc:.text", mb.rng(0x55550000, 0x555500ff), "rx"); + mb.testProcess1.addRegion("first_proc:.text", mb.rng(0x55550000, 0x555500ff), + "rx"); TestTargetModule module = mb.testProcess1.modules.addModule("first_proc", mb.rng(0x55550000, 0x555500ff)); // NOTE: A section should not be required at this point. diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java index e891e9c03c..a6a2ce7d5a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProviderTest.java @@ -146,7 +146,7 @@ public class DebuggerRegistersProviderTest extends AbstractGhidraHeadedDebuggerG Register::isBaseRegister); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); waitFor(() -> { TraceThread thread = recorder.getTraceThread(mb.testThread1); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderObjectTest.java new file mode 100644 index 0000000000..b9f4c9be73 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderObjectTest.java @@ -0,0 +1,96 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.stack; + +import java.io.IOException; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.trace.model.Trace; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerStackProviderObjectTest extends DebuggerStackProviderTest { + + protected SchemaContext ctx; + + @Override + protected void createTrace(String langID) throws IOException { + super.createTrace(langID); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + @Override + protected void useTrace(Trace trace) { + super.useTrace(trace); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + public void activateObjectsMode() throws Exception { + // NOTE the use of index='1' allowing object-based managers to ID unique path + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // <---- NOTE HERE + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java index a85db35c31..92e7ef8ac7 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProviderTest.java @@ -155,7 +155,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testActivateThreadNoStackNoRegsEmpty() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); waitForDomainObject(tb.trace); traceManager.activateThread(thread); @@ -168,7 +168,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testActivateThreadNoStackRegsSynthetic() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); addRegVals(thread); waitForDomainObject(tb.trace); @@ -182,7 +182,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testActivateThreadRegsThenAddEmptyStackEmpty() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); addRegVals(thread); addStack(thread); waitForDomainObject(tb.trace); @@ -197,7 +197,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testActivateThreadThenAddStackPopulatesProvider() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); traceManager.activateThread(thread); TraceStack stack = addStack(thread); addStackFrames(stack); @@ -210,7 +210,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testAddStackThenActivateThreadPopulatesProvider() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -225,7 +225,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testAppendStackUpdatesProvider() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -250,7 +250,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testPushStackUpdatesProvider() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -275,7 +275,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testTruncateStackUpdatesProvider() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -298,7 +298,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testPopStackUpdatesProvider() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -321,7 +321,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testDeleteStackUpdatesProvider() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -343,8 +343,8 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testActivateOtherThread() throws Exception { createAndOpenTrace(); - TraceThread thread1 = addThread("Thread 1"); - TraceThread thread2 = addThread("Thread 2"); + TraceThread thread1 = addThread("Processes[1].Threads[1]"); + TraceThread thread2 = addThread("Processes[1].Threads[2]"); TraceStack stack = addStack(thread1); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -364,7 +364,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testActivateSnap() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -389,7 +389,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testCloseCurrentTraceEmpty() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -410,7 +410,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testSelectRowActivatesFrame() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -435,7 +435,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe public void testActivateFrameSelectsRow() throws Exception { createAndOpenTrace(); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -467,7 +467,7 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe traceManager.openTrace(tb.trace); programManager.openProgram(program); - TraceThread thread = addThread("Thread 1"); + TraceThread thread = addThread("Processes[1].Threads[1]"); TraceStack stack = addStack(thread); addStackFrames(stack); waitForDomainObject(tb.trace); @@ -491,7 +491,8 @@ public class DebuggerStackProviderTest extends AbstractGhidraHeadedDebuggerGUITe try (UndoableTransaction tid = tb.startTransaction()) { tb.trace.getMemoryManager() - .addRegion("bin:.text", Range.atLeast(0L), tb.drng(0x00400000, 0x00400fff), + .addRegion("Processes[1].Memory[bin:.text]", Range.atLeast(0L), + tb.drng(0x00400000, 0x00400fff), TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); TraceLocation dloc = diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderObjectTest.java new file mode 100644 index 0000000000..ae9d9487eb --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderObjectTest.java @@ -0,0 +1,79 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.gui.thread; + +import java.io.IOException; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.trace.model.Trace; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerThreadsProviderObjectTest extends DebuggerThreadsProviderTest { + + protected SchemaContext ctx; + + @Override + protected void createTrace(String langID) throws IOException { + super.createTrace(langID); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + @Override + protected void useTrace(Trace trace) { + super.useTrace(trace); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + public void activateObjectsMode() throws Exception { + // NOTE the use of index='1' allowing object-based managers to ID unique path + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // <---- NOTE HERE + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java index 46ac31f791..0764b66f01 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProviderTest.java @@ -55,9 +55,9 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI protected void addThreads() throws Exception { TraceThreadManager manager = tb.trace.getThreadManager(); try (UndoableTransaction tid = tb.startTransaction()) { - thread1 = manager.addThread("Thread 1", Range.atLeast(0L)); + thread1 = manager.addThread("Processes[1].Threads[1]", Range.atLeast(0L)); thread1.setComment("A comment"); - thread2 = manager.addThread("Thread 2", Range.closed(5L, 10L)); + thread2 = manager.addThread("Processes[1].Threads[2]", Range.closed(5L, 10L)); thread2.setComment("Another comment"); } } @@ -99,7 +99,7 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI ThreadRow thread1Record = threadsDisplayed.get(0); assertEquals(thread1, thread1Record.getThread()); - assertEquals("Thread 1", thread1Record.getName()); + assertEquals("Processes[1].Threads[1]", thread1Record.getName()); assertEquals(Range.atLeast(0L), thread1Record.getLifespan()); assertEquals(0, thread1Record.getCreationSnap()); assertEquals("", thread1Record.getDestructionSnap()); @@ -475,13 +475,15 @@ public class DebuggerThreadsProviderTest extends AbstractGhidraHeadedDebuggerGUI // Not live, so no seek assertEquals(0, traceManager.getCurrentSnap()); + tb.close(); + createTestModel(); mb.createTestProcessesAndThreads(); // Threads needs registers to be recognized by the recorder mb.createTestThreadRegisterBanks(); TraceRecorder recorder = modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); // Wait till two threads are observed in the database diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java index 930bdf7d0b..7e07b5e73a 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProviderTest.java @@ -208,7 +208,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI mb.testProcess1.memory.writeMemory(mb.addr(0x00400000), tb.arr(1, 2, 3, 4)); recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); @@ -348,7 +348,7 @@ public class DebuggerWatchesProviderTest extends AbstractGhidraHeadedDebuggerGUI mb.testProcess1.addRegion(".text", mb.rng(0x00400000, 0x00401000), "rx"); recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceObjectTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceObjectTest.java new file mode 100644 index 0000000000..acee8c2a9a --- /dev/null +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceObjectTest.java @@ -0,0 +1,99 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.service.breakpoint; + +import java.io.IOException; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.trace.model.Trace; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerLogicalBreakpointServiceObjectTest + extends DebuggerLogicalBreakpointServiceTest { + + protected SchemaContext ctx; + + @Override + protected void createTrace(String langID) throws IOException { + super.createTrace(langID); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + @Override + protected void useTrace(Trace trace) { + super.useTrace(trace); + try { + activateObjectsMode(); + } + catch (Exception e) { + throw new AssertionError(e); + } + } + + public void activateObjectsMode() throws Exception { + // NOTE the use of index='...' allowing object-based managers to ID unique path + // TODO: I guess this'll burn down if the naming scheme changes.... + int index = tb.trace.getName().startsWith("[3]") ? 3 : 1; + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // <---- NOTE HERE + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java index 32c207a659..88738338ac 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/breakpoint/DebuggerLogicalBreakpointServiceTest.java @@ -74,7 +74,8 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe @Override public synchronized void breakpointUpdated(LogicalBreakpoint lb) { - Msg.debug(this, "LogicalBreakpoint updated: " + lb); + Msg.debug(this, + "LogicalBreakpoint updated: (" + System.identityHashCode(lb) + ")" + lb); assertTrue(current.contains(lb)); } @@ -165,12 +166,12 @@ public class DebuggerLogicalBreakpointServiceTest extends AbstractGhidraHeadedDe public void startRecorder1() throws Throwable { recorder1 = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); } public void startRecorder3() throws Throwable { recorder3 = modelService.recordTarget(mb.testProcess3, - new TestDebuggerTargetTraceMapper(mb.testProcess3)); + createTargetTraceMapper(mb.testProcess3)); } @After diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java index d7092243fd..cea425f8c3 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServiceTest.java @@ -213,7 +213,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes assertEquals(Set.of(), Set.copyOf(modelService.getTraceRecorders())); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); assertEquals(Set.of(recorder), Set.copyOf(modelService.getTraceRecorders())); } @@ -228,7 +228,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes new CollectionChangeDelegateWrapper<>(recorderChangeListener); modelService.addTraceRecordersChangedListener(wrapper); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); new VerificationsInOrder() { { @@ -243,7 +243,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); // Strong ref CollectionChangeDelegateWrapper wrapper = new CollectionChangeDelegateWrapper<>(recorderChangeListener); @@ -265,7 +265,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); assertNotNull(recorder); waitOn(recorder.init()); // Already initializing, just wait for it to complete @@ -281,7 +281,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); modelService.recordTargetAndActivateTrace(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); waitForSwing(); Trace trace = traceManager.getCurrentTrace(); @@ -290,7 +290,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes traceManager.closeTrace(trace); waitOn(mb.testModel.close()); waitForPass(() -> { - assertEquals(List.of(), trace.getConsumerList()); + assertEquals(List.of(tb), trace.getConsumerList()); }); } @@ -300,7 +300,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); assertEquals(recorder, modelService.getRecorder(mb.testProcess1)); } @@ -311,7 +311,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); assertEquals(recorder, modelService.getRecorder(recorder.getTrace())); } @@ -322,7 +322,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); assertEquals(mb.testProcess1, modelService.getTarget(recorder.getTrace())); } @@ -333,7 +333,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); assertEquals(recorder.getTrace(), modelService.getTrace(mb.testProcess1)); } @@ -344,7 +344,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); // The most complicated case, lest I want another dimension in a cross product mb.createTestThreadStacksAndFramesHaveRegisterBanks(); @@ -373,7 +373,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes preRec.run(); modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); postRec.run(); @@ -420,7 +420,7 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); // The most complicated case, lest I want another dimension in a cross product mb.createTestThreadStacksAndFramesHaveRegisterBanks(); @@ -439,9 +439,9 @@ public class DebuggerModelServiceTest extends AbstractGhidraHeadedDebuggerGUITes // NOTE: getTargetFocus assumes the target is being recorded modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); modelService.recordTarget(mb.testProcess3, - new TestDebuggerTargetTraceMapper(mb.testProcess3)); + createTargetTraceMapper(mb.testProcess3)); assertNull(modelService.getTargetFocus(mb.testProcess1)); assertNull(modelService.getTargetFocus(mb.testProcess3)); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorderTest.java index 4962894f07..8c3c5bb4cf 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/model/DefaultTraceRecorderTest.java @@ -48,7 +48,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); waitForPass(() -> { assertNotNull(recorder.getTraceThread(mb.testThread1)); assertNotNull(recorder.getTraceThread(mb.testThread2)); @@ -61,7 +61,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); TestTargetMemoryRegion targetRegion = @@ -97,7 +97,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); Language lang = trace.getBaseLanguage(); Register r0 = lang.getRegister("r0"); @@ -131,7 +131,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); Language lang = trace.getBaseLanguage(); Register pc = lang.getRegister("pc"); @@ -180,7 +180,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); Language lang = trace.getBaseLanguage(); Register pc = lang.getRegister("pc"); @@ -229,7 +229,7 @@ public class DefaultTraceRecorderTest extends AbstractGhidraHeadedDebuggerGUITes mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); Language lang = trace.getBaseLanguage(); Register pc = lang.getRegister("pc"); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java index 9dfcd6c7c6..5079f5f672 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/tracemgr/DebuggerTraceManagerServiceTest.java @@ -31,7 +31,6 @@ import ghidra.dbg.model.TestTargetStack; import ghidra.dbg.model.TestTargetStackFrameHasRegisterBank; import ghidra.dbg.testutil.DebuggerModelTestUtils; import ghidra.framework.model.DomainFile; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace; import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.thread.TraceThread; @@ -116,7 +115,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge assertNull(traceManager.getCurrentThread()); - DBTraceThread thread; + TraceThread thread; try (UndoableTransaction tid = tb.startTransaction()) { thread = tb.getOrAddThread("Thread 1", 0); } @@ -297,7 +296,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); traceManager.openTrace(trace); @@ -338,7 +337,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); waitForValue(() -> modelService.getTarget(trace)); @@ -400,7 +399,7 @@ public class DebuggerTraceManagerServiceTest extends AbstractGhidraHeadedDebugge mb.createTestProcessesAndThreads(); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); Trace trace = recorder.getTrace(); traceManager.openTrace(trace); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java index 0a81ca9e37..ca6c76073b 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java @@ -50,7 +50,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge "r1", new byte[] { 6 }))); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); Trace trace = recorder.getTrace(); @@ -93,7 +93,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge "r1", new byte[] { 6 }))); TraceRecorder recorder = modelService.recordTarget(mb.testProcess1, - new TestDebuggerTargetTraceMapper(mb.testProcess1)); + createTargetTraceMapper(mb.testProcess1)); TraceThread thread = waitForValue(() -> recorder.getTraceThread(mb.testThread1)); Trace trace = recorder.getTrace(); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java index 9ed4c65689..be3fc48e2b 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/target/schema/TargetObjectSchema.java @@ -718,6 +718,17 @@ public interface TargetObjectSchema { return null; } + /** + * Find the nearest ancestor implementing the given interface along the given path + * + *

+ * If the given path implements the interface, it is returned, i.e., it is not strictly an + * ancestor. + * + * @param type the interface to search for + * @param path the seed path + * @return the found path, or {@code null} if no ancestor implements the interface + */ default List searchForAncestor(Class type, List path) { for (; path != null; path = PathUtils.parent(path)) { TargetObjectSchema schema = getSuccessorSchema(path); diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathMatcher.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathMatcher.java index 9f84141eae..a12854de21 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathMatcher.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathMatcher.java @@ -38,6 +38,23 @@ public class PathMatcher implements PathPredicates { return String.format("", StringUtils.join(patterns, "\n ")); } + @Override + public PathPredicates or(PathPredicates that) { + PathMatcher result = new PathMatcher(); + result.patterns.addAll(this.patterns); + if (that instanceof PathMatcher) { + PathMatcher matcher = (PathMatcher) that; + result.patterns.addAll(matcher.patterns); + } + else if (that instanceof PathPattern) { + result.patterns.add((PathPattern) that); + } + else { + throw new AssertionError(); + } + return result; + } + /** * TODO: We could probably do a lot better, esp. for many patterns, by using a trie. */ @@ -81,6 +98,23 @@ public class PathMatcher implements PathPredicates { return patterns.iterator().next(); } + @Override + public Set getNextKeys(List path) { + Set result = new HashSet<>(); + for (PathPattern pattern : patterns) { + result.addAll(pattern.getNextKeys(path)); + } + if (result.contains("")) { + result.removeIf(PathUtils::isName); + result.add(""); + } + if (result.contains("[]")) { + result.removeIf(PathUtils::isIndex); + result.add("[]"); + } + return result; + } + @Override public Set getNextNames(List path) { Set result = new HashSet<>(); @@ -111,10 +145,10 @@ public class PathMatcher implements PathPredicates { } @Override - public PathMatcher applyIndices(List indices) { + public PathMatcher applyKeys(List indices) { PathMatcher result = new PathMatcher(); for (PathPattern pat : patterns) { - result.addPattern(pat.applyIndices(indices)); + result.addPattern(pat.applyKeys(indices)); } return result; } diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPattern.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPattern.java index a688a46120..16a22100dd 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPattern.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPattern.java @@ -62,26 +62,33 @@ public class PathPattern implements PathPredicates { return pattern.hashCode(); } + @Override + public PathPredicates or(PathPredicates that) { + if (this.equals(that)) { + return this; + } + PathMatcher result = new PathMatcher(); + result.addPattern(this); + if (that instanceof PathPattern) { + result.addPattern(this); + } + else if (that instanceof PathMatcher) { + PathMatcher matcher = (PathMatcher) that; + result.patterns.addAll(matcher.patterns); + } + else { + throw new AssertionError(); + } + return result; + } + public static boolean isWildcard(String pat) { return "[]".equals(pat) || "".equals(pat); } - public static boolean keyMatches(String pat, String key) { - if (key.equals(pat)) { - return true; - } - if ("[]".equals(pat) && PathUtils.isIndex(key)) { - return true; - } - if ("".equals(pat) && PathUtils.isName(key)) { - return true; - } - return false; - } - protected boolean matchesUpTo(List path, int length) { for (int i = 0; i < length; i++) { - if (!keyMatches(pattern.get(i), path.get(i))) { + if (!PathPredicates.keyMatches(pattern.get(i), path.get(i))) { return false; } } @@ -144,11 +151,25 @@ public class PathPattern implements PathPredicates { return this; } + @Override + public Set getNextKeys(List path) { + if (path.size() >= pattern.size()) { + return Set.of(); + } + if (!matchesUpTo(path, path.size())) { + return Set.of(); + } + return Set.of(pattern.get(path.size())); + } + @Override public Set getNextNames(List path) { if (path.size() >= pattern.size()) { return Set.of(); } + if (!matchesUpTo(path, path.size())) { + return Set.of(); + } String pat = pattern.get(path.size()); if (PathUtils.isName(pat)) { return Set.of(pat); @@ -161,6 +182,9 @@ public class PathPattern implements PathPredicates { if (path.size() >= pattern.size()) { return Set.of(); } + if (!matchesUpTo(path, path.size())) { + return Set.of(); + } String pat = pattern.get(path.size()); if (PathUtils.isIndex(pat)) { return Set.of(PathUtils.parseIndex(pat)); @@ -174,7 +198,7 @@ public class PathPattern implements PathPredicates { } @Override - public PathPattern applyIndices(List indices) { + public PathPattern applyKeys(List indices) { List result = new ArrayList<>(pattern.size()); Iterator it = indices.iterator(); for (String pat : pattern) { @@ -196,18 +220,18 @@ public class PathPattern implements PathPredicates { } /** - * If the given path matches, extract indices where matched by wildcards + * If the given path matches, extract keys where matched by wildcards * *

- * This is essentially the inverse of {@link #applyIndices(List)}, but can only be asked of one + * This is essentially the inverse of {@link #applyKeys(List)}, but can only be asked of one * pattern. The keys are returned from left to right, in the order matched by the pattern. Only * those keys matched by a wildcard are included in the result. Indices are extracted with the * brackets {@code []} removed. * * @param path the path to match - * @return the list of matched indices or {@code null} if not matched + * @return the list of matched keys or {@code null} if not matched */ - public List matchIndices(List path) { + public List matchKeys(List path) { int length = pattern.size(); if (length != path.size()) { return null; @@ -216,7 +240,7 @@ public class PathPattern implements PathPredicates { for (int i = 0; i < length; i++) { String pat = pattern.get(i); String key = path.get(i); - if (!keyMatches(pat, key)) { + if (!PathPredicates.keyMatches(pat, key)) { return null; } if (isWildcard(pat)) { diff --git a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java index ce0772334c..c03478cde3 100644 --- a/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java +++ b/Ghidra/Debug/Framework-Debugging/src/main/java/ghidra/dbg/util/PathPredicates.java @@ -23,6 +23,38 @@ import ghidra.dbg.target.TargetObject; import ghidra.dbg.util.PathUtils.PathComparator; public interface PathPredicates { + + static boolean keyMatches(String pat, String key) { + if (key.equals(pat)) { + return true; + } + if ("[]".equals(pat)) { + return PathUtils.isIndex(key); + } + if ("".equals(pat)) { + return PathUtils.isName(key); + } + return false; + } + + static boolean anyMatches(Set pats, String key) { + return pats.stream().anyMatch(p -> keyMatches(p, key)); + } + + static PathPredicates pattern(String... keyPatterns) { + return new PathPattern(List.of(keyPatterns)); + } + + static PathPredicates pattern(List keyPatterns) { + return new PathPattern(keyPatterns); + } + + static PathPredicates parse(String pattern) { + return new PathPattern(PathUtils.parse(pattern)); + } + + PathPredicates or(PathPredicates that); + /** * Check if the entire path passes * @@ -59,10 +91,22 @@ public interface PathPredicates { boolean ancestorMatches(List path, boolean strict); /** - * Assuming a successor of path could match, get the patterns for the next possible key + * Get the patterns for the next possible key * *

- * If the pattern could accept a name next, get all patterns describing those names + * If a successor of the given path cannot match this pattern, the empty set is returned. + * + * @param path the ancestor path + * @return a set of patterns where indices are enclosed in brackets ({@code []) + */ + Set getNextKeys(List path); + + /** + * Get the patterns for the next possible name + * + *

+ * If a successor of the given path cannot match this pattern, the empty set is returned. If the + * pattern could accept a name next, get all patterns describing those names * * @param path the ancestor path * @return a set of patterns @@ -73,10 +117,11 @@ public interface PathPredicates { * Assuming a successor of path could match, get the patterns for the next possible index * *

- * If the pattern could accept an index next, get all patterns describing those indices + * If a successor of the given path cannot match this pattern, the empty set is returned. If the + * pattern could accept an index next, get all patterns describing those indices * * @param path the ancestor path - * @return a set of patterns, without brack@Override ets ({@code []) + * @return a set of patterns, without brackets ({@code []) */ Set getNextIndices(List path); @@ -94,18 +139,6 @@ public interface PathPredicates { */ PathPattern getSingletonPattern(); - static boolean anyMatches(Set pats, String key) { - for (String pat : pats) { - if ("".equals(pat)) { - return true; - } - if (key.equals(pat)) { - return true; - } - } - return false; - } - default NavigableMap, ?> getCachedValues(TargetObject seed) { return getCachedValues(List.of(), seed); } @@ -259,10 +292,10 @@ public interface PathPredicates { * @param indices the indices to substitute * @return the pattern or matcher with the applied substitutions */ - PathPredicates applyIndices(List indices); + PathPredicates applyKeys(List indices); default PathPredicates applyIndices(String... indices) { - return applyIndices(List.of(indices)); + return applyKeys(List.of(indices)); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/build.gradle b/Ghidra/Debug/Framework-TraceModeling/build.gradle index 878990745c..0b6f4871d2 100644 --- a/Ghidra/Debug/Framework-TraceModeling/build.gradle +++ b/Ghidra/Debug/Framework-TraceModeling/build.gradle @@ -25,6 +25,7 @@ dependencies { api project(':Generic') api project(':SoftwareModeling') api project(':ProposedUtils') + api project(':Framework-Debugging') annotationProcessor project(':AnnotationValidator') testImplementation project(':Base') 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 a7bce0f4ff..f4bc1d0728 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 @@ -42,7 +42,6 @@ import ghidra.trace.database.language.DBTraceLanguageManager; import ghidra.trace.database.listing.DBTraceCodeManager; import ghidra.trace.database.listing.DBTraceCommentAdapter; import ghidra.trace.database.memory.DBTraceMemoryManager; -import ghidra.trace.database.memory.DBTraceMemoryRegion; import ghidra.trace.database.module.DBTraceModuleManager; import ghidra.trace.database.module.DBTraceStaticMappingManager; import ghidra.trace.database.program.DBTraceProgramView; @@ -50,9 +49,11 @@ import ghidra.trace.database.program.DBTraceVariableSnapProgramView; import ghidra.trace.database.property.DBTraceAddressPropertyManager; import ghidra.trace.database.stack.DBTraceStackManager; import ghidra.trace.database.symbol.*; +import ghidra.trace.database.target.DBTraceObjectManager; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.database.time.DBTraceTimeManager; import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.util.TraceChangeManager; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.*; @@ -104,6 +105,8 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace @DependentService protected DBTraceModuleManager moduleManager; @DependentService + protected DBTraceObjectManager objectManager; + @DependentService protected DBTraceOverlaySpaceAdapter overlaySpaceAdapter; @DependentService protected DBTraceReferenceManager referenceManager; @@ -340,6 +343,14 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace baseLanguage, this)); } + @DependentService + protected DBTraceObjectManager createObjectManager() + throws CancelledException, IOException { + return createTraceManager("Object Manager", + (openMode, monitor) -> new DBTraceObjectManager(dbh, openMode, rwLock, monitor, + baseLanguage, this)); + } + @DependentService protected DBTraceOverlaySpaceAdapter createOverlaySpaceAdapter() throws CancelledException, IOException { @@ -391,9 +402,11 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace } @DependentService - protected DBTraceThreadManager createThreadManager() throws IOException, CancelledException { + protected DBTraceThreadManager createThreadManager(DBTraceObjectManager objectManager) + throws IOException, CancelledException { return createTraceManager("Thread Manager", - (openMode, monitor) -> new DBTraceThreadManager(dbh, openMode, rwLock, monitor, this)); + (openMode, monitor) -> new DBTraceThreadManager(dbh, openMode, rwLock, monitor, this, + objectManager)); } @DependentService @@ -494,6 +507,11 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace return moduleManager; } + @Override + public DBTraceObjectManager getObjectManager() { + return objectManager; + } + @Internal public DBTraceOverlaySpaceAdapter getOverlaySpaceAdapter() { return overlaySpaceAdapter; @@ -716,29 +734,29 @@ public class DBTrace extends DBCachedDomainObjectAdapter implements Trace, Trace } } - public void updateViewsAddRegionBlock(DBTraceMemoryRegion region) { + public void updateViewsAddRegionBlock(TraceMemoryRegion region) { allViews(v -> v.updateMemoryAddRegionBlock(region)); } - public void updateViewsChangeRegionBlockName(DBTraceMemoryRegion region) { + public void updateViewsChangeRegionBlockName(TraceMemoryRegion region) { allViews(v -> v.updateMemoryChangeRegionBlockName(region)); } - public void updateViewsChangeRegionBlockFlags(DBTraceMemoryRegion region) { - allViews(v -> v.updateMemoryChangeRegionBlockFlags(region)); + public void updateViewsChangeRegionBlockFlags(TraceMemoryRegion region, Range lifespan) { + allViews(v -> v.updateMemoryChangeRegionBlockFlags(region, lifespan)); } - public void updateViewsChangeRegionBlockRange(DBTraceMemoryRegion region, + public void updateViewsChangeRegionBlockRange(TraceMemoryRegion region, AddressRange oldRange, AddressRange newRange) { allViews(v -> v.updateMemoryChangeRegionBlockRange(region, oldRange, newRange)); } - public void updateViewsChangeRegionBlockLifespan(DBTraceMemoryRegion region, + public void updateViewsChangeRegionBlockLifespan(TraceMemoryRegion region, Range oldLifespan, Range newLifespan) { allViews(v -> v.updateMemoryChangeRegionBlockLifespan(region, oldLifespan, newLifespan)); } - public void updateViewsDeleteRegionBlock(DBTraceMemoryRegion region) { + public void updateViewsDeleteRegionBlock(TraceMemoryRegion region) { allViews(v -> v.updateMemoryDeleteRegionBlock(region)); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceUtils.java index 95ab01c72a..b94c375fb8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/DBTraceUtils.java @@ -19,13 +19,13 @@ import java.lang.reflect.Field; import java.net.MalformedURLException; import java.net.URL; import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.Objects; +import java.util.*; +import java.util.Map.Entry; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.stream.Collectors; -import com.google.common.collect.BoundType; -import com.google.common.collect.Range; +import com.google.common.collect.*; import db.*; import ghidra.program.model.address.*; @@ -267,6 +267,155 @@ public enum DBTraceUtils { } } + public static abstract class RangeMapSetter, R, V> { + protected abstract R getRange(E entry); + + protected abstract V getValue(E entry); + + protected abstract void remove(E entry); + + protected abstract D getLower(R range); + + protected abstract D getUpper(R range); + + protected abstract R toRange(D lower, D upper); + + protected abstract D getPrevious(D d); + + protected abstract D getNext(D d); + + protected abstract Iterable getIntersecting(D lower, D upper); + + protected abstract E put(R range, V value); + + protected D getPreviousOrSame(D d) { + D prev = getPrevious(d); + if (prev == null) { + return d; + } + return prev; + } + + protected D getNextOrSame(D d) { + D next = getNext(d); + if (next == null) { + return d; + } + return next; + } + + protected boolean connects(R r1, R r2) { + return getPreviousOrSame(getLower(r1)).compareTo(getUpper(r2)) <= 0 || + getPreviousOrSame(getLower(r2)).compareTo(getUpper(r1)) <= 0; + } + + public E set(R range, V value) { + return set(getLower(range), getUpper(range), value); + } + + public E set(D lower, D upper, V value) { + // Go one out to find abutting ranges, too. + D prev = getPreviousOrSame(lower); + D next = getNextOrSame(upper); + Map toPut = new HashMap<>(); + for (E entry : getIntersecting(prev, next)) { + R r = getRange(entry); + boolean precedesMin = getLower(r).compareTo(lower) < 0; + boolean succeedsMax = getUpper(r).compareTo(upper) > 0; + boolean sameVal = Objects.equals(getValue(entry), value); + if (precedesMin && succeedsMax && sameVal) { + return entry; // The value in this range is already set as specified + } + remove(entry); + if (precedesMin) { + if (sameVal) { + lower = getLower(r); + } + else { + toPut.put(toRange(getLower(r), prev), getValue(entry)); + } + } + if (succeedsMax) { + if (sameVal) { + upper = getUpper(r); + } + else { + toPut.put(toRange(next, getUpper(r)), getValue(entry)); + } + } + } + E result = put(toRange(lower, upper), value); + assert toPut.size() <= 2; + for (Entry ent : toPut.entrySet()) { + put(ent.getKey(), ent.getValue()); + } + return result; + } + } + + public static abstract class AddressRangeMapSetter + extends RangeMapSetter { + @Override + protected Address getLower(AddressRange range) { + return range.getMinAddress(); + } + + @Override + protected Address getUpper(AddressRange range) { + return range.getMaxAddress(); + } + + @Override + protected AddressRange toRange(Address lower, Address upper) { + return new AddressRangeImpl(lower, upper); + } + + @Override + protected Address getPrevious(Address d) { + return d.previous(); + } + + @Override + protected Address getNext(Address d) { + return d.next(); + } + } + + public static abstract class LifespanMapSetter + extends RangeMapSetter, V> { + + @Override + protected Long getLower(Range range) { + return lowerEndpoint(range); + } + + @Override + protected Long getUpper(Range range) { + return upperEndpoint(range); + } + + @Override + protected Range toRange(Long lower, Long upper) { + return DBTraceUtils.toRange(lower, upper); + } + + @Override + protected Long getPrevious(Long d) { + if (d == null || d == Long.MIN_VALUE) { + return null; + } + return d - 1; + } + + @Override + protected Long getNext(Long d) { + if (d == null || d == Long.MAX_VALUE) { + return null; + } + return d + 1; + } + } + public static long lowerEndpoint(Range range) { if (!range.hasLowerBound()) { return Long.MIN_VALUE; @@ -386,6 +535,16 @@ public enum DBTraceUtils { lifespanSetter.accept(data, toRange(data.getY1(), lowerEndpoint(span) - 1)); } + public static List> subtract(Range a, Range b) { + RangeSet set = TreeRangeSet.create(); + set.add(a); + set.remove(b); + return set.asRanges() + .stream() + .map(r -> toRange(lowerEndpoint(r), upperEndpoint(r))) + .collect(Collectors.toList()); + } + @SuppressWarnings("unchecked") public static Iterator covariantIterator(Iterator it) { // Iterators only support read and remove, not insert. Safe to cast. diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java index 6222c5acda..f1576c1070 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/address/DBTraceOverlaySpaceAdapter.java @@ -63,11 +63,7 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager { extends AbstractDBFieldCodec { static final Charset UTF8 = Charset.forName("UTF-8"); - public AddressDBFieldCodec(Class objectType, Field field, int column) { - super(Address.class, objectType, BinaryField.class, field, column); - } - - protected byte[] encode(Address address) { + public static byte[] encode(Address address) { if (address == null) { return null; } @@ -86,6 +82,31 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager { return buf.array(); } + public static Address decode(byte[] enc, DBTraceOverlaySpaceAdapter osa) { + if (enc == null) { + return null; + } + else { + ByteBuffer buf = ByteBuffer.wrap(enc); + byte overlay = buf.get(); + final AddressSpace as; + if (overlay == 1) { + short key = buf.getShort(); + as = osa.spacesByKey.get(key & 0xffffL); + } + else { + short id = buf.getShort(); + as = osa.trace.getInternalAddressFactory().getAddressSpace(id); + } + long offset = buf.getLong(); + return as.getAddress(offset); + } + } + + public AddressDBFieldCodec(Class objectType, Field field, int column) { + super(Address.class, objectType, BinaryField.class, field, column); + } + @Override public void store(Address value, BinaryField f) { f.setBinaryData(encode(value)); @@ -101,25 +122,7 @@ public class DBTraceOverlaySpaceAdapter implements DBTraceManager { protected void doLoad(OT obj, DBRecord record) throws IllegalArgumentException, IllegalAccessException { byte[] data = record.getBinaryData(column); - if (data == null) { - setValue(obj, null); - } - else { - ByteBuffer buf = ByteBuffer.wrap(data); - byte overlay = buf.get(); - final AddressSpace as; - if (overlay == 1) { - short key = buf.getShort(); - as = obj.getOverlaySpaceAdapter().spacesByKey.get(key & 0xffffL); - } - else { - short id = buf.getShort(); - as = obj.getOverlaySpaceAdapter().trace.getInternalAddressFactory() - .getAddressSpace(id); - } - long offset = buf.getLong(); - setValue(obj, as.getAddress(offset)); - } + setValue(obj, decode(data, obj.getOverlaySpaceAdapter())); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmark.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmark.java index aec3fb4c72..f3cd3a0c64 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmark.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmark.java @@ -25,10 +25,10 @@ import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceBookmarkChangeType; import ghidra.trace.model.bookmark.TraceBookmark; import ghidra.trace.model.bookmark.TraceBookmarkType; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; import ghidra.util.database.DBCachedObjectStore; @@ -99,7 +99,7 @@ public class DBTraceBookmark extends AbstractDBTraceAddressSnapRangePropertyMapD } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return space.getThread(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkManager.java index 802abbbfec..fac24ce177 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkManager.java @@ -31,7 +31,6 @@ import ghidra.program.model.lang.Language; import ghidra.trace.database.DBTrace; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.*; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.Trace.TraceBookmarkChangeType; import ghidra.trace.model.bookmark.TraceBookmarkManager; @@ -160,7 +159,7 @@ public class DBTraceBookmarkManager protected static DBTraceSpaceKey unpackRegSpaceKey(AddressSpace space, DBTraceThreadManager threadManager, long id) { long threadKey = unpackRegThread(id); - DBTraceThread thread = threadManager.getThread(threadKey); + TraceThread thread = threadManager.getThread(threadKey); assert thread != null; int frameLevel = unpackRegFrame(id); return DBTraceSpaceKey.create(space, thread, frameLevel); @@ -186,7 +185,7 @@ public class DBTraceBookmarkManager @Override protected DBTraceBookmarkRegisterSpace createRegisterSpace(AddressSpace space, - DBTraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { + TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { return new DBTraceBookmarkRegisterSpace(this, space, thread, ent.getFrameLevel()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkRegisterSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkRegisterSpace.java index a2d1cf3e47..85c08dbe8d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkRegisterSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkRegisterSpace.java @@ -18,24 +18,24 @@ package ghidra.trace.database.bookmark; import java.io.IOException; import ghidra.program.model.address.AddressSpace; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.bookmark.TraceBookmarkRegisterSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.exception.VersionException; public class DBTraceBookmarkRegisterSpace extends DBTraceBookmarkSpace implements TraceBookmarkRegisterSpace { - private final DBTraceThread thread; + private final TraceThread thread; private final int frameLevel; public DBTraceBookmarkRegisterSpace(DBTraceBookmarkManager manager, AddressSpace space, - DBTraceThread thread, int frameLevel) throws VersionException, IOException { + TraceThread thread, int frameLevel) throws VersionException, IOException { super(manager, space); this.thread = thread; this.frameLevel = frameLevel; } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkSpace.java index 8138cdd4fe..f7cba6ddea 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/bookmark/DBTraceBookmarkSpace.java @@ -26,10 +26,10 @@ import ghidra.trace.database.DBTrace; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.DBTraceSpaceBased; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceBookmarkChangeType; import ghidra.trace.model.bookmark.TraceBookmarkSpace; import ghidra.trace.model.bookmark.TraceBookmarkType; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; import ghidra.util.database.DBCachedObjectIndex; @@ -65,7 +65,7 @@ public class DBTraceBookmarkSpace implements TraceBookmarkSpace, DBTraceSpaceBas } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return null; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java index aa1c402398..f4eb3c1737 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpoint.java @@ -26,7 +26,6 @@ import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.Trace.TraceBreakpointChangeType; import ghidra.trace.model.breakpoint.TraceBreakpoint; @@ -128,7 +127,7 @@ public class DBTraceBreakpoint return this; } - public void set(String path, String name, Collection threads, + public void set(String path, String name, Collection threads, Collection kinds, boolean enabled, String comment) { // TODO: Check that the threads exist and that each's lifespan covers the breakpoint's // TODO: This would require additional validation any time those are updated @@ -140,7 +139,7 @@ public class DBTraceBreakpoint } this.threadKeys = new long[threads.size()]; int i = 0; - for (DBTraceThread t : threads) { + for (TraceThread t : threads) { this.threadKeys[i++] = t.getKey(); } this.flagsByte = 0; @@ -386,7 +385,8 @@ public class DBTraceBreakpoint } @Override - public boolean isEnabled() { + public boolean isEnabled(long snap) { + // NB. Only object mode support per-snap enablement try (LockHold hold = LockHold.lock(space.lock.readLock())) { return enabled; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManager.java index c4dcb63355..8e92c3212b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManager.java @@ -30,7 +30,6 @@ import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager; import ghidra.trace.database.space.DBTraceDelegatingManager; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.breakpoint.*; import ghidra.trace.model.thread.TraceThread; @@ -60,7 +59,7 @@ public class DBTraceBreakpointManager } @Override - protected DBTraceBreakpointSpace createRegisterSpace(AddressSpace space, DBTraceThread thread, + protected DBTraceBreakpointSpace createRegisterSpace(AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { throw new UnsupportedOperationException(); } @@ -80,9 +79,9 @@ public class DBTraceBreakpointManager return lock.writeLock(); } - protected void checkDuplicatePath(DBTraceBreakpoint ignore, String path, Range lifespan) + protected void checkDuplicatePath(TraceBreakpoint ignore, String path, Range lifespan) throws DuplicateNameException { - for (DBTraceBreakpoint pc : getBreakpointsByPath(path)) { + for (TraceBreakpoint pc : getBreakpointsByPath(path)) { if (pc == ignore) { continue; } @@ -98,23 +97,38 @@ public class DBTraceBreakpointManager public TraceBreakpoint addBreakpoint(String path, Range lifespan, AddressRange range, Collection threads, Collection kinds, boolean enabled, String comment) throws DuplicateNameException { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .addBreakpoint(path, lifespan, range, threads, kinds, enabled, comment); + } checkDuplicatePath(null, path, lifespan); return delegateWrite(range.getAddressSpace(), m -> m.addBreakpoint(path, lifespan, range, threads, kinds, enabled, comment)); } @Override - public Collection getAllBreakpoints() { + public Collection getAllBreakpoints() { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().getAllObjects(TraceObjectBreakpointLocation.class); + } return delegateCollection(getActiveMemorySpaces(), m -> m.getAllBreakpoints()); } @Override - public Collection getBreakpointsByPath(String path) { + public Collection getBreakpointsByPath(String path) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsByPath(path, TraceObjectBreakpointLocation.class); + } return delegateCollection(getActiveMemorySpaces(), m -> m.getBreakpointsByPath(path)); } @Override public TraceBreakpoint getPlacedBreakpointByPath(long snap, String path) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectByPath(snap, path, TraceObjectBreakpointLocation.class); + } try (LockHold hold = LockHold.lock(lock.readLock())) { return getBreakpointsByPath(path) .stream() @@ -125,14 +139,24 @@ public class DBTraceBreakpointManager } @Override - public Collection getBreakpointsAt(long snap, Address address) { + public Collection getBreakpointsAt(long snap, Address address) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsContaining(snap, address, TraceObjectBreakpointLocation.KEY_RANGE, + TraceObjectBreakpointLocation.class); + } return delegateRead(address.getAddressSpace(), m -> m.getBreakpointsAt(snap, address), Collections.emptyList()); } @Override - public Collection getBreakpointsIntersecting(Range span, + public Collection getBreakpointsIntersecting(Range span, AddressRange range) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsIntersecting(span, range, TraceObjectBreakpointLocation.KEY_RANGE, + TraceObjectBreakpointLocation.class); + } return delegateRead(range.getAddressSpace(), m -> m.getBreakpointsIntersecting(span, range), Collections.emptyList()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointSpace.java index d3c564cd65..20e0fb2965 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceBreakpointSpace.java @@ -99,11 +99,9 @@ public class DBTraceBreakpointSpace implements DBTraceSpaceBased { for (TraceThread t : threads) { threadManager.assertIsMine(t); } - @SuppressWarnings({ "rawtypes", "unchecked" }) // checked by above assertIsMine - Collection dbThreads = (Collection) threads; DBTraceBreakpoint breakpoint = breakpointMapSpace.put(new ImmutableTraceAddressSnapRange(range, lifespan), null); - breakpoint.set(path, path, dbThreads, kinds, enabled, comment); + breakpoint.set(path, path, threads, kinds, enabled, comment); trace.setChanged( new TraceChangeRecord<>(TraceBreakpointChangeType.ADDED, this, breakpoint)); return breakpoint; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java new file mode 100644 index 0000000000..ccfe5756ca --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointLocation.java @@ -0,0 +1,285 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.breakpoint; + +import java.util.Collection; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.*; +import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.dbg.util.PathMatcher; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.target.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceBreakpointChangeType; +import ghidra.trace.model.breakpoint.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.*; +import ghidra.util.LockHold; +import ghidra.util.exception.DuplicateNameException; + +public class DBTraceObjectBreakpointLocation + implements TraceObjectBreakpointLocation, DBTraceObjectInterface { + + protected class BreakpointChangeTranslator extends Translator { + protected BreakpointChangeTranslator(DBTraceObject object, TraceBreakpoint iface) { + super(KEY_RANGE, object, iface); + } + + @Override + protected TraceChangeType getAddedType() { + return TraceBreakpointChangeType.ADDED; + } + + @Override + protected TraceChangeType> getLifespanChangedType() { + return TraceBreakpointChangeType.LIFESPAN_CHANGED; + } + + @Override + protected TraceChangeType getChangedType() { + return TraceBreakpointChangeType.CHANGED; + } + + @Override + protected boolean appliesToKey(String key) { + return KEY_RANGE.equals(key) || + TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key) || + TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key) || + KEY_COMMENT.equals(key); + } + + @Override + protected TraceChangeType getDeletedType() { + return TraceBreakpointChangeType.DELETED; + } + } + + private final DBTraceObject object; + private final BreakpointChangeTranslator translator; + + // Keep copies here for when the object gets invalidated + private AddressRange range; + + public DBTraceObjectBreakpointLocation(DBTraceObject object) { + this.object = object; + + translator = new BreakpointChangeTranslator(object, this); + } + + @Override + public Trace getTrace() { + return object.getTrace(); + } + + @Override + public String getPath() { + return object.getCanonicalPath().toString(); + } + + @Override + public void setName(String name) { + object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + } + + @Override + public String getName() { + return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), + TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); + } + + @Override + public void setRange(AddressRange range) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), KEY_RANGE, range); + this.range = range; + } + } + + @Override + public AddressRange getRange() { + return range = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_RANGE, + AddressRange.class, range); + } + + @Override + public Address getMinAddress() { + AddressRange range = getRange(); + return range == null ? null : range.getMinAddress(); + } + + @Override + public Address getMaxAddress() { + AddressRange range = getRange(); + return range == null ? null : range.getMaxAddress(); + } + + @Override + public long getLength() { + AddressRange range = getRange(); + return range == null ? 0 : range.getLength(); + } + + @Override + public void setLifespan(Range lifespan) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + TraceObjectInterfaceUtils.setLifespan(TraceObjectBreakpointLocation.class, object, + lifespan); + } + } + + @Override + public Range getLifespan() { + return object.getLifespan(); + } + + @Override + public long getPlacedSnap() { + return object.getMinSnap(); + } + + @Override + public void setClearedSnap(long clearedSnap) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + setLifespan(DBTraceUtils.toRange(getPlacedSnap(), clearedSnap)); + } + } + + @Override + public long getClearedSnap() { + return object.getMaxSnap(); + } + + @Override + public TraceBreakpoint splitAndSet(long snap, boolean enabled, + Collection kinds) { + try (LockHold hold = object.getTrace().lockWrite()) { + if (enabled != isEnabled(snap)) { + object.setValue(DBTraceUtils.toRange(snap, getClearedSnap()), + TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled); + } + return this; + } + } + + @Override + public void setEnabled(boolean enabled) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled); + } + } + + @Override + public boolean isEnabled(long snap) { + try (LockHold hold = object.getTrace().lockRead()) { + Boolean locEn = TraceObjectInterfaceUtils.getValue(object, snap, + TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, Boolean.class, null); + if (locEn != null) { + return locEn; + } + return getSpecification().isEnabled(snap); + } + } + + @Override + public void setKinds(Collection kinds) { + try (LockHold hold = object.getTrace().lockWrite()) { + TraceObjectBreakpointSpec spec = getSpecification(); + if (spec.getObject() != this.getObject()) { + throw new UnsupportedOperationException("Set via the specification instead"); + } + spec.setKinds(kinds); + } + } + + @Override + public Set getKinds() { + try (LockHold hold = object.getTrace().lockRead()) { + return getSpecification().getKinds(); + } + } + + @Override + public Set getThreads() { + // TODO: Delete this? It's sort of deprecated out the gate anyway.... + DBTraceObjectManager manager = object.getManager(); + TargetObjectSchema schema = manager.getRootSchema(); + try (LockHold hold = object.getTrace().lockRead()) { + Set threads = + object.queryAncestorsInterface(getLifespan(), TraceObjectThread.class) + .collect(Collectors.toSet()); + if (!threads.isEmpty()) { + return threads; + } + + PathMatcher procMatcher = schema.searchFor(TargetProcess.class, false); + return object.getAncestors(getLifespan(), procMatcher) + .flatMap(proc -> proc.getFirstParent(object) + .querySuccessorsInterface(getLifespan(), + TraceObjectThread.class)) + .collect(Collectors.toSet()); + } + } + + @Override + public void setComment(String comment) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), KEY_COMMENT, comment); + } + } + + @Override + public String getComment() { + return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), KEY_COMMENT, + String.class, ""); + } + + @Override + public void delete() { + object.deleteTree(); + } + + @Override + public TraceObject getObject() { + return object; + } + + @Override + public TraceObjectBreakpointSpec getSpecification() { + try (LockHold hold = object.getTrace().lockRead()) { + return object.queryAncestorsInterface(getLifespan(), TraceObjectBreakpointSpec.class) + .findAny() + .orElseThrow(); + } + } + + public TraceAddressSpace getTraceAddressSpace() { + return spaceForValue(object.getMinSnap(), KEY_RANGE); + } + + @Override + public TraceChangeRecord translateEvent(TraceChangeRecord rec) { + return translator.translate(rec); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java new file mode 100644 index 0000000000..4109dc4200 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/breakpoint/DBTraceObjectBreakpointSpec.java @@ -0,0 +1,231 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.breakpoint; + +import java.util.*; +import java.util.stream.Collectors; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetBreakpointSpec; +import ghidra.dbg.target.TargetObject; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRange; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.target.DBTraceObject; +import ghidra.trace.database.target.DBTraceObjectInterface; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceBreakpointChangeType; +import ghidra.trace.model.Trace.TraceObjectChangeType; +import ghidra.trace.model.breakpoint.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceAddressSpace; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.util.LockHold; +import ghidra.util.Msg; +import ghidra.util.exception.DuplicateNameException; + +public class DBTraceObjectBreakpointSpec + implements TraceObjectBreakpointSpec, DBTraceObjectInterface { + private final DBTraceObject object; + + private Set kinds; + + public DBTraceObjectBreakpointSpec(DBTraceObject object) { + this.object = object; + } + + @Override + public Trace getTrace() { + return object.getTrace(); + } + + @Override + public String getPath() { + return object.getCanonicalPath().toString(); + } + + @Override + public void setName(String name) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + } + } + + @Override + public String getName() { + return TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), + TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); + } + + @Override + public AddressRange getRange() { + throw new UnsupportedOperationException("Ask a location instead"); + } + + @Override + public Address getMinAddress() { + throw new UnsupportedOperationException("Ask a location instead"); + } + + @Override + public Address getMaxAddress() { + throw new UnsupportedOperationException("Ask a location instead"); + } + + @Override + public long getLength() { + throw new UnsupportedOperationException("Ask a location instead"); + } + + @Override + public Range getLifespan() { + return object.getLifespan(); + } + + @Override + public long getPlacedSnap() { + return object.getMinSnap(); + } + + @Override + public void setClearedSnap(long clearedSnap) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + setLifespan(DBTraceUtils.toRange(getPlacedSnap(), clearedSnap)); + } + } + + @Override + public long getClearedSnap() { + return object.getMaxSnap(); + } + + @Override + public void setLifespan(Range lifespan) throws DuplicateNameException { + TraceObjectInterfaceUtils.setLifespan(TraceObjectThread.class, object, lifespan); + } + + @Override + public TraceBreakpoint splitAndSet(long snap, boolean enabled, + Collection kinds) { + throw new UnsupportedOperationException("Only used by default trace recorder"); + } + + @Override + public void setEnabled(boolean enabled) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, enabled); + } + } + + @Override + public boolean isEnabled(long snap) { + return TraceObjectInterfaceUtils.getValue(object, snap, + TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME, Boolean.class, false); + } + + @Override + public void setKinds(Collection kinds) { + // TODO: More efficient encoding + // TODO: Target-Trace mapping is implied by encoded name. Seems bad. + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, + kinds.stream().map(k -> k.name()).collect(Collectors.joining(","))); + this.kinds = Set.copyOf(kinds); + } + } + + @Override + public Set getKinds() { + Set result = new HashSet<>(); + String kindsStr = TraceObjectInterfaceUtils.getValue(object, getPlacedSnap(), + TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, String.class, null); + if (kindsStr == null) { + return kinds; + } + for (String name : kindsStr.split(",")) { + try { + result.add(TraceBreakpointKind.valueOf(name)); + } + catch (IllegalArgumentException e) { + Msg.warn(this, "Could not decode breakpoint kind from trace database: " + name); + } + } + return kinds = result; + } + + @Override + public Set getThreads() { + throw new UnsupportedOperationException("Ask a location instead"); + } + + @Override + public void setComment(String comment) { + throw new UnsupportedOperationException("Set on a location instead"); + } + + @Override + public String getComment() { + throw new UnsupportedOperationException("Ask a location instead"); + } + + @Override + public void delete() { + object.deleteTree(); + } + + @Override + public TraceObject getObject() { + return object; + } + + @Override + public Collection getLocations() { + try (LockHold hold = object.getTrace().lockRead()) { + return object + .querySuccessorsInterface(getLifespan(), TraceObjectBreakpointLocation.class) + .collect(Collectors.toSet()); + } + } + + @Override + public TraceChangeRecord translateEvent(TraceChangeRecord rec) { + if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) { + TraceChangeRecord cast = + TraceObjectChangeType.VALUE_CHANGED.cast(rec); + String key = cast.getAffectedObject().getEntryKey(); + boolean applies = TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME.equals(key) || + TargetBreakpointSpec.ENABLED_ATTRIBUTE_NAME.equals(key); + if (!applies) { + return null; + } + assert cast.getAffectedObject().getParent() == object; + for (TraceObjectBreakpointLocation loc : getLocations()) { + DBTraceObjectBreakpointLocation dbLoc = (DBTraceObjectBreakpointLocation) loc; + TraceAddressSpace space = dbLoc.getTraceAddressSpace(); + TraceChangeRecord evt = new TraceChangeRecord<>( + TraceBreakpointChangeType.CHANGED, space, loc, null, null); + object.getTrace().setChanged(evt); + } + return null; + } + return null; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextManager.java index 7bbac5faa5..428e96267d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextManager.java @@ -36,7 +36,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager; import ghidra.trace.database.space.DBTraceDelegatingManager; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.context.TraceRegisterContextManager; @@ -106,7 +105,7 @@ public class DBTraceRegisterContextManager extends @Override protected DBTraceRegisterContextRegisterSpace createRegisterSpace(AddressSpace space, - DBTraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { + TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { // TODO: Should I just forbid this? It doesn't seem sane. Then again, what do I know? return new DBTraceRegisterContextRegisterSpace(this, dbh, space, ent, thread); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextRegisterSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextRegisterSpace.java index afc01a0ffb..aa98800dd2 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextRegisterSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextRegisterSpace.java @@ -20,17 +20,17 @@ import java.io.IOException; import db.DBHandle; import ghidra.program.model.address.AddressSpace; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.context.TraceRegisterContextRegisterSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.exception.VersionException; public class DBTraceRegisterContextRegisterSpace extends DBTraceRegisterContextSpace implements TraceRegisterContextRegisterSpace { - private final DBTraceThread thread; + private final TraceThread thread; private final int frameLevel; public DBTraceRegisterContextRegisterSpace(DBTraceRegisterContextManager manager, DBHandle dbh, - AddressSpace space, DBTraceSpaceEntry ent, DBTraceThread thread) + AddressSpace space, DBTraceSpaceEntry ent, TraceThread thread) throws VersionException, IOException { super(manager, dbh, space, ent); this.thread = thread; @@ -38,7 +38,7 @@ public class DBTraceRegisterContextRegisterSpace extends DBTraceRegisterContextS } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextSpace.java index 8a04f63ce7..6fe1b74a13 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/context/DBTraceRegisterContextSpace.java @@ -37,10 +37,10 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; import ghidra.trace.database.space.DBTraceSpaceBased; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.ImmutableTraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.context.TraceRegisterContextSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.LockHold; import ghidra.util.database.*; import ghidra.util.database.annot.*; @@ -126,12 +126,12 @@ public class DBTraceRegisterContextSpace implements TraceRegisterContextSpace, D } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return null; } protected long getThreadKey() { - DBTraceThread thread = getThread(); + TraceThread thread = getThread(); return thread == null ? -1 : thread.getKey(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java index 20ef264b39..d550ba9279 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/data/DBTraceDataSettingsAdapter.java @@ -29,7 +29,6 @@ import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.data.DBTraceDataSettingsAdapter.DBTraceSettingsEntry; import ghidra.trace.database.map.*; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceAddressSpace; @@ -183,7 +182,7 @@ public class DBTraceDataSettingsAdapter implements DBTraceDataSettingsOperations { public DBTraceDataSettingsRegisterSpace(String tableName, DBCachedObjectStoreFactory storeFactory, ReadWriteLock lock, AddressSpace space, - DBTraceThread thread, int frameLevel, Class dataType, + TraceThread thread, int frameLevel, Class dataType, DBTraceAddressSnapRangePropertyMapDataFactory dataFactory) throws VersionException, IOException { super(tableName, storeFactory, lock, space, thread, frameLevel, dataType, dataFactory); @@ -217,7 +216,7 @@ public class DBTraceDataSettingsAdapter @Override protected DBTraceAddressSnapRangePropertyMapRegisterSpace createRegisterSpace( - AddressSpace space, DBTraceThread thread, DBTraceSpaceEntry ent) + AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { return new DBTraceDataSettingsRegisterSpace( tableName(space, ent.getThreadKey(), ent.getFrameLevel()), diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeManager.java index f71e1f8280..02395493d3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeManager.java @@ -46,7 +46,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAdd import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager; import ghidra.trace.database.space.DBTraceDelegatingManager; import ghidra.trace.database.symbol.DBTraceReferenceManager; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.AddressSnap; import ghidra.trace.model.DefaultAddressSnap; @@ -222,7 +221,7 @@ public class DBTraceCodeManager // Internal public UndefinedDBTraceData doCreateUndefinedUnit(long snap, Address address, - DBTraceThread thread, int frameLevel) { + TraceThread thread, int frameLevel) { return undefinedCache.computeIfAbsent(new DefaultAddressSnap(address, snap), ot -> new UndefinedDBTraceData(trace, snap, address, thread, frameLevel)); } @@ -279,7 +278,7 @@ public class DBTraceCodeManager } @Override - protected DBTraceCodeRegisterSpace createRegisterSpace(AddressSpace space, DBTraceThread thread, + protected DBTraceCodeRegisterSpace createRegisterSpace(AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { return new DBTraceCodeRegisterSpace(this, dbh, space, ent, thread); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeRegisterSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeRegisterSpace.java index 620e8c0384..0644690b04 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeRegisterSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeRegisterSpace.java @@ -20,23 +20,23 @@ import java.io.IOException; import db.DBHandle; import ghidra.program.model.address.AddressSpace; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.listing.TraceCodeRegisterSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.exception.VersionException; public class DBTraceCodeRegisterSpace extends DBTraceCodeSpace implements TraceCodeRegisterSpace { - protected final DBTraceThread thread; + protected final TraceThread thread; private final int frameLevel; public DBTraceCodeRegisterSpace(DBTraceCodeManager manager, DBHandle dbh, AddressSpace space, - DBTraceSpaceEntry ent, DBTraceThread thread) throws VersionException, IOException { + DBTraceSpaceEntry ent, TraceThread thread) throws VersionException, IOException { super(manager, dbh, space, ent); this.thread = thread; this.frameLevel = ent.getFrameLevel(); } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeSpace.java index 1388682642..0e4d7f91b8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeSpace.java @@ -37,9 +37,9 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAdd import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; import ghidra.trace.database.space.DBTraceSpaceBased; import ghidra.trace.database.symbol.DBTraceReferenceManager; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.listing.TraceCodeSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.ByteArrayUtils; import ghidra.util.LockHold; import ghidra.util.database.DBCachedObjectStoreFactory; @@ -169,7 +169,7 @@ public class DBTraceCodeSpace implements TraceCodeSpace, DBTraceSpaceBased { } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return null; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitAdapter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitAdapter.java index 7d5f4f756c..0fdfb09632 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitAdapter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitAdapter.java @@ -32,10 +32,10 @@ import ghidra.program.model.mem.Memory; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.symbol.*; import ghidra.trace.database.DBTrace; -import ghidra.trace.database.memory.DBTraceMemoryRegion; import ghidra.trace.database.symbol.DBTraceReference; import ghidra.trace.model.listing.TraceCodeUnit; import ghidra.trace.model.map.TracePropertyMap; +import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.symbol.TraceReference; import ghidra.trace.model.symbol.TraceSymbol; @@ -74,7 +74,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter if (!showBlockName) { return address.toString(false, pad); } - DBTraceMemoryRegion region = + TraceMemoryRegion region = getTrace().getMemoryManager().getRegionContaining(getStartSnap(), address); if (region == null) { return address.toString(showBlockName, pad); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitsRegisterView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitsRegisterView.java index 3c16f3faf8..2d705b080d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitsRegisterView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceCodeUnitsRegisterView.java @@ -15,8 +15,8 @@ */ package ghidra.trace.database.listing; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.listing.TraceCodeUnitsRegisterView; +import ghidra.trace.model.thread.TraceThread; public class DBTraceCodeUnitsRegisterView extends DBTraceCodeUnitsView implements TraceCodeUnitsRegisterView { @@ -25,7 +25,7 @@ public class DBTraceCodeUnitsRegisterView extends DBTraceCodeUnitsView } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return space.getThread(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataRegisterView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataRegisterView.java index 3fce07eac2..eab3e633f3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataRegisterView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceDataRegisterView.java @@ -15,8 +15,8 @@ */ package ghidra.trace.database.listing; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.listing.TraceDataRegisterView; +import ghidra.trace.model.thread.TraceThread; public class DBTraceDataRegisterView extends DBTraceDataView implements TraceDataRegisterView { public DBTraceDataRegisterView(DBTraceCodeSpace space) { @@ -24,7 +24,7 @@ public class DBTraceDataRegisterView extends DBTraceDataView implements TraceDat } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return space.getThread(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsRegisterView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsRegisterView.java index 0db270f8d0..c6dcc8aa4e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsRegisterView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsRegisterView.java @@ -15,8 +15,8 @@ */ package ghidra.trace.database.listing; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.listing.TraceInstructionsRegisterView; +import ghidra.trace.model.thread.TraceThread; public class DBTraceInstructionsRegisterView extends DBTraceInstructionsView implements TraceInstructionsRegisterView { @@ -26,7 +26,7 @@ public class DBTraceInstructionsRegisterView extends DBTraceInstructionsView } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return space.getThread(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java index c9846a89cb..668e2c3a1e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/DBTraceInstructionsView.java @@ -38,7 +38,6 @@ import ghidra.trace.model.listing.TraceInstructionsView; import ghidra.trace.util.OverlappingObjectIterator; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; -import ghidra.util.SystemUtilities; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java index c308e12ea8..74b9624585 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/listing/UndefinedDBTraceData.java @@ -31,10 +31,10 @@ import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.data.DBTraceDataSettingsOperations; import ghidra.trace.database.memory.DBTraceMemorySpace; import ghidra.trace.database.space.DBTraceSpaceKey; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.ImmutableTraceAddressSnapRange; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.listing.TraceData; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceAddressSpace; public class UndefinedDBTraceData implements DBTraceDataAdapter, DBTraceSpaceKey { @@ -42,10 +42,10 @@ public class UndefinedDBTraceData implements DBTraceDataAdapter, DBTraceSpaceKey protected final long snap; protected final Range lifespan; protected final Address address; - protected final DBTraceThread thread; + protected final TraceThread thread; protected final int frameLevel; - public UndefinedDBTraceData(DBTrace trace, long snap, Address address, DBTraceThread thread, + public UndefinedDBTraceData(DBTrace trace, long snap, Address address, TraceThread thread, int frameLevel) { this.trace = trace; this.snap = snap; @@ -118,7 +118,7 @@ public class UndefinedDBTraceData implements DBTraceDataAdapter, DBTraceSpaceKey } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMap.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMap.java index a1470273c5..c453238f3a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMap.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMap.java @@ -35,7 +35,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Abstract import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager; import ghidra.trace.database.space.DBTraceDelegatingManager; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.map.TraceAddressSnapRangePropertyMap; @@ -89,7 +88,7 @@ public class DBTraceAddressSnapRangePropertyMap createRegisterSpace( - AddressSpace space, DBTraceThread thread, DBTraceSpaceEntry ent) + AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { return new DBTraceAddressSnapRangePropertyMapRegisterSpace<>( tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(), diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapRegisterSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapRegisterSpace.java index 233349bf0a..a1ffb284d0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapRegisterSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/DBTraceAddressSnapRangePropertyMapRegisterSpace.java @@ -21,20 +21,20 @@ import java.util.concurrent.locks.ReadWriteLock; import ghidra.program.model.address.AddressSpace; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap.DBTraceAddressSnapRangePropertyMapDataFactory; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.map.TraceAddressSnapRangePropertyMapRegisterSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.database.DBCachedObjectStoreFactory; import ghidra.util.exception.VersionException; public class DBTraceAddressSnapRangePropertyMapRegisterSpace> extends DBTraceAddressSnapRangePropertyMapSpace implements TraceAddressSnapRangePropertyMapRegisterSpace { - protected final DBTraceThread thread; + protected final TraceThread thread; protected final int frameLevel; public DBTraceAddressSnapRangePropertyMapRegisterSpace(String tableName, DBCachedObjectStoreFactory storeFactory, ReadWriteLock lock, AddressSpace space, - DBTraceThread thread, int frameLevel, Class dataType, + TraceThread thread, int frameLevel, Class dataType, DBTraceAddressSnapRangePropertyMapDataFactory dataFactory) throws VersionException, IOException { super(tableName, storeFactory, lock, space, dataType, dataFactory); @@ -43,7 +43,7 @@ public class DBTraceAddressSnapRangePropertyMapRegisterSpace lifespan, + public TraceMemoryRegion addRegion(String path, Range lifespan, AddressRange range, Collection flags) throws TraceOverlappedRegionException, DuplicateNameException { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().addMemoryRegion(path, lifespan, range, flags); + } try { return delegateWrite(range.getAddressSpace(), m -> m.addRegion(path, lifespan, range, flags)); @@ -144,35 +147,50 @@ public class DBTraceMemoryManager } @Override - public Collection getAllRegions() { + public Collection getAllRegions() { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().getAllObjects(TraceObjectMemoryRegion.class); + } return delegateCollection(getActiveMemorySpaces(), m -> m.getAllRegions()); } - // Internal - public Collection getRegionsInternal() { - return delegateCollection(getActiveMemorySpaces(), m -> m.regionMapSpace.values()); - } - @Override - public DBTraceMemoryRegion getLiveRegionByPath(long snap, String regionName) { + public TraceMemoryRegion getLiveRegionByPath(long snap, String path) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectByPath(snap, path, TraceObjectMemoryRegion.class); + } // Not efficient, but I don't anticipate many regions - return delegateFirst(getActiveMemorySpaces(), m -> m.getLiveRegionByPath(snap, regionName)); + return delegateFirst(getActiveMemorySpaces(), m -> m.getLiveRegionByPath(snap, path)); } @Override - public DBTraceMemoryRegion getRegionContaining(long snap, Address address) { + public TraceMemoryRegion getRegionContaining(long snap, Address address) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectContaining(snap, address, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, + TraceObjectMemoryRegion.class); + } return delegateRead(address.getAddressSpace(), m -> m.getRegionContaining(snap, address)); } @Override - public Collection getRegionsIntersecting(Range lifespan, + public Collection getRegionsIntersecting(Range lifespan, AddressRange range) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsIntersecting(lifespan, range, + TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, TraceObjectMemoryRegion.class); + } return delegateRead(range.getAddressSpace(), m -> m.getRegionsIntersecting(lifespan, range), Collections.emptyList()); } @Override - public Collection getRegionsAtSnap(long snap) { + public Collection getRegionsAtSnap(long snap) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().getObjectsAtSnap(snap, TraceObjectMemoryRegion.class); + } return delegateCollection(memSpaces.values(), m -> m.getRegionsAtSnap(snap)); } @@ -193,6 +211,11 @@ public class DBTraceMemoryManager @Override public AddressSetView getRegionsAddressSet(long snap) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsAddressSet(snap, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, + TraceObjectMemoryRegion.class, r -> true); + } return new UnionAddressSetView(Collections2.transform(getActiveMemorySpaces(), m -> m.getRegionsAddressSet(snap))); } @@ -200,6 +223,11 @@ public class DBTraceMemoryManager @Override public AddressSetView getRegionsAddressSetWith(long snap, Predicate predicate) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsAddressSet(snap, TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, + TraceObjectMemoryRegion.class, predicate); + } return new UnionAddressSetView(Collections2.transform(getActiveMemorySpaces(), m -> m.getRegionsAddressSetWith(snap, predicate))); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegion.java index 093ad5f504..ea31955b16 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegion.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegion.java @@ -65,7 +65,7 @@ public class DBTraceMemoryRegion private final DBTraceMemorySpace space; - private final Set flags = EnumSet.noneOf(TraceMemoryFlag.class); + private final EnumSet flags = EnumSet.noneOf(TraceMemoryFlag.class); public DBTraceMemoryRegion(DBTraceMemorySpace space, DBTraceAddressSnapRangePropertyMapTree tree, @@ -81,11 +81,7 @@ public class DBTraceMemoryRegion return; } flags.clear(); - for (TraceMemoryFlag f : TraceMemoryFlag.values()) { - if ((flagsByte & f.getBits()) != 0) { - flags.add(f); - } - } + TraceMemoryFlag.fromBits(flags, flagsByte); } @Override @@ -271,14 +267,11 @@ public class DBTraceMemoryRegion @Override public void setFlags(Collection flags) { try (LockHold hold = LockHold.lock(space.lock.writeLock())) { - this.flagsByte = 0; + this.flagsByte = TraceMemoryFlag.toBits(flags); this.flags.clear(); - for (TraceMemoryFlag f : flags) { - this.flagsByte |= f.getBits(); - this.flags.add(f); - } + this.flags.addAll(flags); update(FLAGS_COLUMN); - space.trace.updateViewsChangeRegionBlockFlags(this); + space.trace.updateViewsChangeRegionBlockFlags(this, lifespan); } space.trace.setChanged( new TraceChangeRecord<>(TraceMemoryRegionChangeType.CHANGED, space, this)); @@ -288,12 +281,10 @@ public class DBTraceMemoryRegion @Override public void addFlags(Collection flags) { try (LockHold hold = LockHold.lock(space.lock.writeLock())) { - for (TraceMemoryFlag f : flags) { - this.flagsByte |= f.getBits(); - this.flags.add(f); - } + this.flagsByte |= TraceMemoryFlag.toBits(flags); + this.flags.addAll(flags); update(FLAGS_COLUMN); - space.trace.updateViewsChangeRegionBlockFlags(this); + space.trace.updateViewsChangeRegionBlockFlags(this, lifespan); } space.trace.setChanged( new TraceChangeRecord<>(TraceMemoryRegionChangeType.CHANGED, space, this)); @@ -303,12 +294,10 @@ public class DBTraceMemoryRegion @Override public void clearFlags(Collection flags) { try (LockHold hold = LockHold.lock(space.lock.writeLock())) { - for (TraceMemoryFlag f : flags) { - this.flagsByte &= ~f.getBits(); - this.flags.remove(f); - } + this.flagsByte &= ~TraceMemoryFlag.toBits(flags); + this.flags.removeAll(flags); update(FLAGS_COLUMN); - space.trace.updateViewsChangeRegionBlockFlags(this); + space.trace.updateViewsChangeRegionBlockFlags(this, lifespan); } space.trace.setChanged( new TraceChangeRecord<>(TraceMemoryRegionChangeType.CHANGED, space, this)); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegisterSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegisterSpace.java index b5d5316677..ef550f4c34 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegisterSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemoryRegisterSpace.java @@ -26,18 +26,18 @@ import ghidra.program.model.address.AddressRange; import ghidra.program.model.address.AddressSpace; import ghidra.trace.database.listing.DBTraceCodeRegisterSpace; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.memory.*; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.VersionException; public class DBTraceMemoryRegisterSpace extends DBTraceMemorySpace implements TraceMemoryRegisterSpace { - protected final DBTraceThread thread; + protected final TraceThread thread; private final int frameLevel; public DBTraceMemoryRegisterSpace(DBTraceMemoryManager manager, DBHandle dbh, - AddressSpace space, DBTraceSpaceEntry ent, DBTraceThread thread) + AddressSpace space, DBTraceSpaceEntry ent, TraceThread thread) throws IOException, VersionException { super(manager, dbh, space, ent); this.thread = thread; @@ -45,7 +45,7 @@ public class DBTraceMemoryRegisterSpace extends DBTraceMemorySpace } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java index 60a5c08353..e759971802 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemorySpace.java @@ -33,16 +33,17 @@ import ghidra.program.model.address.*; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.DBTraceUtils.AddressRangeMapSetter; import ghidra.trace.database.DBTraceUtils.OffsetSnap; import ghidra.trace.database.listing.DBTraceCodeSpace; import ghidra.trace.database.map.*; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; import ghidra.trace.database.space.DBTraceSpaceBased; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.*; import ghidra.trace.model.Trace.*; import ghidra.trace.model.memory.*; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; import ghidra.trace.util.TraceViewportSpanIterator; import ghidra.util.*; @@ -261,7 +262,7 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return null; } @@ -274,55 +275,42 @@ public class DBTraceMemorySpace implements Unfinished, TraceMemorySpace, DBTrace if (state == null) { throw new NullPointerException(); } - // Go one out to find abutting ranges, too. - Address prev = start.previous(); - if (prev == null) { - prev = start; - } - Address next = end.next(); - if (next == null) { - next = end; - } - Map toPut = new HashMap<>(); - for (Entry entry : stateMapSpace.reduce( - TraceAddressSnapRangeQuery.intersecting(prev, next, snap, snap)).entries()) { - // NOTE: Entries are in no particular order - AddressRange range = entry.getKey().getRange(); - boolean precedesMin = range.getMinAddress().compareTo(start) < 0; - boolean procedesMax = range.getMaxAddress().compareTo(end) > 0; - boolean sameState = entry.getValue() == state; - if (precedesMin && procedesMax && sameState) { - return; // The value in this range is already the desired state + + new AddressRangeMapSetter, TraceMemoryState>() { + @Override + protected AddressRange getRange(Entry entry) { + return entry.getKey().getRange(); } - stateMapSpace.remove(entry); - if (precedesMin) { - if (sameState) { - start = range.getMinAddress(); - } - else { - toPut.put( - new ImmutableTraceAddressSnapRange(range.getMinAddress(), prev, snap, snap), - entry.getValue()); - } + + @Override + protected TraceMemoryState getValue( + Entry entry) { + return entry.getValue(); } - if (procedesMax) { - if (sameState) { - end = range.getMaxAddress(); - } - else { - toPut.put( - new ImmutableTraceAddressSnapRange(next, range.getMaxAddress(), snap, snap), - entry.getValue()); - } + + @Override + protected void remove(Entry entry) { + stateMapSpace.remove(entry); } - } - if (state != TraceMemoryState.UNKNOWN) { - stateMapSpace.put(start, end, snap, state); - } - assert toPut.size() <= 2; - for (Entry ent : toPut.entrySet()) { - stateMapSpace.put(ent.getKey(), ent.getValue()); - } + + @Override + protected Iterable> getIntersecting( + Address lower, Address upper) { + return stateMapSpace + .reduce(TraceAddressSnapRangeQuery.intersecting(lower, upper, snap, snap)) + .entries(); + } + + @Override + protected Entry put(AddressRange range, + TraceMemoryState value) { + if (value != TraceMemoryState.UNKNOWN) { + stateMapSpace.put(new ImmutableTraceAddressSnapRange(range, snap), value); + } + return null; // Don't need to return it + } + }.set(start, end, state); + trace.setChanged(new TraceChangeRecord<>(TraceMemoryStateChangeType.CHANGED, this, new ImmutableTraceAddressSnapRange(start, end, snap, snap), state)); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java new file mode 100644 index 0000000000..41898ebb53 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectMemoryRegion.java @@ -0,0 +1,312 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import java.util.*; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetMemoryRegion; +import ghidra.dbg.target.TargetObject; +import ghidra.program.model.address.*; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.target.DBTraceObject; +import ghidra.trace.database.target.DBTraceObjectInterface; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceMemoryRegionChangeType; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.trace.util.TraceChangeType; +import ghidra.util.LockHold; +import ghidra.util.exception.DuplicateNameException; + +public class DBTraceObjectMemoryRegion implements TraceObjectMemoryRegion, DBTraceObjectInterface { + + protected class RegionChangeTranslator extends Translator { + protected RegionChangeTranslator(DBTraceObject object, TraceMemoryRegion iface) { + super(TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, object, iface); + } + + @Override + protected TraceChangeType getAddedType() { + return TraceMemoryRegionChangeType.ADDED; + } + + @Override + protected TraceChangeType> getLifespanChangedType() { + return TraceMemoryRegionChangeType.LIFESPAN_CHANGED; + } + + @Override + protected TraceChangeType getChangedType() { + return TraceMemoryRegionChangeType.CHANGED; + } + + @Override + protected boolean appliesToKey(String key) { + return TargetMemoryRegion.RANGE_ATTRIBUTE_NAME.equals(key) || + TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key) || + TargetMemoryRegion.READABLE_ATTRIBUTE_NAME.equals(key) || + TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME.equals(key) || + TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME.equals(key); + } + + @Override + protected TraceChangeType getDeletedType() { + return TraceMemoryRegionChangeType.DELETED; + } + } + + private final DBTraceObject object; + private final RegionChangeTranslator translator; + + public DBTraceObjectMemoryRegion(DBTraceObject object) { + this.object = object; + + translator = new RegionChangeTranslator(object, this); + } + + @Override + public Trace getTrace() { + return object.getTrace(); + } + + @Override + public String getPath() { + return object.getCanonicalPath().toString(); + } + + @Override + public void setName(String name) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + object.getTrace().updateViewsChangeRegionBlockName(this); + } + } + + @Override + public String getName() { + TraceObjectValue value = + object.getValue(getCreationSnap(), TargetObject.DISPLAY_ATTRIBUTE_NAME); + return value == null ? "" : (String) value.getValue(); + } + + @Override + public void setLifespan(Range newLifespan) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + Range oldLifespan = getLifespan(); + if (Objects.equals(oldLifespan, newLifespan)) { + return; + } + TraceObjectInterfaceUtils.setLifespan(TraceObjectMemoryRegion.class, object, + newLifespan); + object.getTrace().updateViewsChangeRegionBlockLifespan(this, oldLifespan, newLifespan); + } + } + + @Override + public Range getLifespan() { + return object.getLifespan(); + } + + @Override + public void setCreationSnap(long creationSnap) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + setLifespan(DBTraceUtils.toRange(creationSnap, getDestructionSnap())); + } + } + + @Override + public long getCreationSnap() { + return object.getMinSnap(); + } + + @Override + public void setDestructionSnap(long destructionSnap) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + setLifespan(DBTraceUtils.toRange(getCreationSnap(), destructionSnap)); + } + } + + @Override + public long getDestructionSnap() { + return object.getMaxSnap(); + } + + @Override + public void setRange(AddressRange newRange) { + try (LockHold hold = object.getTrace().lockWrite()) { + AddressRange oldRange = getRange(); + if (Objects.equals(oldRange, newRange)) { + return; + } + object.setValue(getLifespan(), TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, newRange); + object.getTrace().updateViewsChangeRegionBlockRange(this, oldRange, newRange); + } + } + + @Override + public AddressRange getRange() { + try (LockHold hold = object.getTrace().lockRead()) { + return TraceObjectInterfaceUtils.getValue(object, getCreationSnap(), + TargetMemoryRegion.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); + } + } + + @Override + public void setMinAddress(Address min) { + try (LockHold hold = object.getTrace().lockWrite()) { + setRange(DBTraceUtils.toRange(min, getMaxAddress())); + } + } + + @Override + public Address getMinAddress() { + AddressRange range = getRange(); + return range == null ? null : range.getMinAddress(); + } + + @Override + public void setMaxAddress(Address max) { + try (LockHold hold = object.getTrace().lockWrite()) { + setRange(DBTraceUtils.toRange(getMinAddress(), max)); + } + } + + @Override + public Address getMaxAddress() { + AddressRange range = getRange(); + return range == null ? null : range.getMaxAddress(); + } + + @Override + public void setLength(long length) throws AddressOverflowException { + try (LockHold hold = object.getTrace().lockWrite()) { + setRange(new AddressRangeImpl(getMinAddress(), length)); + } + } + + @Override + public long getLength() { + return getRange().getLength(); + } + + protected static String keyForFlag(TraceMemoryFlag flag) { + switch (flag) { + case READ: + return TargetMemoryRegion.READABLE_ATTRIBUTE_NAME; + case WRITE: + return TargetMemoryRegion.WRITABLE_ATTRIBUTE_NAME; + case EXECUTE: + return TargetMemoryRegion.EXECUTABLE_ATTRIBUTE_NAME; + case VOLATILE: + return KEY_VOLATILE; + default: + throw new AssertionError(); + } + } + + @Override + public void setFlags(Range lifespan, Collection flags) { + try (LockHold hold = object.getTrace().lockWrite()) { + for (TraceMemoryFlag flag : TraceMemoryFlag.values()) { + Boolean val = flags.contains(flag) ? true : null; + object.setValue(lifespan, keyForFlag(flag), val); + } + object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan); + } + } + + @Override + public void addFlags(Range lifespan, Collection flags) { + try (LockHold hold = object.getTrace().lockWrite()) { + for (TraceMemoryFlag flag : flags) { + object.setValue(lifespan, keyForFlag(flag), true); + } + object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan); + } + } + + @Override + public void clearFlags(Range lifespan, Collection flags) { + try (LockHold hold = object.getTrace().lockWrite()) { + for (TraceMemoryFlag flag : flags) { + object.setValue(lifespan, keyForFlag(flag), null); + } + object.getTrace().updateViewsChangeRegionBlockFlags(this, lifespan); + } + } + + @Override + public void setFlags(Collection flags) { + try (LockHold hold = object.getTrace().lockWrite()) { + setFlags(getLifespan(), flags); + } + } + + @Override + public void addFlags(Collection flags) { + try (LockHold hold = object.getTrace().lockWrite()) { + addFlags(getLifespan(), flags); + } + } + + @Override + public void clearFlags(Collection flags) { + try (LockHold hold = object.getTrace().lockWrite()) { + clearFlags(getLifespan(), flags); + } + } + + @Override + public Set getFlags(long snap) { + EnumSet result = EnumSet.noneOf(TraceMemoryFlag.class); + for (TraceMemoryFlag flag : TraceMemoryFlag.values()) { + if (object.getValue(snap, keyForFlag(flag)) != null) { + result.add(flag); + } + } + return result; + } + + @Override + public Set getFlags() { + try (LockHold hold = object.getTrace().lockRead()) { + return getFlags(getCreationSnap()); + } + } + + @Override + public void delete() { + try (LockHold hold = object.getTrace().lockWrite()) { + object.deleteTree(); + object.getTrace().updateViewsDeleteRegionBlock(this); + } + } + + @Override + public TraceObject getObject() { + return object; + } + + @Override + public TraceChangeRecord translateEvent(TraceChangeRecord rec) { + return translator.translate(rec); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectRegister.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectRegister.java new file mode 100644 index 0000000000..f77d201f99 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceObjectRegister.java @@ -0,0 +1,105 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import java.math.BigInteger; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetRegister; +import ghidra.dbg.util.PathUtils; +import ghidra.pcode.utils.Utils; +import ghidra.trace.database.target.DBTraceObject; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.memory.TraceObjectRegister; +import ghidra.trace.model.target.*; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.model.thread.TraceObjectThread; + +public class DBTraceObjectRegister implements TraceObjectRegister { + private final DBTraceObject object; + + public DBTraceObjectRegister(DBTraceObject object) { + this.object = object; + } + + @Override + public TraceObject getObject() { + return object; + } + + @Override + public TraceObjectThread getThread() { + return object.queryAncestorsInterface(object.getLifespan(), TraceObjectThread.class) + .findAny() + .orElseThrow(); + } + + @Override + public String getName() { + TraceObjectKeyPath path = object.getCanonicalPath(); + if (PathUtils.isIndex(path.key())) { + return path.index(); + } + return path.key(); + } + + @Override + public int getLength() { + return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), + TargetRegister.LENGTH_ATTRIBUTE_NAME, Integer.class, 0); + } + + @Override + public void setValue(Range lifespan, byte[] value) { + int length = getLength(); + if (length != 0 && value.length != length) { + throw new IllegalArgumentException("Length must match the register"); + } + object.setValue(lifespan, TargetRegister.VALUE_ATTRIBUTE_NAME, value); + } + + @Override + public byte[] getValue(long snap) { + TraceObjectValue ov = object.getValue(snap, TargetRegister.VALUE_ATTRIBUTE_NAME); + if (ov == null) { + return null; + } + Object val = ov.getValue(); + if (val instanceof byte[]) { + // TODO: Should I correct mismatched size? + return (byte[]) val; + } + if (val instanceof String) { + // Always base 16. Model API says byte array for register value is big endian. + BigInteger bigVal = new BigInteger((String) val, 16); + return Utils.bigIntegerToBytes(bigVal, getLength(), true); + } + throw new ClassCastException("Cannot convert " + val + " to byte array for register value"); + } + + @Override + public void setState(Range lifespan, TraceMemoryState state) { + // NB. There's no model equivalent, so encode using ordinal + object.setValue(lifespan, KEY_STATE, state.ordinal()); + } + + @Override + public TraceMemoryState getState(long snap) { + return TraceMemoryState.values()[TraceObjectInterfaceUtils.getValue(object, snap, KEY_STATE, + Integer.class, TraceMemoryState.UNKNOWN.ordinal())]; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceModuleManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceModuleManager.java index b69bd46707..5430248752 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceModuleManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceModuleManager.java @@ -23,16 +23,16 @@ import java.util.concurrent.locks.ReadWriteLock; import com.google.common.collect.Range; import db.DBHandle; +import ghidra.dbg.target.TargetModule; +import ghidra.dbg.target.TargetSection; import ghidra.program.model.address.*; import ghidra.program.model.lang.Language; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager; import ghidra.trace.database.space.DBTraceDelegatingManager; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceModuleChangeType; -import ghidra.trace.model.modules.TraceModuleManager; -import ghidra.trace.model.modules.TraceSection; +import ghidra.trace.model.modules.*; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; @@ -64,9 +64,9 @@ public class DBTraceModuleManager throw new UnsupportedOperationException(); } - protected void checkModulePathConflicts(DBTraceModule ignore, String modulePath, + protected void checkModulePathConflicts(TraceModule ignore, String modulePath, Range moduleLifespan) throws DuplicateNameException { - for (DBTraceModule pc : doGetModulesByPath(modulePath)) { + for (TraceModule pc : doGetModulesByPath(modulePath)) { if (pc == ignore) { continue; } @@ -80,12 +80,17 @@ public class DBTraceModuleManager protected void checkSectionPathConflicts(DBTraceSection ignore, String sectionPath, Range moduleLifespan) throws DuplicateNameException { - Collection pathConflicts = doGetSectionsByPath(sectionPath); - for (DBTraceSection pc : pathConflicts) { + Collection pathConflicts = doGetSectionsByPath(sectionPath); + for (TraceSection pc : pathConflicts) { if (pc == ignore) { continue; } - if (!DBTraceUtils.intersect(pc.getLifespan(), moduleLifespan)) { + /** + * TODO: Certainly, any two sections at the same path will belong to the same module and + * so have the same lifespan, no? I suppose this logic is only true in objects mode..., + * and there, the logic is performed by the value key duplicate check. + */ + if (!DBTraceUtils.intersect(pc.getModule().getLifespan(), moduleLifespan)) { continue; } throw new DuplicateNameException("Section with path '" + sectionPath + @@ -94,31 +99,41 @@ public class DBTraceModuleManager } @Override - public DBTraceModule addModule(String modulePath, String moduleName, AddressRange range, + public TraceModule addModule(String modulePath, String moduleName, AddressRange range, Range lifespan) throws DuplicateNameException { try (LockHold hold = LockHold.lock(lock.writeLock())) { return doAddModule(modulePath, moduleName, range, lifespan); } } - protected DBTraceModule doAddModule(String modulePath, String moduleName, AddressRange range, + protected TraceModule doAddModule(String modulePath, String moduleName, AddressRange range, Range lifespan) throws DuplicateNameException { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().addModule(modulePath, moduleName, lifespan, range); + } checkModulePathConflicts(null, modulePath, lifespan); return delegateWrite(range.getAddressSpace(), m -> m.doAddModule(modulePath, moduleName, range, lifespan)); } - protected Collection doGetModulesByPath(String modulePath) { + protected Collection doGetModulesByPath(String modulePath) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().getObjectsByPath(modulePath, TraceObjectModule.class); + } return delegateCollection(memSpaces.values(), m -> m.doGetModulesByPath(modulePath)); } @Override - public Collection getModulesByPath(String modulePath) { + public Collection getModulesByPath(String modulePath) { return Collections.unmodifiableCollection(doGetModulesByPath(modulePath)); } @Override - public DBTraceModule getLoadedModuleByPath(long snap, String modulePath) { + public TraceModule getLoadedModuleByPath(long snap, String modulePath) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectByPath(snap, modulePath, TraceObjectModule.class); + } try (LockHold hold = LockHold.lock(lock.readLock())) { return doGetModulesByPath(modulePath) .stream() @@ -129,24 +144,40 @@ public class DBTraceModuleManager } @Override - public Collection getAllModules() { + public Collection getAllModules() { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().getAllObjects(TraceObjectModule.class); + } return delegateCollection(memSpaces.values(), m -> m.getAllModules()); } @Override - public Collection getLoadedModules(long snap) { + public Collection getLoadedModules(long snap) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().getObjectsAtSnap(snap, TraceObjectModule.class); + } return delegateCollection(memSpaces.values(), m -> m.getLoadedModules(snap)); } @Override - public Collection getModulesAt(long snap, Address address) { + public Collection getModulesAt(long snap, Address address) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsContaining(snap, address, TargetModule.RANGE_ATTRIBUTE_NAME, + TraceObjectModule.class); + } return delegateRead(address.getAddressSpace(), m -> m.getModulesAt(snap, address), Set.of()); } @Override - public Collection getModulesIntersecting(Range lifespan, + public Collection getModulesIntersecting(Range lifespan, AddressRange range) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsIntersecting(lifespan, range, TargetModule.RANGE_ATTRIBUTE_NAME, + TraceObjectModule.class); + } return delegateRead(range.getAddressSpace(), m -> m.getModulesIntersecting(lifespan, range), Set.of()); } @@ -157,14 +188,24 @@ public class DBTraceModuleManager } @Override - public Collection getSectionsAt(long snap, Address address) { + public Collection getSectionsAt(long snap, Address address) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsContaining(snap, address, TargetSection.RANGE_ATTRIBUTE_NAME, + TraceObjectSection.class); + } return delegateRead(address.getAddressSpace(), m -> m.getSectionsAt(snap, address), Set.of()); } @Override - public Collection getSectionsIntersecting(Range lifespan, + public Collection getSectionsIntersecting(Range lifespan, AddressRange range) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectsIntersecting(lifespan, range, TargetSection.RANGE_ATTRIBUTE_NAME, + TraceObjectSection.class); + } return delegateRead(range.getAddressSpace(), m -> m.getSectionsIntersecting(lifespan, range), Set.of()); } @@ -186,7 +227,7 @@ public class DBTraceModuleManager } @Override - protected DBTraceModuleSpace createRegisterSpace(AddressSpace space, DBTraceThread thread, + protected DBTraceModuleSpace createRegisterSpace(AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { throw new AssertionError(); } @@ -204,7 +245,10 @@ public class DBTraceModuleManager } @Override - public Collection getAllSections() { + public Collection getAllSections() { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().getAllObjects(TraceObjectSection.class); + } return delegateCollection(memSpaces.values(), m -> m.getAllSections()); } @@ -212,12 +256,15 @@ public class DBTraceModuleManager return delegateCollection(memSpaces.values(), m -> m.doGetSectionsByModuleId(key)); } - protected Collection doGetSectionsByPath(String sectionPath) { + protected Collection doGetSectionsByPath(String sectionPath) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager().getObjectsByPath(sectionPath, TraceObjectSection.class); + } return delegateCollection(memSpaces.values(), m -> m.doGetSectionsByPath(sectionPath)); } @Override - public Collection getSectionsByPath(String sectionPath) { + public Collection getSectionsByPath(String sectionPath) { try (LockHold hold = LockHold.lock(lock.readLock())) { return Collections.unmodifiableCollection(doGetSectionsByPath(sectionPath)); } @@ -225,10 +272,14 @@ public class DBTraceModuleManager @Override public TraceSection getLoadedSectionByPath(long snap, String sectionPath) { + if (trace.getObjectManager().hasSchema()) { + return trace.getObjectManager() + .getObjectByPath(snap, sectionPath, TraceObjectSection.class); + } try (LockHold hold = LockHold.lock(lock.readLock())) { return doGetSectionsByPath(sectionPath) .stream() - .filter(s -> s.getLifespan().contains(snap)) + .filter(s -> s.getModule().getLifespan().contains(snap)) .findAny() .orElse(null); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java new file mode 100644 index 0000000000..ce7632795e --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectModule.java @@ -0,0 +1,242 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.module; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.*; +import ghidra.dbg.util.PathMatcher; +import ghidra.dbg.util.PathUtils; +import ghidra.program.model.address.*; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.target.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceModuleChangeType; +import ghidra.trace.model.modules.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.trace.util.TraceChangeType; +import ghidra.util.LockHold; +import ghidra.util.exception.DuplicateNameException; + +public class DBTraceObjectModule implements TraceObjectModule, DBTraceObjectInterface { + + protected class ModuleChangeTranslator extends Translator { + protected ModuleChangeTranslator(DBTraceObject object, TraceModule iface) { + super(TargetModule.RANGE_ATTRIBUTE_NAME, object, iface); + } + + @Override + protected TraceChangeType getAddedType() { + return TraceModuleChangeType.ADDED; + } + + @Override + protected TraceChangeType> getLifespanChangedType() { + return TraceModuleChangeType.LIFESPAN_CHANGED; + } + + @Override + protected TraceChangeType getChangedType() { + return TraceModuleChangeType.CHANGED; + } + + @Override + protected boolean appliesToKey(String key) { + return TargetModule.RANGE_ATTRIBUTE_NAME.equals(key) || + TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key); + } + + @Override + protected TraceChangeType getDeletedType() { + return TraceModuleChangeType.DELETED; + } + } + + private final DBTraceObject object; + private final ModuleChangeTranslator translator; + + public DBTraceObjectModule(DBTraceObject object) { + this.object = object; + + translator = new ModuleChangeTranslator(object, this); + } + + @Override + public Trace getTrace() { + return object.getTrace(); + } + + @Override + public TraceSection addSection(String sectionPath, String sectionName, AddressRange range) + throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + DBTraceObjectManager manager = object.getManager(); + List sectionKeyList = PathUtils.parse(sectionPath); + if (!PathUtils.isAncestor(object.getCanonicalPath().getKeyList(), sectionKeyList)) { + throw new IllegalArgumentException( + "Section path must be a successor of this module's path"); + } + return manager.addSection(sectionPath, sectionName, getLifespan(), range); + } + } + + @Override + public String getPath() { + return object.getCanonicalPath().toString(); + } + + @Override + public void setName(String name) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + } + } + + @Override + public String getName() { + return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(), + TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); + } + + @Override + public void setRange(AddressRange range) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), TargetModule.RANGE_ATTRIBUTE_NAME, range); + } + } + + @Override + public AddressRange getRange() { + return TraceObjectInterfaceUtils.getValue(object, getLoadedSnap(), + TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); + } + + @Override + public void setBase(Address base) { + try (LockHold hold = object.getTrace().lockWrite()) { + setRange(DBTraceUtils.toRange(base, getMaxAddress())); + } + } + + @Override + public Address getBase() { + AddressRange range = getRange(); + return range == null ? null : range.getMinAddress(); + } + + @Override + public void setMaxAddress(Address max) { + try (LockHold hold = object.getTrace().lockWrite()) { + setRange(DBTraceUtils.toRange(getBase(), max)); + } + } + + @Override + public Address getMaxAddress() { + AddressRange range = getRange(); + return range == null ? null : range.getMaxAddress(); + } + + @Override + public void setLength(long length) throws AddressOverflowException { + try (LockHold hold = object.getTrace().lockWrite()) { + setRange(new AddressRangeImpl(getBase(), length)); + } + } + + @Override + public long getLength() { + return getRange().getLength(); + } + + @Override + public void setLifespan(Range lifespan) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + TraceObjectInterfaceUtils.setLifespan(TraceObjectModule.class, object, lifespan); + for (TraceObjectSection section : getSections()) { + section.getObject().setLifespan(lifespan); + } + } + } + + @Override + public Range getLifespan() { + return object.getLifespan(); + } + + @Override + public void setLoadedSnap(long loadedSnap) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + setLifespan(DBTraceUtils.toRange(loadedSnap, getUnloadedSnap())); + } + } + + @Override + public long getLoadedSnap() { + return object.getMinSnap(); + } + + @Override + public void setUnloadedSnap(long unloadedSnap) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + setLifespan(DBTraceUtils.toRange(getLoadedSnap(), unloadedSnap)); + } + } + + @Override + public long getUnloadedSnap() { + return object.getMaxSnap(); + } + + @Override + public Collection getSections() { + try (LockHold hold = object.getTrace().lockRead()) { + return object.querySuccessorsInterface(getLifespan(), TraceObjectSection.class) + .collect(Collectors.toSet()); + } + } + + @Override + public TraceObjectSection getSectionByName(String sectionName) { + PathMatcher matcher = object.getTargetSchema().searchFor(TargetSection.class, true); + PathMatcher applied = matcher.applyKeys(List.of(sectionName)); + return object.getSuccessors(getLifespan(), applied) + .map(p -> p.getLastChild(object).queryInterface(TraceObjectSection.class)) + .findAny() + .orElse(null); + } + + @Override + public void delete() { + object.deleteTree(); + } + + @Override + public TraceObject getObject() { + return object; + } + + @Override + public TraceChangeRecord translateEvent(TraceChangeRecord rec) { + return translator.translate(rec); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectSection.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectSection.java new file mode 100644 index 0000000000..df8e7c614b --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/DBTraceObjectSection.java @@ -0,0 +1,132 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.module; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.*; +import ghidra.program.model.address.AddressRange; +import ghidra.trace.database.target.DBTraceObject; +import ghidra.trace.database.target.DBTraceObjectInterface; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceSectionChangeType; +import ghidra.trace.model.modules.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.trace.util.TraceChangeType; +import ghidra.util.LockHold; +import ghidra.util.exception.DuplicateNameException; + +public class DBTraceObjectSection implements TraceObjectSection, DBTraceObjectInterface { + + protected class SectionTranslator extends Translator { + protected SectionTranslator(DBTraceObject object, TraceSection iface) { + super(TargetSection.RANGE_ATTRIBUTE_NAME, object, iface); + } + + @Override + protected TraceChangeType getAddedType() { + return TraceSectionChangeType.ADDED; + } + + @Override + protected TraceChangeType> getLifespanChangedType() { + return null; // it's the module's lifespan that matters. + } + + @Override + protected TraceChangeType getChangedType() { + return TraceSectionChangeType.CHANGED; + } + + @Override + protected boolean appliesToKey(String key) { + return TargetSection.RANGE_ATTRIBUTE_NAME.equals(key) || + TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key); + } + + @Override + protected TraceChangeType getDeletedType() { + return TraceSectionChangeType.DELETED; + } + } + + private final DBTraceObject object; + private final SectionTranslator translator; + + public DBTraceObjectSection(DBTraceObject object) { + this.object = object; + + translator = new SectionTranslator(object, this); + } + + @Override + public Trace getTrace() { + return object.getTrace(); + } + + @Override + public TraceModule getModule() { + try (LockHold hold = object.getTrace().lockRead()) { + return object.queryAncestorsInterface(object.getLifespan(), TraceObjectModule.class) + .findAny() + .orElseThrow(); + } + } + + @Override + public String getPath() { + return object.getCanonicalPath().toString(); + } + + @Override + public void setName(String name) throws DuplicateNameException { + object.setValue(object.getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + } + + @Override + public String getName() { + return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), + TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); + } + + @Override + public void setRange(AddressRange range) { + object.setValue(object.getLifespan(), TargetModule.RANGE_ATTRIBUTE_NAME, range); + } + + @Override + public AddressRange getRange() { + return TraceObjectInterfaceUtils.getValue(object, object.getMinSnap(), + TargetModule.RANGE_ATTRIBUTE_NAME, AddressRange.class, null); + } + + @Override + public void delete() { + object.deleteTree(); + } + + @Override + public TraceObject getObject() { + return object; + } + + @Override + public TraceChangeRecord translateEvent(TraceChangeRecord rec) { + return translator.translate(rec); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/TraceObjectSection.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/TraceObjectSection.java new file mode 100644 index 0000000000..3a8e903008 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/module/TraceObjectSection.java @@ -0,0 +1,34 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.module; + +import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.TargetSection; +import ghidra.program.model.address.AddressRange; +import ghidra.trace.model.modules.TraceSection; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; + +@TraceObjectInfo( + targetIf = TargetSection.class, + shortName = "section", + fixedKeys = { + TargetObject.DISPLAY_ATTRIBUTE_NAME, + TargetSection.RANGE_ATTRIBUTE_NAME + }) +public interface TraceObjectSection extends TraceSection, TraceObjectInterface { + void setRange(AddressRange range); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java index 5635c4dc1d..15aa7d9b6d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewListing.java @@ -38,13 +38,13 @@ import ghidra.program.model.util.CodeUnitInsertionException; import ghidra.program.model.util.PropertyMap; import ghidra.trace.database.DBTrace; import ghidra.trace.database.listing.UndefinedDBTraceData; -import ghidra.trace.database.memory.DBTraceMemoryRegion; import ghidra.trace.database.memory.DBTraceMemorySpace; import ghidra.trace.database.symbol.DBTraceFunctionSymbol; import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.*; import ghidra.trace.model.listing.*; import ghidra.trace.model.map.TracePropertyMap; +import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramViewListing; import ghidra.trace.model.symbol.TraceFunctionSymbol; @@ -79,7 +79,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV protected final TraceCodeOperations codeOperations; protected final DBTraceProgramViewRootModule rootModule; - protected final Map fragmentsByRegion = + protected final Map fragmentsByRegion = new HashMap<>(); protected final Map undefinedCache = @@ -779,7 +779,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV @Override public ProgramFragment getFragment(String treeName, Address addr) { - DBTraceMemoryRegion region = program.memory.getTopRegion( + TraceMemoryRegion region = program.memory.getTopRegion( s -> program.trace.getMemoryManager().getRegionContaining(s, addr)); if (region == null) { return null; @@ -798,7 +798,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV @Override public ProgramFragment getFragment(String treeName, String name) { - DBTraceMemoryRegion region = program.memory.getTopRegion( + TraceMemoryRegion region = program.memory.getTopRegion( s -> program.trace.getMemoryManager().getLiveRegionByPath(s, name)); if (region == null) { return null; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java index 13400d4a7d..c74c61aab0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/AbstractDBTraceProgramViewMemory.java @@ -29,6 +29,7 @@ import ghidra.program.model.address.*; import ghidra.program.model.mem.*; import ghidra.trace.database.memory.*; import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramViewMemory; import ghidra.trace.util.MemoryAdapter; @@ -53,7 +54,7 @@ public abstract class AbstractDBTraceProgramViewMemory } protected void regionBlockRemoved( - RemovalNotification rn) { + RemovalNotification rn) { // Nothing } @@ -138,7 +139,7 @@ public abstract class AbstractDBTraceProgramViewMemory @Override public AddressSetView getExecuteSet() { AddressSet result = new AddressSet(); - for (DBTraceMemoryRegion region : memoryManager.getRegionsInternal()) { + for (TraceMemoryRegion region : memoryManager.getAllRegions()) { if (!region.isExecute() || !program.isRegionVisible(region, region.getLifespan())) { continue; } @@ -520,8 +521,12 @@ public abstract class AbstractDBTraceProgramViewMemory protected synchronized void changeRange(AddressRange remove, AddressRange add) { if (!forceFullView) { AddressSet temp = new AddressSet(addressSet); - temp.delete(remove); - temp.add(add); + if (remove != null) { + temp.delete(remove); + } + if (add != null) { + temp.add(add); + } addressSet = temp; } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java index 1567ee10a6..98475ec6ae 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramView.java @@ -1562,37 +1562,36 @@ public class DBTraceProgramView implements TraceProgramView { trace.removeTransactionListener(listener); } - public void updateMemoryAddRegionBlock(DBTraceMemoryRegion region) { + public void updateMemoryAddRegionBlock(TraceMemoryRegion region) { if (!isRegionVisible(region)) { return; } memory.updateAddRegionBlock(region); } - public void updateMemoryChangeRegionBlockName(DBTraceMemoryRegion region) { + public void updateMemoryChangeRegionBlockName(TraceMemoryRegion region) { if (!isRegionVisible(region)) { return; } memory.updateChangeRegionBlockName(region); } - public void updateMemoryChangeRegionBlockFlags(DBTraceMemoryRegion region) { - if (!isRegionVisible(region)) { + public void updateMemoryChangeRegionBlockFlags(TraceMemoryRegion region, Range lifespan) { + if (!isRegionVisible(region, lifespan)) { return; } memory.updateChangeRegionBlockFlags(region); } - public void updateMemoryChangeRegionBlockRange(DBTraceMemoryRegion region, - AddressRange oldRange, - AddressRange newRange) { + public void updateMemoryChangeRegionBlockRange(TraceMemoryRegion region, + AddressRange oldRange, AddressRange newRange) { if (!isRegionVisible(region)) { return; } memory.updateChangeRegionBlockRange(region, oldRange, newRange); } - public void updateMemoryChangeRegionBlockLifespan(DBTraceMemoryRegion region, + public void updateMemoryChangeRegionBlockLifespan(TraceMemoryRegion region, Range oldLifespan, Range newLifespan) { boolean inOld = isRegionVisible(region, oldLifespan); boolean inNew = isRegionVisible(region, newLifespan); @@ -1604,7 +1603,7 @@ public class DBTraceProgramView implements TraceProgramView { } } - public void updateMemoryDeleteRegionBlock(DBTraceMemoryRegion region) { + public void updateMemoryDeleteRegionBlock(TraceMemoryRegion region) { if (!isRegionVisible(region)) { return; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewFragment.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewFragment.java index b66d970730..6f5a197dc5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewFragment.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewFragment.java @@ -19,24 +19,24 @@ import java.util.Iterator; import ghidra.program.model.address.*; import ghidra.program.model.listing.*; -import ghidra.trace.database.memory.DBTraceMemoryRegion; +import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.util.exception.DuplicateNameException; import ghidra.util.exception.NotFoundException; // TODO: Destroy this in favor of databased trees? public class DBTraceProgramViewFragment implements ProgramFragment { protected final AbstractDBTraceProgramViewListing listing; - protected final DBTraceMemoryRegion region; + protected final TraceMemoryRegion region; public DBTraceProgramViewFragment(AbstractDBTraceProgramViewListing listing, - DBTraceMemoryRegion region) { + TraceMemoryRegion region) { this.listing = listing; this.region = region; } @Override public String getComment() { - return region.description(); + return ""; } @Override @@ -76,22 +76,27 @@ public class DBTraceProgramViewFragment implements ProgramFragment { @Override public boolean contains(Address addr) { - return region.contains(addr, listing.program.snap); + return region.getRange().contains(addr) && + region.getLifespan().contains(listing.program.snap); } @Override public boolean contains(Address start, Address end) { // Regions are contiguous - long snap = listing.program.snap; - return region.contains(start, snap) && region.contains(end, snap); + AddressRange range = region.getRange(); + return range.contains(start) && range.contains(end) && + region.getLifespan().contains(listing.program.snap); } @Override public boolean contains(AddressSetView rangeSet) { - long snap = listing.program.snap; + if (!region.getLifespan().contains(listing.program.snap)) { + return false; + } for (AddressRange range : rangeSet) { - if (!region.contains(range.getMinAddress(), snap) || - !region.contains(range.getMaxAddress(), snap)) { + AddressRange regionRange = region.getRange(); + if (!regionRange.contains(range.getMinAddress()) || + !regionRange.contains(range.getMaxAddress())) { return false; } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java index e3598d330e..46cfaa6e0e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemory.java @@ -23,12 +23,12 @@ import com.google.common.cache.CacheBuilder; import ghidra.program.model.address.*; import ghidra.program.model.mem.MemoryBlock; -import ghidra.trace.database.memory.DBTraceMemoryRegion; +import ghidra.trace.model.memory.TraceMemoryRegion; public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { // NB. Keep both per-region and force-full (per-space) block sets ready - private final Map regionBlocks = + private final Map regionBlocks = CacheBuilder.newBuilder() .removalListener(this::regionBlockRemoved) .weakValues() @@ -45,10 +45,10 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { super(program); } - protected DBTraceMemoryRegion getTopRegion(Function regFunc) { + protected TraceMemoryRegion getTopRegion(Function regFunc) { return program.viewport.getTop(s -> { // TODO: There is probably an early-bail condition I can check for. - DBTraceMemoryRegion reg = regFunc.apply(s); + TraceMemoryRegion reg = regFunc.apply(s); if (reg != null && program.isRegionVisible(reg)) { return reg; } @@ -56,10 +56,10 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { }); } - protected void forVisibleRegions(Consumer action) { + protected void forVisibleRegions(Consumer action) { for (long s : program.viewport.getOrderedSnaps()) { // NOTE: This is slightly faster than new AddressSet(mm.getRegionsAddressSet(snap)) - for (DBTraceMemoryRegion reg : memoryManager.getRegionsAtSnap(s)) { + for (TraceMemoryRegion reg : memoryManager.getRegionsAtSnap(s)) { if (program.isRegionVisible(reg)) { action.accept(reg); } @@ -75,7 +75,7 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { addressSet = temp; } - protected MemoryBlock getRegionBlock(DBTraceMemoryRegion region) { + protected MemoryBlock getRegionBlock(TraceMemoryRegion region) { return regionBlocks.computeIfAbsent(region, r -> new DBTraceProgramViewMemoryRegionBlock(program, region)); } @@ -90,7 +90,7 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { if (forceFullView) { return getSpaceBlock(addr.getAddressSpace()); } - DBTraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr)); + TraceMemoryRegion region = getTopRegion(s -> memoryManager.getRegionContaining(s, addr)); return region == null ? null : getRegionBlock(region); } @@ -100,7 +100,7 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { AddressSpace space = program.getAddressFactory().getAddressSpace(blockName); return space == null ? null : getSpaceBlock(space); } - DBTraceMemoryRegion region = + TraceMemoryRegion region = getTopRegion(s -> memoryManager.getLiveRegionByPath(s, blockName)); return region == null ? null : getRegionBlock(region); } @@ -118,26 +118,26 @@ public class DBTraceProgramViewMemory extends AbstractDBTraceProgramViewMemory { return result.toArray(new MemoryBlock[result.size()]); } - public void updateAddRegionBlock(DBTraceMemoryRegion region) { + public void updateAddRegionBlock(TraceMemoryRegion region) { // TODO: add block to cache? addRange(region.getRange()); } - public void updateChangeRegionBlockName(DBTraceMemoryRegion region) { + public void updateChangeRegionBlockName(TraceMemoryRegion region) { // Nothing. Block name is taken from region, uncached } - public void updateChangeRegionBlockFlags(DBTraceMemoryRegion region) { + public void updateChangeRegionBlockFlags(TraceMemoryRegion region) { // Nothing. Block flags are taken from region, uncached } - public void updateChangeRegionBlockRange(DBTraceMemoryRegion region, AddressRange oldRange, + public void updateChangeRegionBlockRange(TraceMemoryRegion region, AddressRange oldRange, AddressRange newRange) { // TODO: update cached block? Nothing to update. changeRange(oldRange, newRange); } - public void updateDeleteRegionBlock(DBTraceMemoryRegion region) { + public void updateDeleteRegionBlock(TraceMemoryRegion region) { regionBlocks.remove(region); removeRange(region.getRange()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemoryRegionBlock.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemoryRegionBlock.java index 2deac59bb6..76c7202e28 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemoryRegionBlock.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewMemoryRegionBlock.java @@ -20,18 +20,16 @@ import java.math.BigInteger; import ghidra.framework.store.LockException; import ghidra.program.model.address.*; -import ghidra.trace.database.memory.DBTraceMemoryRegion; import ghidra.trace.database.memory.DBTraceMemorySpace; -import ghidra.trace.model.memory.TraceMemoryFlag; -import ghidra.trace.model.memory.TraceMemorySpaceInputStream; +import ghidra.trace.model.memory.*; // TODO: Proper locking all over here public class DBTraceProgramViewMemoryRegionBlock extends AbstractDBTraceProgramViewMemoryBlock { - private final DBTraceMemoryRegion region; + private final TraceMemoryRegion region; public DBTraceProgramViewMemoryRegionBlock(DBTraceProgramView program, - DBTraceMemoryRegion region) { + TraceMemoryRegion region) { super(program); this.region = region; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisterListing.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisterListing.java index de39e49d0a..7f83449056 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisterListing.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisterListing.java @@ -20,14 +20,14 @@ import com.google.common.collect.Range; import ghidra.program.model.address.*; import ghidra.trace.database.listing.DBTraceCodeRegisterSpace; import ghidra.trace.database.listing.UndefinedDBTraceData; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.program.TraceProgramViewRegisterListing; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; public class DBTraceProgramViewRegisterListing extends AbstractDBTraceProgramViewListing implements TraceProgramViewRegisterListing { - private final DBTraceThread thread; + private final TraceThread thread; private Address minAddr; private Address maxAddr; @@ -42,7 +42,7 @@ public class DBTraceProgramViewRegisterListing extends AbstractDBTraceProgramVie } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java index 14ee0e4e06..1109ed1f40 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegisters.java @@ -35,7 +35,6 @@ import ghidra.program.model.util.AddressSetPropertyMap; import ghidra.program.model.util.PropertyMapManager; import ghidra.trace.database.listing.DBTraceCodeRegisterSpace; import ghidra.trace.database.memory.DBTraceMemoryRegisterSpace; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace; import ghidra.trace.model.data.TraceBasedDataTypeManager; import ghidra.trace.model.program.TraceProgramView; @@ -50,7 +49,7 @@ public class DBTraceProgramViewRegisters implements TraceProgramView { protected final DomainObjectEventQueues eventQueues; private final DBTraceProgramView view; - private final DBTraceThread thread; + private final TraceThread thread; private final DBTraceProgramViewRegisterListing listing; private final DBTraceProgramViewRegisterMemory memory; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegistersReferenceManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegistersReferenceManager.java index 7c684f453b..6b7e7e88c6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegistersReferenceManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewRegistersReferenceManager.java @@ -15,17 +15,17 @@ */ package ghidra.trace.database.program; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.listing.TraceCodeOperations; import ghidra.trace.model.symbol.TraceReferenceOperations; +import ghidra.trace.model.thread.TraceThread; public class DBTraceProgramViewRegistersReferenceManager extends AbstractDBTraceProgramViewReferenceManager { - private final DBTraceThread thread; + private final TraceThread thread; public DBTraceProgramViewRegistersReferenceManager(DBTraceProgramView program, - DBTraceThread thread) { + TraceThread thread) { super(program); this.thread = thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java index 0945544dea..2bd9c037cb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/space/AbstractDBTraceSpaceBasedManager.java @@ -29,7 +29,6 @@ import ghidra.program.model.address.AddressFactory; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; import ghidra.trace.database.*; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.thread.TraceThread; @@ -134,7 +133,11 @@ public abstract class AbstractDBTraceSpaceBasedManager frame = ImmutablePair.of(thread, frameLevel); if (!createIfAbsent) { @@ -203,8 +206,8 @@ public abstract class AbstractDBTraceSpaceBasedManager { + protected StackChangeTranslator(DBTraceObject object, TraceStack iface) { + super(null, object, iface); + } + + @Override + protected TraceChangeType getAddedType() { + return TraceStackChangeType.ADDED; + } + + @Override + protected TraceChangeType> getLifespanChangedType() { + return null; + } + + @Override + protected TraceChangeType getChangedType() { + return TraceStackChangeType.CHANGED; + } + + @Override + protected boolean appliesToKey(String key) { + return false; + } + + @Override + protected TraceChangeType getDeletedType() { + return TraceStackChangeType.DELETED; + } + } + + private final DBTraceObject object; + private final StackChangeTranslator translator; + + public DBTraceObjectStack(DBTraceObject object) { + this.object = object; + + translator = new StackChangeTranslator(object, this); + } + + @Override + public TraceThread getThread() { + try (LockHold hold = object.getTrace().lockRead()) { + return object.queryAncestorsInterface(object.getLifespan(), TraceObjectThread.class) + .findAny() + .orElseThrow(); + } + } + + @Override + public long getSnap() { + return object.getMinSnap(); + } + + @Override + public int getDepth() { + try (LockHold hold = object.getTrace().lockRead()) { + return object + .querySuccessorsInterface(object.getLifespan(), TraceObjectStackFrame.class) + .map(f -> f.getLevel()) + .reduce(Integer::max) + .map(m -> m + 1) + .orElse(0); + } + } + + protected TraceObjectStackFrame doAddStackFrame(int level) { + try (LockHold hold = object.getTrace().lockWrite()) { + PathMatcher matcher = object.getTargetSchema().searchFor(TargetStackFrame.class, true); + List relKeyList = + matcher.applyIndices(PathUtils.makeIndex(level)).getSingletonPath(); + if (relKeyList == null) { + throw new IllegalStateException("Could not determine where to create new frame"); + } + List keyList = + PathUtils.extend(object.getCanonicalPath().getKeyList(), relKeyList); + return object.getManager().addStackFrame(keyList, getSnap()); + } + } + + protected void copyFrameAttributes(TraceObjectStackFrame from, TraceObjectStackFrame to) { + // TODO: All attributes or just those known to StackFrame? + to.setProgramCounter(from.getProgramCounter()); + } + + protected void shiftFrameAttributes(int from, int to, int count, + List frames) { + if (from == to) { + return; + } + if (from < to) { + for (int i = count - 1; i >= 0; i--) { + copyFrameAttributes(frames.get(from + i), frames.get(to + i)); + } + } + else { + for (int i = 0; i < count; i++) { + copyFrameAttributes(frames.get(from + i), frames.get(to + i)); + } + } + } + + protected void clearFrameAttributes(int start, int end, List frames) { + for (int i = start; i < end; i++) { + TraceObjectStackFrame frame = frames.get(i); + frame.setProgramCounter(null); + } + } + + @Override + public void setDepth(int depth, boolean atInner) { + try (LockHold hold = object.getTrace().lockWrite()) { + List frames = // Want mutable list + doGetFrames().collect(Collectors.toCollection(ArrayList::new)); + int curDepth = frames.size(); + if (curDepth == depth) { + return; + } + if (depth < curDepth) { + if (atInner) { + int diff = curDepth - depth; + shiftFrameAttributes(diff, 0, depth, frames); + } + for (int i = depth; i < curDepth; i++) { + frames.get(i).getObject().deleteTree(); + } + } + else { + for (int i = curDepth; i < depth; i++) { + frames.add(doAddStackFrame(i)); + } + if (atInner) { + int diff = depth - curDepth; + shiftFrameAttributes(0, diff, curDepth, frames); + clearFrameAttributes(0, diff, frames); + } + } + } + } + + protected TraceStackFrame doGetFrame(int level) { + TargetObjectSchema schema = object.getTargetSchema(); + PathPredicates matcher = schema.searchFor(TargetStackFrame.class, true); + matcher = matcher.applyIndices(PathUtils.makeIndex(level)); + return object.getSuccessors(object.getLifespan(), matcher) + .findAny() + .map(p -> p.getLastChild(object).queryInterface(TraceObjectStackFrame.class)) + .orElse(null); + } + + @Override + // This assumes the frame indices are contiguous and include 0 + public TraceStackFrame getFrame(int level, boolean ensureDepth) { + if (ensureDepth) { + try (LockHold hold = object.getTrace().lockWrite()) { + if (level >= getDepth()) { + setDepth(level + 1, false); + } + return doGetFrame(level); + } + } + else { + try (LockHold hold = object.getTrace().lockRead()) { + return doGetFrame(level); + } + } + } + + protected Stream doGetFrames() { + return object + .querySuccessorsInterface(object.getLifespan(), TraceObjectStackFrame.class) + .sorted(Comparator.comparing(f -> f.getLevel())); + } + + @Override + public List getFrames() { + try (LockHold hold = object.getTrace().lockRead()) { + return doGetFrames().collect(Collectors.toList()); + } + } + + @Override + public void delete() { + object.deleteTree(); + } + + @Override + public TraceObject getObject() { + return object; + } + + @Override + public TraceChangeRecord translateEvent(TraceChangeRecord rec) { + return translator.translate(rec); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStackFrame.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStackFrame.java new file mode 100644 index 0000000000..34650cc277 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceObjectStackFrame.java @@ -0,0 +1,147 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.stack; + +import java.util.List; + +import ghidra.dbg.target.TargetStackFrame; +import ghidra.dbg.util.PathUtils; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.CodeUnit; +import ghidra.trace.database.target.DBTraceObject; +import ghidra.trace.database.target.DBTraceObjectInterface; +import ghidra.trace.model.Trace.TraceObjectChangeType; +import ghidra.trace.model.Trace.TraceStackChangeType; +import ghidra.trace.model.stack.TraceObjectStack; +import ghidra.trace.model.stack.TraceObjectStackFrame; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.util.TraceAddressSpace; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.util.LockHold; + +public class DBTraceObjectStackFrame implements TraceObjectStackFrame, DBTraceObjectInterface { + private final DBTraceObject object; + + public DBTraceObjectStackFrame(DBTraceObject object) { + this.object = object; + } + + @Override + public TraceObjectStack getStack() { + try (LockHold hold = object.getTrace().lockRead()) { + return object + .queryCanonicalAncestorsInterface(object.getLifespan(), TraceObjectStack.class) + .findAny() + .orElseThrow(); + } + } + + @Override + public int getLevel() { + List keys = object.getCanonicalPath().getKeyList(); + for (int i = keys.size() - 1; i >= 0; i--) { + String k = keys.get(i); + if (!PathUtils.isIndex(k)) { + continue; + } + String index = PathUtils.parseIndex(k); + try { + return Integer.parseInt(index, 10); // TODO: How to know the radix? + // TODO: Perhaps just have an attribute that is its level? + } + catch (NumberFormatException e) { + // fall to preceding key + } + } + throw new IllegalStateException("Frame has no index!?"); + } + + @Override + public Address getProgramCounter() { + return TraceObjectInterfaceUtils.getValue(object, object.getMaxSnap(), + TargetStackFrame.PC_ATTRIBUTE_NAME, Address.class, null); + } + + @Override + public void setProgramCounter(Address pc) { + try (LockHold hold = object.getTrace().lockWrite()) { + if (pc == Address.NO_ADDRESS) { + pc = null; + } + object.setValue(object.getLifespan(), TargetStackFrame.PC_ATTRIBUTE_NAME, pc); + } + } + + @Override + public String getComment() { + // TODO: One day, we'll have dynamic columns in the debugger + /** + * I don't use an attribute for this, because there's not a nice way track the "identity" of + * a stack frame. If the frame is re-used (the recommendation for connector development), + * the same comment may not necessarily apply. It'd be nice if the connector re-assigned + * levels so that identical objects implied identical frames, but that's quite a burden. The + * closest identity heuristic is the program counter. Instead of commenting the frame, I'll + * comment the memory at the program counter (often, really the return address). Not + * perfect, since it may collide with other comments, but a decent approximation that will + * follow the "same frame" as its level changes. + */ + try (LockHold hold = object.getTrace().lockRead()) { + Address pc = getProgramCounter(); + return pc == null ? null + : object.getTrace() + .getCommentAdapter() + .getComment(object.getMaxSnap(), pc, CodeUnit.EOL_COMMENT); + } + } + + @Override + public void setComment(String comment) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.getTrace() + .getCommentAdapter() + .setComment(object.getLifespan(), getProgramCounter(), CodeUnit.EOL_COMMENT, + comment); + } + } + + @Override + public TraceObject getObject() { + return object; + } + + protected boolean isPcChange(TraceChangeRecord rec) { + TraceChangeRecord cast = + TraceObjectChangeType.VALUE_CHANGED.cast(rec); + return TargetStackFrame.PC_ATTRIBUTE_NAME.equals(cast.getAffectedObject().getEntryKey()); + } + + @Override + public TraceChangeRecord translateEvent(TraceChangeRecord rec) { + if (rec.getEventType() == TraceObjectChangeType.CREATED.getType() || + rec.getEventType() == TraceObjectChangeType.DELETED.getType() || + rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType() && + isPcChange(rec)) { + // NB. Affected object may be the wrapped object, or the value entry + TraceAddressSpace space = + spaceForValue(object.getMinSnap(), TargetStackFrame.PC_ATTRIBUTE_NAME); + return new TraceChangeRecord<>(TraceStackChangeType.CHANGED, space, getStack(), null, + null); + } + return null; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStack.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStack.java index d92df16baa..91dbe03054 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStack.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStack.java @@ -22,7 +22,6 @@ import java.util.*; import db.BinaryField; import db.DBRecord; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceStackChangeType; import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.stack.TraceStackFrame; @@ -109,7 +108,7 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack { private final DBTraceStackManager manager; - private DBTraceThread thread; + private TraceThread thread; private final List frames = new ArrayList<>(); public DBTraceStack(DBTraceStackManager manager, DBCachedObjectStore store, @@ -135,7 +134,7 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack { } } - void set(DBTraceThread thread, long snap) { + void set(TraceThread thread, long snap) { this.thread = thread; threadSnap.threadKey = thread.getKey(); threadSnap.snap = snap; @@ -169,7 +168,7 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack { update(FRAMES_COLUMN); } - protected void doUpdateFrameDepths(int start, int end) { + protected void doUpdateFrameLevels(int start, int end) { for (int i = start; i < end; i++) { frames.get(i).setLevel(i); } @@ -177,13 +176,13 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack { @Override public void setDepth(int depth, boolean atInner) { - //System.err.println("setDepth(threadKey=" + thread.getKey() + "snap=" + getSnap() + - // ",depth=" + depth + ",inner=" + atInner + ");"); - int curDepth = frameKeys == null ? 0 : frameKeys.length; - if (depth == curDepth) { - return; - } try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + //System.err.println("setDepth(threadKey=" + thread.getKey() + "snap=" + getSnap() + + // ",depth=" + depth + ",inner=" + atInner + ");"); + int curDepth = frameKeys == null ? 0 : frameKeys.length; + if (depth == curDepth) { + return; + } if (depth < curDepth) { List toRemove = atInner ? frames.subList(0, curDepth - depth) @@ -193,7 +192,7 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack { } toRemove.clear(); if (atInner) { - doUpdateFrameDepths(0, frames.size()); + doUpdateFrameLevels(0, frames.size()); } } else { @@ -204,11 +203,11 @@ public class DBTraceStack extends DBAnnotatedObject implements TraceStack { } if (atInner) { frames.addAll(0, toAdd); - doUpdateFrameDepths(0, frames.size()); + doUpdateFrameLevels(0, frames.size()); } else { frames.addAll(toAdd); - doUpdateFrameDepths(frames.size() - toAdd.size(), frames.size()); + doUpdateFrameLevels(frames.size() - toAdd.size(), frames.size()); } } doUpdateFrameKeys(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java index d442c03236..5c2b534fcf 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/stack/DBTraceStackManager.java @@ -16,21 +16,27 @@ package ghidra.trace.database.stack; import java.io.IOException; +import java.util.List; import java.util.concurrent.locks.ReadWriteLock; +import com.google.common.collect.Range; + import db.DBHandle; import generic.NestedIterator; +import ghidra.dbg.target.*; +import ghidra.dbg.util.*; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSetView; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceManager; import ghidra.trace.database.address.DBTraceOverlaySpaceAdapter; import ghidra.trace.database.stack.DBTraceStack.ThreadSnap; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.Trace.TraceStackChangeType; -import ghidra.trace.model.stack.TraceStackFrame; -import ghidra.trace.model.stack.TraceStackManager; +import ghidra.trace.model.stack.*; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectKeyPath; +import ghidra.trace.model.thread.TraceObjectThread; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; @@ -84,17 +90,63 @@ public class DBTraceStackManager implements TraceStackManager, DBTraceManager { trace.dbError(e); } - public DBTraceStack getStackByKey(long stackKey) { + protected DBTraceStack getStackByKey(long stackKey) { return stackStore.getObjectAt(stackKey); } - public DBTraceStackFrame getFrameByKey(long frameKey) { + protected DBTraceStackFrame getFrameByKey(long frameKey) { return frameStore.getObjectAt(frameKey); } + public static PathPredicates single(TraceObject seed, Class targetIf) { + PathMatcher stackMatcher = seed.getTargetSchema().searchFor(targetIf, false); + PathPattern singleton = stackMatcher.getSingletonPattern(); + if (singleton.getSingletonPath() == null) { + throw new IllegalStateException("Schema doesn't provide a unique " + + targetIf.getSimpleName() + " for " + seed.getCanonicalPath()); + } + return singleton; + } + + protected TraceObjectStack doGetOrAddObjectStack(TraceThread thread, long snap, + boolean createIfAbsent) { + TraceObjectThread objThread = (TraceObjectThread) thread; + TraceObject obj = objThread.getObject(); + PathPredicates predicates = single(obj, TargetStack.class); + if (createIfAbsent) { + try (LockHold hold = trace.lockWrite()) { + TraceObjectStack stack = + trace.getObjectManager() + .getSuccessor(obj, predicates, snap, TraceObjectStack.class); + if (stack != null) { + return stack; + } + List keyList = PathUtils.extend(obj.getCanonicalPath().getKeyList(), + predicates.getSingletonPath()); + return trace.getObjectManager().addStack(keyList, snap); + } + } + try (LockHold hold = trace.lockRead()) { + return trace.getObjectManager() + .getSuccessor(obj, predicates, snap, TraceObjectStack.class); + } + } + + protected TraceObjectStack doGetLatestObjectStack(TraceThread thread, long snap) { + TraceObjectThread objThread = (TraceObjectThread) thread; + TraceObject obj = objThread.getObject(); + List keyList = single(obj, TargetStack.class).getSingletonPath(); + return trace.getObjectManager() + .getLatestSuccessor(obj, TraceObjectKeyPath.of(keyList), snap, + TraceObjectStack.class); + } + @Override - public DBTraceStack getStack(TraceThread thread, long snap, boolean createIfAbsent) { - DBTraceThread dbThread = threadManager.assertIsMine(thread); + public TraceStack getStack(TraceThread thread, long snap, boolean createIfAbsent) { + threadManager.assertIsMine(thread); + if (trace.getObjectManager().hasSchema()) { + return doGetOrAddObjectStack(thread, snap, createIfAbsent); + } DBTraceStack stack; ThreadSnap key = new ThreadSnap(thread.getKey(), snap); if (createIfAbsent) { @@ -104,7 +156,7 @@ public class DBTraceStackManager implements TraceStackManager, DBTraceManager { return stack; } stack = stackStore.create(); - stack.set(dbThread, snap); + stack.set(thread, snap); } trace.setChanged(new TraceChangeRecord<>(TraceStackChangeType.ADDED, null, stack)); return stack; @@ -113,22 +165,35 @@ public class DBTraceStackManager implements TraceStackManager, DBTraceManager { } @Override - public DBTraceStack getLatestStack(TraceThread thread, long snap) { + public TraceStack getLatestStack(TraceThread thread, long snap) { threadManager.assertIsMine(thread); - DBTraceStack found = stacksByThreadSnap.floorValue(new ThreadSnap(thread.getKey(), snap)); - if (found == null) { - return null; + try (LockHold hold = LockHold.lock(lock.readLock())) { + if (trace.getObjectManager().hasSchema()) { + return doGetLatestObjectStack(thread, snap); + } + DBTraceStack found = + stacksByThreadSnap.floorValue(new ThreadSnap(thread.getKey(), snap)); + if (found == null) { + return null; + } + if (found.getThread() != thread || found.getSnap() > snap) { + // Encoded field results in unsigned index + // NB. Conventionally, a search should never traverse 0 (real to scratch space) + return null; + } + return found; } - if (found.getThread() != thread || found.getSnap() > snap) { - // Encoded field results in unsigned index - // NB. Conventionally, a search should never traverse 0 (real to scratch space) - return null; - } - return found; } @Override + // TODO: Should probably include a lifespan parameter? public Iterable getFramesIn(AddressSetView set) { + if (trace.getObjectManager().hasSchema()) { + return () -> NestedIterator.start(set.iterator(), rng -> trace.getObjectManager() + .getObjectsIntersecting(Range.all(), rng, TargetStackFrame.PC_ATTRIBUTE_NAME, + TraceObjectStackFrame.class) + .iterator()); + } return () -> NestedIterator.start(set.iterator(), rng -> framesByPC .sub(rng.getMinAddress(), true, rng.getMaxAddress(), true) .values() diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbol.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbol.java index fadde9a0e4..f2526e3e57 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbol.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbol.java @@ -39,10 +39,10 @@ import ghidra.trace.database.address.DBTraceOverlaySpaceAdapter.DecodesAddresses import ghidra.trace.database.program.DBTraceProgramView; import ghidra.trace.database.symbol.DBTraceSymbolManager.DBTraceSymbolIDEntry; import ghidra.trace.database.symbol.DBTraceSymbolManager.MySymbolTypes; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceSymbolChangeType; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.symbol.TraceSymbol; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceAddressSpace; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; @@ -172,7 +172,7 @@ public abstract class AbstractDBTraceSymbol extends DBAnnotatedObject } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return null; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbolSingleTypeWithLocationView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbolSingleTypeWithLocationView.java index e507598437..4cdc832f9d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbolSingleTypeWithLocationView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/AbstractDBTraceSymbolSingleTypeWithLocationView.java @@ -31,7 +31,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.DBTraceSpaceKey; import ghidra.trace.database.symbol.DBTraceSymbolManager.DBTraceSymbolIDEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.symbol.TraceNamespaceSymbol; import ghidra.trace.model.symbol.TraceSymbolManager; @@ -193,11 +192,10 @@ public abstract class AbstractDBTraceSymbolSingleTypeWithLocationView getIntersecting(Range span, TraceThread thread, AddressRange range, boolean includeDynamicSymbols) { try (LockHold hold = LockHold.lock(manager.lock.readLock())) { - DBTraceThread dbThread = - thread == null ? null : manager.trace.getThreadManager().assertIsMine(thread); - manager.assertValidThreadAddress(dbThread, range.getMinAddress()); // Only examines space + manager.trace.getThreadManager().assertIsMine(thread); + manager.assertValidThreadAddress(thread, range.getMinAddress()); // Only examines space DBTraceAddressSnapRangePropertyMapSpace space = - manager.idMap.get(DBTraceSpaceKey.create(range.getAddressSpace(), dbThread, 0), + manager.idMap.get(DBTraceSpaceKey.create(range.getAddressSpace(), thread, 0), false); if (space == null) { return Collections.emptyList(); @@ -214,11 +212,10 @@ public abstract class AbstractDBTraceSymbolSingleTypeWithLocationView getIntersecting(Range span, TraceThread thread, AddressRange range, boolean includeDynamicSymbols, boolean forward) { try (LockHold hold = LockHold.lock(manager.lock.readLock())) { - DBTraceThread dbThread = - thread == null ? null : manager.trace.getThreadManager().assertIsMine(thread); - manager.assertValidThreadAddress(dbThread, range.getMinAddress()); // Only examines space + manager.trace.getThreadManager().assertIsMine(thread); + manager.assertValidThreadAddress(thread, range.getMinAddress()); // Only examines space DBTraceAddressSnapRangePropertyMapSpace space = - manager.idMap.get(DBTraceSpaceKey.create(range.getAddressSpace(), dbThread, 0), + manager.idMap.get(DBTraceSpaceKey.create(range.getAddressSpace(), thread, 0), false); if (space == null) { return Collections.emptyList(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateManager.java index 42df5867d7..21a4f17dee 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateManager.java @@ -30,7 +30,6 @@ import ghidra.program.model.lang.Language; import ghidra.trace.database.DBTrace; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager; import ghidra.trace.database.space.DBTraceDelegatingManager; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.symbol.TraceEquateManager; @@ -116,8 +115,7 @@ public class DBTraceEquateManager @Override protected DBTraceEquateRegisterSpace createRegisterSpace(AddressSpace space, - DBTraceThread thread, - DBTraceSpaceEntry ent) throws VersionException, IOException { + TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { return new DBTraceEquateRegisterSpace(this, dbh, space, ent, thread); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateRegisterSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateRegisterSpace.java index 026996ce41..934c08706d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateRegisterSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateRegisterSpace.java @@ -20,18 +20,18 @@ import java.io.IOException; import db.DBHandle; import ghidra.program.model.address.AddressSpace; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.symbol.TraceEquateRegisterSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.exception.VersionException; public class DBTraceEquateRegisterSpace extends DBTraceEquateSpace implements TraceEquateRegisterSpace { - protected final DBTraceThread thread; + protected final TraceThread thread; private final int frameLevel; public DBTraceEquateRegisterSpace(DBTraceEquateManager manager, DBHandle dbh, - AddressSpace space, DBTraceSpaceEntry ent, DBTraceThread thread) + AddressSpace space, DBTraceSpaceEntry ent, TraceThread thread) throws VersionException, IOException { super(manager, dbh, space, ent); this.thread = thread; @@ -39,7 +39,7 @@ public class DBTraceEquateRegisterSpace extends DBTraceEquateSpace } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateSpace.java index 05738f4afb..d388c73933 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceEquateSpace.java @@ -33,8 +33,8 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Abstract import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; import ghidra.trace.database.space.DBTraceSpaceBased; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.symbol.TraceEquateSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.LockHold; import ghidra.util.database.*; import ghidra.util.database.annot.*; @@ -135,7 +135,7 @@ public class DBTraceEquateSpace implements DBTraceSpaceBased, TraceEquateSpace { } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return null; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceLabelSymbol.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceLabelSymbol.java index 054a42cb7a..917d0fbf4d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceLabelSymbol.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceLabelSymbol.java @@ -28,9 +28,9 @@ import ghidra.trace.database.address.DBTraceOverlaySpaceAdapter.AddressDBFieldCo import ghidra.trace.database.address.DBTraceOverlaySpaceAdapter.DecodesAddresses; import ghidra.trace.database.listing.*; import ghidra.trace.database.space.DBTraceSpaceKey; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceSymbolChangeType; import ghidra.trace.model.symbol.TraceLabelSymbol; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceAddressSpace; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; @@ -70,7 +70,7 @@ public class DBTraceLabelSymbol extends AbstractDBTraceSymbol @DBAnnotatedField(column = END_SNAP_COLUMN_NAME) protected long endSnap; - protected DBTraceThread thread; + protected TraceThread thread; protected Range lifespan; public DBTraceLabelSymbol(DBTraceSymbolManager manager, DBCachedObjectStore store, @@ -89,7 +89,7 @@ public class DBTraceLabelSymbol extends AbstractDBTraceSymbol lifespan = DBTraceUtils.toRange(startSnap, endSnap); } - protected void set(Range lifespan, DBTraceThread thread, Address address, String name, + protected void set(Range lifespan, TraceThread thread, Address address, String name, DBTraceNamespaceSymbol parent, SourceType source) { this.name = name; this.parentID = parent.getID(); @@ -146,7 +146,7 @@ public class DBTraceLabelSymbol extends AbstractDBTraceSymbol } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceLabelSymbolView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceLabelSymbolView.java index a512f019ea..7a09b25235 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceLabelSymbolView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceLabelSymbolView.java @@ -19,7 +19,6 @@ import com.google.common.collect.Range; import ghidra.program.model.address.Address; import ghidra.program.model.symbol.*; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceSymbolChangeType; import ghidra.trace.model.symbol.TraceLabelSymbolView; import ghidra.trace.model.symbol.TraceNamespaceSymbol; @@ -46,13 +45,12 @@ public class DBTraceLabelSymbolView } DBTraceSymbolManager.assertValidName(name); try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { - DBTraceThread dbThread = - thread == null ? null : manager.trace.getThreadManager().assertIsMine(thread); + manager.trace.getThreadManager().assertIsMine(thread); DBTraceNamespaceSymbol dbnsParent = manager.assertIsMine((Namespace) parent); - manager.assertValidThreadAddress(dbThread, address); + manager.assertValidThreadAddress(thread, address); DBTraceLabelSymbol label = store.create(); - label.set(lifespan, dbThread, address, name, dbnsParent, source); - manager.putID(lifespan, dbThread, address, label.getID()); + label.set(lifespan, thread, address, name, dbnsParent, source); + manager.putID(lifespan, thread, address, label.getID()); cacheForAt.notifyNewEntry(lifespan, address, label); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReference.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReference.java index 36b4012ae0..6d2c815839 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReference.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReference.java @@ -25,10 +25,10 @@ import ghidra.program.model.symbol.*; import ghidra.trace.database.DBTrace; import ghidra.trace.database.DBTraceUtils; import ghidra.trace.database.symbol.DBTraceReferenceSpace.DBTraceReferenceEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceReferenceChangeType; import ghidra.trace.model.Trace.TraceSymbolChangeType; import ghidra.trace.model.symbol.*; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; @@ -44,7 +44,7 @@ public class DBTraceReference implements TraceReference { return ent.space.trace; } - public DBTraceThread getThread() { + public TraceThread getThread() { return ent.space.getThread(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceManager.java index 7f0e6d53a1..f8437c788f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceManager.java @@ -34,7 +34,6 @@ import ghidra.trace.database.address.DBTraceOverlaySpaceAdapter; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager; import ghidra.trace.database.space.DBTraceDelegatingManager; import ghidra.trace.database.symbol.DBTraceReferenceSpace.DBTraceReferenceEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.stack.TraceStackFrame; import ghidra.trace.model.symbol.TraceReference; @@ -70,7 +69,7 @@ public class DBTraceReferenceManager extends @Override protected DBTraceReferenceRegisterSpace createRegisterSpace(AddressSpace space, - DBTraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { + TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { return new DBTraceReferenceRegisterSpace(this, dbh, space, ent, thread); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceRegisterSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceRegisterSpace.java index 7f08c0be67..5b73b04e79 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceRegisterSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceRegisterSpace.java @@ -20,17 +20,17 @@ import java.io.IOException; import db.DBHandle; import ghidra.program.model.address.AddressSpace; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.symbol.TraceReferenceRegisterSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.exception.VersionException; public class DBTraceReferenceRegisterSpace extends DBTraceReferenceSpace implements TraceReferenceRegisterSpace { - protected final DBTraceThread thread; + protected final TraceThread thread; private final int frameLevel; public DBTraceReferenceRegisterSpace(DBTraceReferenceManager manager, DBHandle dbh, - AddressSpace space, DBTraceSpaceEntry ent, DBTraceThread thread) + AddressSpace space, DBTraceSpaceEntry ent, TraceThread thread) throws VersionException, IOException { super(manager, dbh, space, ent); this.thread = thread; @@ -38,7 +38,7 @@ public class DBTraceReferenceRegisterSpace extends DBTraceReferenceSpace } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return thread; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceSpace.java index d382d2d891..26d958c4b7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceReferenceSpace.java @@ -39,11 +39,11 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Abstract import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.AbstractDBTraceSpaceBasedManager.DBTraceSpaceEntry; import ghidra.trace.database.space.DBTraceSpaceBased; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace.TraceReferenceChangeType; import ghidra.trace.model.Trace.TraceSymbolChangeType; import ghidra.trace.model.symbol.TraceReference; import ghidra.trace.model.symbol.TraceReferenceSpace; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; import ghidra.util.database.*; @@ -389,7 +389,7 @@ public class DBTraceReferenceSpace implements DBTraceSpaceBased, TraceReferenceS } @Override - public DBTraceThread getThread() { + public TraceThread getThread() { return null; } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolManager.java index 2f8f2532d3..1f585efed0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolManager.java @@ -36,12 +36,12 @@ import ghidra.trace.database.map.*; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.DBTraceSpaceKey; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.Trace; import ghidra.trace.model.Trace.TraceFunctionTagChangeType; import ghidra.trace.model.Trace.TraceSymbolChangeType; import ghidra.trace.model.symbol.*; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; import ghidra.util.database.*; @@ -661,7 +661,7 @@ public class DBTraceSymbolManager implements TraceSymbolManager, DBTraceManager // DataTypes of Function returns, params, locals, globalRegs } - protected void assertValidThreadAddress(DBTraceThread thread, Address address) { + protected void assertValidThreadAddress(TraceThread thread, Address address) { if (thread != null && address.isMemoryAddress()) { throw new IllegalArgumentException( "Memory addresses cannot be associated with a thread"); @@ -886,7 +886,7 @@ public class DBTraceSymbolManager implements TraceSymbolManager, DBTraceManager protected boolean doDeleteSymbol(AbstractDBTraceSymbol symbol) { byte typeID = symbol.getSymbolType().getID(); - DBTraceThread thread = symbol.getThread(); + TraceThread thread = symbol.getThread(); AbstractDBTraceSymbol deleted = symbolViews.get(typeID).store.deleteKey(symbol.getKey()); if (deleted == null) { return false; @@ -901,21 +901,21 @@ public class DBTraceSymbolManager implements TraceSymbolManager, DBTraceManager return true; } - protected void putID(Range lifespan, DBTraceThread thread, Address address, long id) { + protected void putID(Range lifespan, TraceThread thread, Address address, long id) { idMap.get(DBTraceSpaceKey.create(address.getAddressSpace(), thread, 0), true) .put(address, lifespan, id); // TODO: Add to ancestors' too? // NOTE: Might be hard to remove because of overlaps } - protected void putID(Range lifespan, DBTraceThread thread, AddressRange rng, long id) { + protected void putID(Range lifespan, TraceThread thread, AddressRange rng, long id) { idMap.get(DBTraceSpaceKey.create(rng.getAddressSpace(), thread, 0), true) .put(rng, lifespan, id); // TODO: Add to ancestors' too? // NOTE: Might be hard to remove because of overlaps } - protected void delID(DBTraceThread thread, AddressSpace addressSpace, long id) { + protected void delID(TraceThread thread, AddressSpace addressSpace, long id) { DBTraceAddressSnapRangePropertyMapSpace space = idMap.get(DBTraceSpaceKey.create(addressSpace, thread, 0), false); if (space == null) { @@ -929,7 +929,7 @@ public class DBTraceSymbolManager implements TraceSymbolManager, DBTraceManager } protected void assertNotDuplicate(AbstractDBTraceSymbol exclude, Range lifespan, - DBTraceThread thread, Address address, String name, DBTraceNamespaceSymbol parent) + TraceThread thread, Address address, String name, DBTraceNamespaceSymbol parent) throws DuplicateNameException { if (address.isMemoryAddress()) { for (AbstractDBTraceSymbol duplicate : labelsAndFunctions.getIntersecting(lifespan, diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolMultipleTypesWithLocationView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolMultipleTypesWithLocationView.java index 1c6106534a..916a8c8d71 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolMultipleTypesWithLocationView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/symbol/DBTraceSymbolMultipleTypesWithLocationView.java @@ -28,7 +28,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.space.DBTraceSpaceKey; import ghidra.trace.database.symbol.DBTraceSymbolManager.DBTraceSymbolIDEntry; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.symbol.TraceNamespaceSymbol; import ghidra.trace.model.symbol.TraceSymbolWithLocationView; import ghidra.trace.model.thread.TraceThread; @@ -80,11 +79,10 @@ public class DBTraceSymbolMultipleTypesWithLocationView space = - manager.idMap.get(DBTraceSpaceKey.create(range.getAddressSpace(), dbThread, 0), + manager.idMap.get(DBTraceSpaceKey.create(range.getAddressSpace(), thread, 0), false); if (space == null) { return Collections.emptyList(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java new file mode 100644 index 0000000000..937648d0d8 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObject.java @@ -0,0 +1,893 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.function.Function; +import java.util.stream.*; + +import org.apache.commons.collections4.IteratorUtils; + +import com.google.common.collect.Iterators; +import com.google.common.collect.Range; + +import db.DBRecord; +import db.StringField; +import ghidra.dbg.target.TargetBreakpointLocation; +import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.dbg.util.*; +import ghidra.lifecycle.Experimental; +import ghidra.program.model.address.*; +import ghidra.trace.database.DBTrace; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointLocation; +import ghidra.trace.database.breakpoint.DBTraceObjectBreakpointSpec; +import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapSpace; +import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; +import ghidra.trace.database.memory.DBTraceObjectMemoryRegion; +import ghidra.trace.database.memory.DBTraceObjectRegister; +import ghidra.trace.database.module.*; +import ghidra.trace.database.stack.DBTraceObjectStack; +import ghidra.trace.database.stack.DBTraceObjectStackFrame; +import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple; +import ghidra.trace.database.target.InternalTraceObjectValue.ValueLifespanSetter; +import ghidra.trace.database.target.LifespanCorrector.Direction; +import ghidra.trace.database.target.LifespanCorrector.Operation; +import ghidra.trace.database.thread.DBTraceObjectThread; +import ghidra.trace.model.Trace.TraceObjectChangeType; +import ghidra.trace.model.breakpoint.TraceObjectBreakpointLocation; +import ghidra.trace.model.breakpoint.TraceObjectBreakpointSpec; +import ghidra.trace.model.memory.TraceObjectMemoryRegion; +import ghidra.trace.model.memory.TraceObjectRegister; +import ghidra.trace.model.modules.TraceObjectModule; +import ghidra.trace.model.stack.TraceObjectStack; +import ghidra.trace.model.stack.TraceObjectStackFrame; +import ghidra.trace.model.target.*; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.util.LockHold; +import ghidra.util.Msg; +import ghidra.util.database.*; +import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; +import ghidra.util.database.annot.*; +import ghidra.util.database.spatial.rect.Rectangle2DDirection; + +@DBAnnotatedObjectInfo(version = 0) +public class DBTraceObject extends DBAnnotatedObject implements TraceObject { + protected static final String TABLE_NAME = "Objects"; + + protected static // + Map.Entry, Function> safeEntry( + Class cls, Function ctor) { + return Map.entry(cls, ctor); + } + + protected static final Map, // + Function> CTORS = Map.ofEntries( + safeEntry(TraceObjectThread.class, DBTraceObjectThread::new), + safeEntry(TraceObjectMemoryRegion.class, DBTraceObjectMemoryRegion::new), + safeEntry(TraceObjectModule.class, DBTraceObjectModule::new), + safeEntry(TraceObjectSection.class, DBTraceObjectSection::new), + safeEntry(TraceObjectBreakpointSpec.class, DBTraceObjectBreakpointSpec::new), + safeEntry(TraceObjectBreakpointLocation.class, + DBTraceObjectBreakpointLocation::new), + safeEntry(TraceObjectStack.class, DBTraceObjectStack::new), + safeEntry(TraceObjectStackFrame.class, DBTraceObjectStackFrame::new), + safeEntry(TraceObjectRegister.class, DBTraceObjectRegister::new)); + + protected static final class ObjectPathDBFieldCodec + extends AbstractDBFieldCodec { + + public ObjectPathDBFieldCodec(Class objectType, Field field, + int column) { + super(TraceObjectKeyPath.class, objectType, StringField.class, field, column); + } + + protected String encode(TraceObjectKeyPath value) { + return value == null ? null : value.toString(); + } + + protected TraceObjectKeyPath decode(String path) { + return TraceObjectKeyPath.parse(path); + } + + @Override + public void store(TraceObjectKeyPath value, StringField f) { + f.setString(encode(value)); + } + + @Override + protected void doStore(DBAnnotatedObject obj, DBRecord record) + throws IllegalArgumentException, IllegalAccessException { + record.setString(column, encode(getValue(obj))); + } + + @Override + protected void doLoad(DBAnnotatedObject obj, DBRecord record) + throws IllegalArgumentException, IllegalAccessException { + setValue(obj, decode(record.getString(column))); + } + } + + // Canonical path + static final String PATH_COLUMN_NAME = "Path"; + static final String MIN_SNAP_COLUMN_NAME = "MinSnap"; + static final String MAX_SNAP_COLUMN_NAME = "MaxSnap"; + + @DBAnnotatedColumn(PATH_COLUMN_NAME) + static DBObjectColumn PATH_COLUMN; + @DBAnnotatedColumn(MIN_SNAP_COLUMN_NAME) + static DBObjectColumn MIN_SNAP_COLUMN; + @DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME) + static DBObjectColumn MAX_SNAP_COLUMN; + + @DBAnnotatedField( + column = PATH_COLUMN_NAME, + codec = ObjectPathDBFieldCodec.class, + indexed = true) + private TraceObjectKeyPath path; + @DBAnnotatedField(column = MIN_SNAP_COLUMN_NAME) + private long minSnap; + @DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME) + private long maxSnap; + + protected final DBTraceObjectManager manager; + + private Range lifespan; + + private Map, TraceObjectInterface> ifaces; + + public DBTraceObject(DBTraceObjectManager manager, DBCachedObjectStore store, + DBRecord record) { + super(store, record); + this.manager = manager; + } + + @Override + protected void fresh(boolean created) throws IOException { + if (created) { + return; + } + if (path != null) { + freshIfaces(); + } + lifespan = DBTraceUtils.toRange(minSnap, maxSnap); + } + + @Override + public String toString() { + return "TraceObject: " + getCanonicalPath(); + } + + protected void freshIfaces() { + if (ifaces != null) { + return; + } + Set> targetIfaces = getTargetSchema().getInterfaces(); + ifaces = CTORS.entrySet() + .stream() + .filter( + e -> targetIfaces.contains(TraceObjectInterfaceUtils.toTargetIf(e.getKey()))) + .collect( + Collectors.toUnmodifiableMap(e -> e.getKey(), e -> e.getValue().apply(this))); + } + + protected void set(TraceObjectKeyPath path, Range lifespan) { + this.path = path; + this.lifespan = lifespan; + this.doSetLifespan(lifespan); + update(PATH_COLUMN); + + freshIfaces(); + } + + protected void doSetLifespan(Range lifespan) { + this.minSnap = DBTraceUtils.lowerEndpoint(lifespan); + this.maxSnap = DBTraceUtils.upperEndpoint(lifespan); + update(MIN_SNAP_COLUMN, MAX_SNAP_COLUMN); + this.lifespan = DBTraceUtils.toRange(minSnap, maxSnap); + } + + @Override + public DBTrace getTrace() { + return manager.trace; + } + + public DBTraceObjectManager getManager() { + return manager; + } + + @Override + public DBTraceObject getRoot() { + return manager.getRootObject(); + } + + @Override + public TraceObjectKeyPath getCanonicalPath() { + try (LockHold hold = manager.trace.lockRead()) { + return path; + } + } + + @Override + public void insert(ConflictResolution resolution) { + try (LockHold hold = manager.trace.lockWrite()) { + for (InternalTraceObjectValue val : getParents()) { + if (val.isCanonical() && DBTraceUtils.intersect(val.getLifespan(), lifespan)) { + return; + } + } + TraceObjectKeyPath parentPath = path.parent(); + for (DBTraceObject parent : manager.getObjectsByCanonicalPath(parentPath)) { + if (DBTraceUtils.intersect(parent.getLifespan(), lifespan)) { + parent.setValue(lifespan, path.key(), this, resolution); + return; + } + } + DBTraceObject parent = manager.createObject(parentPath, lifespan); + parent.setValue(lifespan, path.key(), this, resolution); + parent.insert(resolution); + } + } + + @Override + public boolean isRoot() { + try (LockHold hold = manager.trace.lockRead()) { + return path.isRoot(); + } + } + + protected Stream doGetAllPaths(Range span, + DBTraceObjectValPath post) { + if (isRoot()) { + return Stream.of(post); + } + return getParents().stream() + .filter(e -> !post.contains(e)) + .flatMap(e -> e.doGetAllPaths(span, post)); + } + + @Override + public Stream getAllPaths(Range span) { + try (LockHold hold = manager.trace.lockRead()) { + return doGetAllPaths(span, DBTraceObjectValPath.of()); + } + } + + @Override + public void setLifespan(Range lifespan) { + // TODO: Could derive fixed attributes from schema and set their lifespans, too.... + try (LockHold hold = manager.trace.lockWrite()) { + Range oldLifespan = getLifespan(); + doSetLifespan(lifespan); + emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.LIFESPAN_CHANGED, null, this, + oldLifespan, lifespan)); + } + } + + @Experimental + public void correctLifespans(Direction direction, Operation operation, + ConflictResolution resolution) { + new LifespanCorrector(direction, operation, resolution).correctLifespans(this); + } + + @Override + public Range getLifespan() { + try (LockHold hold = manager.trace.lockRead()) { + return lifespan; + } + } + + @Override + public void setMinSnap(long minSnap) { + setLifespan(DBTraceUtils.toRange(minSnap, maxSnap)); + } + + @Override + public long getMinSnap() { + try (LockHold hold = manager.trace.lockRead()) { + return minSnap; + } + } + + @Override + public void setMaxSnap(long maxSnap) { + setLifespan(DBTraceUtils.toRange(minSnap, maxSnap)); + } + + @Override + public long getMaxSnap() { + try (LockHold hold = manager.trace.lockRead()) { + return maxSnap; + } + } + + @Override + public Collection> getInterfaces() { + Set> targetIfs = getTargetSchema().getInterfaces(); + return CTORS.keySet() + .stream() + .filter(iface -> targetIfs.contains(TraceObjectInterfaceUtils.toTargetIf(iface))) + .collect(Collectors.toSet()); + } + + @Override + public I queryInterface(Class ifCls) { + return ifCls.cast(ifaces.get(ifCls)); + } + + @Override + public Collection getParents() { + return manager.valuesByChild.get(this); + } + + protected void collectNonRangedValues(Collection result) { + for (DBTraceObjectValue val : manager.valuesByTriple + .tail(new PrimaryTriple(this, "", Long.MIN_VALUE), true) + .values()) { + if (val.getParent() != this) { + break; + } + result.add(val); + } + } + + @Override + public Collection getValues() { + try (LockHold hold = manager.trace.lockRead()) { + List result = new ArrayList<>(); + collectNonRangedValues(result); + + for (DBTraceAddressSnapRangePropertyMapSpace space : manager.rangeValueMap + .getActiveMemorySpaces()) { + for (DBTraceObjectAddressRangeValue val : space.values()) { + if (val.getParent() != this) { + continue; + } + result.add(val); + } + } + + return result; + } + } + + protected Collection doGetElements() { + List result = new ArrayList<>(); + for (DBTraceObjectValue val : manager.valuesByTriple + .sub(new PrimaryTriple(this, "[", Long.MIN_VALUE), true, + new PrimaryTriple(this, "\\", Long.MIN_VALUE), false) + .values()) { + result.add(val); + } + return result; + } + + @Override + public Collection getElements() { + try (LockHold hold = manager.trace.lockRead()) { + return doGetElements(); + } + } + + protected Collection doGetAttributes() { + List result = new ArrayList<>(); + for (DBTraceObjectValue val : manager.valuesByTriple + .sub(new PrimaryTriple(this, "", Long.MIN_VALUE), true, + new PrimaryTriple(this, "[", Long.MIN_VALUE), false) + .values()) { + result.add(val); + } + for (DBTraceObjectValue val : manager.valuesByTriple + .tail(new PrimaryTriple(this, "\\", Long.MIN_VALUE), true) + .values()) { + if (val.getParent() != this) { + break; + } + result.add(val); + } + return result; + } + + @Override + public Collection getAttributes() { + try (LockHold hold = manager.trace.lockRead()) { + return doGetAttributes(); + } + } + + protected void doCheckConflicts(Range span, String key, Object value) { + for (InternalTraceObjectValue val : doGetValues(span, key)) { + if (!Objects.equals(value, val.getValue())) { + throw new DuplicateKeyException(key); + } + } + } + + protected Collection doGetValues(Range span, + String key) { + return doGetValues(DBTraceUtils.lowerEndpoint(span), DBTraceUtils.upperEndpoint(span), key); + } + + protected Collection doGetValues(long lower, long upper, + String key) { + try (LockHold hold = manager.trace.lockRead()) { + // Collect triplet-indexed values + Set result = new LinkedHashSet<>(); + PrimaryTriple min = new PrimaryTriple(this, key, lower); + PrimaryTriple max = new PrimaryTriple(this, key, upper); + DBTraceObjectValue floor = manager.valuesByTriple.floorValue(min); + if (floor != null && floor.getParent() == this && key.equals(floor.getEntryKey()) && + floor.getLifespan().contains(lower)) { + result.add(floor); + } + for (DBTraceObjectValue val : manager.valuesByTriple.sub(min, true, max, true) + .values()) { + result.add(val); + } + + // Collect R*-Tree-indexed values + for (DBTraceAddressSnapRangePropertyMapSpace space : manager.rangeValueMap + .getActiveMemorySpaces()) { + AddressSpace as = space.getAddressSpace(); + + for (DBTraceObjectAddressRangeValue val : manager.rangeValueMap + .reduce(TraceAddressSnapRangeQuery + .intersecting(as.getMinAddress(), as.getMaxAddress(), lower, upper)) + .values()) { + if (val.getParent() != this) { + continue; + } + if (!key.equals(val.getEntryKey())) { + continue; + } + result.add(val); + } + } + + return result.stream() + .sorted(Comparator.comparing(v -> v.getMinSnap())) + .collect(Collectors.toList()); + } + } + + protected DBTraceObjectValue doGetNonRangedValue(long snap, String key) { + DBTraceObjectValue floor = + manager.valuesByTriple.floorValue(new PrimaryTriple(this, key, snap)); + if (floor == null || floor.getParent() != this || !key.equals(floor.getEntryKey()) || + !floor.getLifespan().contains(snap)) { + return null; + } + return floor; + } + + protected Stream doGetNonRangedValues(Range span, String key, + boolean forward) { + DBCachedObjectIndex sub = manager.valuesByTriple.sub( + new PrimaryTriple(this, key, DBTraceUtils.lowerEndpoint(span)), true, + new PrimaryTriple(this, key, DBTraceUtils.upperEndpoint(span)), true); + Spliterator spliterator = (forward ? sub : sub.descending()) + .values() + .spliterator(); + return StreamSupport.stream(spliterator, false); + } + + protected DBTraceObjectAddressRangeValue doGetRangedValue(long snap, String key) { + for (DBTraceAddressSnapRangePropertyMapSpace space : manager.rangeValueMap + .getActiveMemorySpaces()) { + AddressSpace as = space.getAddressSpace(); + for (DBTraceObjectAddressRangeValue val : space + .reduce(TraceAddressSnapRangeQuery.atSnap(snap, as)) + .values()) { + if (val.getParent() == this && key.equals(val.getEntryKey())) { + return val; + } + } + } + return null; + } + + protected Stream doGetRangedValues(Range span, + String key, boolean forward) { + Rectangle2DDirection dir = forward + ? Rectangle2DDirection.BOTTOMMOST + : Rectangle2DDirection.TOPMOST; + List> iterators = manager.rangeValueMap + .getActiveMemorySpaces() + .stream() + .map(s -> IteratorUtils.filteredIterator(s + .reduce(TraceAddressSnapRangeQuery.intersecting(span, s.getAddressSpace()) + .starting(dir)) + .orderedValues() + .iterator(), + v -> key.equals(v.getEntryKey()))) + .collect(Collectors.toList()); + Comparator order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder(); + Comparator comparator = + Comparator.comparing(v -> v.getMinSnap(), order); + Iterator merged = + Iterators.mergeSorted(iterators, comparator); + return StreamSupport + .stream(Spliterators.spliteratorUnknownSize(merged, Spliterator.ORDERED), false) + .filter(v -> key.equals(v.getEntryKey())); + } + + @Override + public InternalTraceObjectValue getValue(long snap, String key) { + try (LockHold hold = manager.trace.lockRead()) { + DBTraceObjectValue nrVal = doGetNonRangedValue(snap, key); + if (nrVal != null) { + return nrVal; + } + return doGetRangedValue(snap, key); + } + } + + protected Stream doGetOrderedValues(Range span, String key, + boolean forward) { + Stream nrVals = doGetNonRangedValues(span, key, forward); + Stream rVals = doGetRangedValues(span, key, forward); + Comparator order = forward ? Comparator.naturalOrder() : Comparator.reverseOrder(); + Comparator comparator = + Comparator.comparing(v -> v.getMinSnap(), order); + Iterator merged = + Iterators.mergeSorted(Arrays.asList(nrVals.iterator(), rVals.iterator()), comparator); + return StreamSupport + .stream(Spliterators.spliteratorUnknownSize(merged, Spliterator.ORDERED), false); + } + + @Override + public Stream getOrderedValues(Range span, String key, + boolean forward) { + try (LockHold hold = manager.trace.lockRead()) { + return doGetOrderedValues(span, key, forward); + } + } + + @Override + public InternalTraceObjectValue getElement(long snap, String index) { + return getValue(snap, PathUtils.makeKey(index)); + } + + @Override + public InternalTraceObjectValue getElement(long snap, long index) { + return getElement(snap, PathUtils.makeIndex(index)); + } + + @Override + public TraceObjectValue getAttribute(long snap, String name) { + if (!PathUtils.isName(name)) { + throw new IllegalArgumentException("name cannot be an index"); + } + return getValue(snap, name); + } + + protected Stream doGetAncestors(Range span, + DBTraceObjectValPath post, PathPredicates predicates) { + if (predicates.matches(getCanonicalPath().getKeyList())) { + return Stream.of(post); + } + if (isRoot()) { + return Stream.empty(); + } + return getParents().stream() + .filter(e -> !post.contains(e)) + .flatMap(e -> e.doGetAncestors(span, post, predicates)); + } + + @Override + public Stream getAncestors( + Range span, PathPredicates rootPredicates) { + try (LockHold hold = manager.trace.lockRead()) { + return doGetAncestors(span, DBTraceObjectValPath.of(), rootPredicates); + } + } + + protected Stream doGetSuccessors( + Range span, DBTraceObjectValPath pre, PathPredicates predicates) { + Set nextKeys = predicates.getNextKeys(pre.getKeyList()); + if (nextKeys.isEmpty()) { + return Stream.empty(); + } + + Stream attrStream; + if (nextKeys.contains("")) { + attrStream = doGetAttributes().stream() + .filter(v -> DBTraceUtils.intersect(span, v.getLifespan())); + } + else { + attrStream = Stream.empty(); + } + + Stream elemStream; + if (nextKeys.contains("[]")) { + elemStream = doGetElements().stream() + .filter(v -> DBTraceUtils.intersect(span, v.getLifespan())); + } + else { + elemStream = Stream.empty(); + } + + Stream restStream = nextKeys.stream() + .filter(k -> !"".equals(k) && !"[]".equals(k)) + .flatMap(k -> doGetValues(span, k).stream()); + + return Stream.concat(Stream.concat(attrStream, elemStream), restStream) + .flatMap(v -> v.doGetSuccessors(span, pre, predicates)); + } + + @Override + public Stream getSuccessors( + Range span, PathPredicates relativePredicates) { + try (LockHold hold = manager.trace.lockRead()) { + return doGetSuccessors(span, DBTraceObjectValPath.of(), relativePredicates); + } + } + + protected Stream doGetOrderedSuccessors(Range span, + DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) { + Set nextKeys = predicates.getNextKeys(pre.getKeyList()); + if (nextKeys.isEmpty()) { + return null; + } + if (nextKeys.size() != 1) { + throw new IllegalArgumentException("predicates must be a singleton"); + } + String next = nextKeys.iterator().next(); + if (PathPattern.isWildcard(next)) { + throw new IllegalArgumentException("predicates must be a singleton"); + } + return doGetOrderedValues(span, next, forward) + .flatMap(v -> v.doGetOrderedSuccessors(span, pre, predicates, forward)); + } + + @Override + public Stream getOrderedSuccessors(Range span, + TraceObjectKeyPath relativePath, boolean forward) { + try (LockHold hold = manager.trace.lockRead()) { + return doGetOrderedSuccessors(span, DBTraceObjectValPath.of(), + new PathPattern(relativePath.getKeyList()), forward); + } + } + + protected InternalTraceObjectValue doCreateValue(Range lifespan, String key, + Object value) { + return manager.doCreateValue(lifespan, this, key, value); + } + + // HACK: Because breakpoint uses address,length instead of range. FIXME! + protected void applyBreakpointRangeHack(Range lifespan, String key, Object value, + ConflictResolution resolution) { + /** + * NOTE: This should only be happening in Target/TraceBreakpointLocation, but I suppose + * anything using this scheme should be hacked. + */ + Address address; + int length; + if (key == TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME && + value instanceof Address) { + address = (Address) value; + Object lengthObj = getValue(DBTraceUtils.lowerEndpoint(lifespan), + TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME); + if (!(lengthObj instanceof Integer)) { + return; + } + length = (Integer) lengthObj; + } + else if (key == TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME && + value instanceof Integer) { + length = (Integer) value; + Object addressObj = getValue(DBTraceUtils.lowerEndpoint(lifespan), + TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME); + if (!(addressObj instanceof Address)) { + return; + } + address = (Address) addressObj; + } + else { + return; + } + try { + setValue(lifespan, TraceObjectBreakpointLocation.KEY_RANGE, + new AddressRangeImpl(address, length), resolution); + } + catch (AddressOverflowException e) { + Msg.warn(this, "Could not set range: " + e); + } + } + + @Override + public InternalTraceObjectValue setValue(Range lifespan, String key, Object value, + ConflictResolution resolution) { + try (LockHold hold = manager.trace.lockWrite()) { + if (isDeleted()) { + throw new IllegalStateException("Cannot set value on deleted object."); + } + InternalTraceObjectValue oldEntry = getValue(DBTraceUtils.lowerEndpoint(lifespan), key); + Object oldVal = null; + if (oldEntry != null && oldEntry.getLifespan().encloses(lifespan)) { + oldVal = oldEntry.getValue(); + } + if (resolution == ConflictResolution.DENY) { + doCheckConflicts(lifespan, key, value); + } + InternalTraceObjectValue result = new ValueLifespanSetter(lifespan, value) { + @Override + protected Iterable getIntersecting(Long lower, + Long upper) { + return Collections.unmodifiableCollection(doGetValues(lower, upper, key)); + } + + @Override + protected InternalTraceObjectValue create(Range range, Object value) { + return doCreateValue(range, key, value); + } + }.set(lifespan, value); + if (result == null && oldEntry == null) { + return null; + } + emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.VALUE_CHANGED, + null, result != null ? result : oldEntry, oldVal, value)); + + // NB. It will cause another event. good. + applyBreakpointRangeHack(lifespan, key, value, resolution); + return result; + } + } + + @Override + public TraceObjectValue setValue(Range lifespan, String key, Object value) { + return setValue(lifespan, key, value, ConflictResolution.TRUNCATE); + } + + @Override + public TraceObjectValue setAttribute(Range lifespan, String name, Object value) { + if (!PathUtils.isName(name)) { + throw new IllegalArgumentException("Attribute name must not be an index"); + } + return setValue(lifespan, name, value); + } + + @Override + public TraceObjectValue setElement(Range lifespan, String index, Object value) { + return setValue(lifespan, PathUtils.makeKey(index), value); + } + + @Override + public TraceObjectValue setElement(Range lifespan, long index, Object value) { + return setElement(lifespan, PathUtils.makeIndex(index), value); + } + + @Override + public TargetObjectSchema getTargetSchema() { + return manager.rootSchema.getSuccessorSchema(path.getKeyList()); + } + + @Override + public Stream queryAncestorsInterface(Range span, + Class ifClass) { + Class targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass); + // This is a sort of meet-in-the-middle. The type search must originate from the root + PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false); + return getAncestors(span, matcher).map(p -> p.getFirstParent(this).queryInterface(ifClass)); + } + + @Override + public Stream queryCanonicalAncestorsInterface( + Range span, Class ifClass) { + Class targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass); + // This is a sort of meet-in-the-middle. The type search must originate from the root + PathMatcher matcher = getManager().getRootSchema().searchFor(targetIf, false); + List parentPath = getCanonicalPath().getKeyList(); + if (!matcher.ancestorMatches(parentPath, false)) { + return Stream.of(); + } + for (; !parentPath.isEmpty(); parentPath = PathUtils.parent(parentPath)) { + if (matcher.matches(parentPath)) { + return manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of(parentPath)) + .stream() + .filter(o -> DBTraceUtils.intersect(span, o.getLifespan())) + .map(o -> o.queryInterface(ifClass)) + .filter(i -> i != null); + } + } + return Stream.of(); + } + + @Override + public Stream querySuccessorsInterface(Range span, + Class ifClass) { + Class targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass); + PathMatcher matcher = getTargetSchema().searchFor(targetIf, true); + return getSuccessors(span, matcher).map(p -> p.getLastChild(this).queryInterface(ifClass)) + .filter(i -> i != null); // because GP-1301 + } + + protected void doDelete() { + manager.doDeleteObject(this); + } + + protected void doDeleteReferringValues() { + for (InternalTraceObjectValue child : getValues()) { + child.doDelete(); + } + for (DBTraceObjectValue parent : getParents()) { + parent.doDelete(); + } + } + + protected void doDeleteSuccessors() { + List children = new ArrayList<>(); + collectNonRangedValues(children); + for (DBTraceObjectValue child : children) { + child.doDeleteSuccessors(); + } + } + + @Override + public void delete() { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + doDeleteReferringValues(); + doDelete(); + } + } + + protected void doDeleteTree() { + doDeleteSuccessors(); + doDeleteReferringValues(); + doDelete(); + } + + @Override + public void deleteTree() { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + doDeleteTree(); + } + } + + @Override + public DBTraceObject truncateOrDelete(Range span) { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + List> removed = DBTraceUtils.subtract(lifespan, span); + if (removed.isEmpty()) { + doDelete(); + return null; + } + if (removed.size() == 2) { + throw new IllegalArgumentException("Cannot create a gap in an object's lifespan"); + } + doSetLifespan(removed.get(0)); + return this; + } + } + + protected void emitEvents(TraceChangeRecord rec) { + manager.trace.setChanged(rec); + for (TraceObjectInterface iface : ifaces.values()) { + DBTraceObjectInterface dbIface = (DBTraceObjectInterface) iface; + TraceChangeRecord evt = dbIface.translateEvent(rec); + if (evt != null) { + manager.trace.setChanged(evt); + } + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java new file mode 100644 index 0000000000..ca32ebd6e2 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectAddressRangeValue.java @@ -0,0 +1,204 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import java.util.stream.Stream; + +import com.google.common.collect.Range; + +import db.DBRecord; +import ghidra.dbg.util.PathPredicates; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree; +import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; +import ghidra.trace.database.target.DBTraceObjectValue.DBTraceObjectDBFieldCodec; +import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.util.TraceAddressSpace; +import ghidra.util.LockHold; +import ghidra.util.database.DBCachedObjectStore; +import ghidra.util.database.DBObjectColumn; +import ghidra.util.database.annot.*; + +@DBAnnotatedObjectInfo(version = 0) +public class DBTraceObjectAddressRangeValue + extends AbstractDBTraceAddressSnapRangePropertyMapData + implements InternalTraceObjectValue { + public static final String TABLE_NAME = "ObjectRangeValue"; + + static final String PARENT_COLUMN_NAME = "ValueParent"; + static final String KEY_COLUMN_NAME = "ValueKey"; + static final String TYPE_COLUMN_NAME = "IsAddress"; + + @DBAnnotatedColumn(PARENT_COLUMN_NAME) + static DBObjectColumn PARENT_COLUMN; + @DBAnnotatedColumn(KEY_COLUMN_NAME) + static DBObjectColumn KEY_COLUMN; + @DBAnnotatedColumn(TYPE_COLUMN_NAME) + static DBObjectColumn TYPE_COLUMN; + + @DBAnnotatedField( + column = PARENT_COLUMN_NAME, + indexed = true, + codec = DBTraceObjectDBFieldCodec.class) + private DBTraceObject parent; + @DBAnnotatedField(column = KEY_COLUMN_NAME) + private String entryKey; + @DBAnnotatedField(column = TYPE_COLUMN_NAME) + private boolean isAddress; + + protected final DBTraceObjectManager manager; + + public DBTraceObjectAddressRangeValue(DBTraceObjectManager manager, + DBTraceAddressSnapRangePropertyMapTree tree, + DBCachedObjectStore store, DBRecord record) { + super(tree, store, record); + this.manager = manager; + } + + @Override + protected void setRecordValue(DBTraceObjectAddressRangeValue value) { + // Nothing to do. I am the value + assert value == null; + } + + public TraceAddressSpace getTraceAddressSpace() { + return tree.getMapSpace(); + } + + @Override + protected DBTraceObjectAddressRangeValue getRecordValue() { + return this; + } + + void set(DBTraceObject parent, String key, boolean isAddress) { + this.parent = parent; + this.entryKey = key; + this.isAddress = isAddress; + update(PARENT_COLUMN, KEY_COLUMN, TYPE_COLUMN); + } + + @Override + public Trace getTrace() { + return manager.trace; + } + + @Override + public DBTraceObjectManager getManager() { + return manager; + } + + @Override + public DBTraceObject getParent() { + return parent; + } + + @Override + public String getEntryKey() { + return entryKey; + } + + @Override + public Object getValue() { + if (isAddress) { + return getRange().getMinAddress(); + } + return getRange(); + } + + @Override + public DBTraceObject getChild() { + throw new ClassCastException(); + } + + @Override + public boolean isCanonical() { + return false; + } + + @Override + public void doSetLifespan(Range lifespan) { + super.doSetLifespan(lifespan); + } + + @Override + public void setMinSnap(long minSnap) { + try (LockHold hold = manager.trace.lockWrite()) { + setLifespan(DBTraceUtils.toRange(minSnap, getY2())); + } + } + + @Override + public long getMinSnap() { + try (LockHold hold = manager.trace.lockRead()) { + return getY1(); + } + } + + @Override + public void setMaxSnap(long maxSnap) { + try (LockHold hold = manager.trace.lockWrite()) { + setLifespan(DBTraceUtils.toRange(getY1(), maxSnap)); + } + } + + @Override + public long getMaxSnap() { + try (LockHold hold = manager.trace.lockRead()) { + return getY2(); + } + } + + @Override + public Stream doGetSuccessors(Range span, + DBTraceObjectValPath pre, PathPredicates predicates) { + DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this); + if (predicates.matches(path.getKeyList())) { + return Stream.of(path); + } + return Stream.empty(); + } + + @Override + public Stream doGetOrderedSuccessors(Range span, + DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) { + return doGetSuccessors(span, pre, predicates); + } + + @Override + public void doDelete() { + manager.rangeValueMap.deleteData(this); + } + + @Override + public void delete() { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + doDelete(); + } + } + + @Override + public void deleteTree() { + delete(); + } + + @Override + public TraceObjectValue truncateOrDelete(Range span) { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + return doTruncateOrDelete(span); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java new file mode 100644 index 0000000000..4bc4506daa --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectInterface.java @@ -0,0 +1,141 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import com.google.common.collect.Range; + +import ghidra.trace.model.Trace.TraceObjectChangeType; +import ghidra.trace.model.TraceUniqueObject; +import ghidra.trace.model.target.*; +import ghidra.trace.util.*; +import ghidra.util.database.ObjectKey; + +public interface DBTraceObjectInterface extends TraceObjectInterface, TraceUniqueObject { + + abstract class Translator { + private final String spaceValueKey; + private final DBTraceObject object; + private final T iface; + + public Translator(String spaceValueKey, DBTraceObject object, T iface) { + this.spaceValueKey = spaceValueKey; + this.object = object; + this.iface = iface; + } + + protected abstract TraceChangeType getAddedType(); + + protected abstract TraceChangeType> getLifespanChangedType(); + + protected abstract TraceChangeType getChangedType(); + + protected abstract boolean appliesToKey(String key); + + protected abstract TraceChangeType getDeletedType(); + + public TraceChangeRecord translate(TraceChangeRecord rec) { + TraceAddressSpace space = spaceValueKey == null ? null + : spaceForValue(object, object.getMinSnap(), spaceValueKey); + if (rec.getEventType() == TraceObjectChangeType.CREATED.getType()) { + TraceChangeType type = getAddedType(); + if (type == null) { + return null; + } + assert rec.getAffectedObject() == object; + return new TraceChangeRecord<>(type, space, iface, null, + null); + } + if (rec.getEventType() == TraceObjectChangeType.LIFESPAN_CHANGED.getType()) { + if (object.isDeleted()) { + return null; + } + TraceChangeType> type = getLifespanChangedType(); + if (type == null) { + return null; + } + assert rec.getAffectedObject() == object; + TraceChangeRecord> cast = + TraceObjectChangeType.LIFESPAN_CHANGED.cast(rec); + return new TraceChangeRecord<>(type, space, iface, + cast.getOldValue(), cast.getNewValue()); + } + if (rec.getEventType() == TraceObjectChangeType.VALUE_CHANGED.getType()) { + if (object.isDeleted()) { + return null; + } + TraceChangeType type = getChangedType(); + if (type == null) { + return null; + } + TraceChangeRecord cast = + TraceObjectChangeType.VALUE_CHANGED.cast(rec); + String key = cast.getAffectedObject().getEntryKey(); + if (!appliesToKey(key)) { + return null; + } + assert cast.getAffectedObject().getParent() == object; + return new TraceChangeRecord<>(type, space, iface, null, null); + } + if (rec.getEventType() == TraceObjectChangeType.DELETED.getType()) { + TraceChangeType type = getDeletedType(); + if (type == null) { + return null; + } + assert rec.getAffectedObject() == object; + return new TraceChangeRecord<>(type, space, iface, null, null); + } + return null; + } + } + + /** + * Translate an object event into the interface-specific event + * + *

+ * Both the object event and the interface-specific event, if applicable, will be emitted. If + * multiple events need to be emitted, then this method may emit them directly via its object's + * trace. If exactly one event needs to be emitted, then this method should return the + * translated record. If no translation applies, or if the translated event(s) were emitted + * directly, this method returns {@code null}. + * + * @param rec the object event + * @return the interface-specific event to emit, or {@code null} + */ + TraceChangeRecord translateEvent(TraceChangeRecord rec); + + static TraceAddressSpace spaceForValue(TraceObject object, long snap, String key) { + TraceObjectValue val = object.getAttribute(snap, key); + if (val instanceof DBTraceObjectAddressRangeValue) { + DBTraceObjectAddressRangeValue addrVal = (DBTraceObjectAddressRangeValue) val; + return addrVal.getTraceAddressSpace(); + } + return null; + } + + default TraceAddressSpace spaceForValue(long snap, String key) { + return spaceForValue(getObject(), snap, key); + } + + @Override + default ObjectKey getObjectKey() { + return getObject().getObjectKey(); + } + + @Override + default boolean isDeleted() { + return getObject().isDeleted(); + } +} 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 new file mode 100644 index 0000000000..ea97aac7a2 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectManager.java @@ -0,0 +1,652 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.*; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jdom.JDOMException; + +import com.google.common.collect.Range; + +import db.*; +import ghidra.dbg.target.TargetBreakpointSpec; +import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.schema.*; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.util.*; +import ghidra.lifecycle.Internal; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.trace.database.DBTrace; +import ghidra.trace.database.DBTraceManager; +import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMap; +import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; +import ghidra.trace.database.module.TraceObjectSection; +import ghidra.trace.database.target.DBTraceObjectValue.PrimaryTriple; +import ghidra.trace.database.thread.DBTraceObjectThread; +import ghidra.trace.database.thread.DBTraceThreadManager; +import ghidra.trace.model.ImmutableTraceAddressSnapRange; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceObjectChangeType; +import ghidra.trace.model.breakpoint.TraceBreakpointKind; +import ghidra.trace.model.breakpoint.TraceObjectBreakpointLocation; +import ghidra.trace.model.memory.*; +import ghidra.trace.model.modules.TraceObjectModule; +import ghidra.trace.model.stack.TraceObjectStack; +import ghidra.trace.model.stack.TraceObjectStackFrame; +import ghidra.trace.model.target.*; +import ghidra.trace.model.target.DuplicateKeyException; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.util.LockHold; +import ghidra.util.database.*; +import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; +import ghidra.util.database.DBCachedObjectStoreFactory.PrimitiveCodec; +import ghidra.util.database.annot.*; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.VersionException; +import ghidra.util.task.TaskMonitor; + +public class DBTraceObjectManager implements TraceObjectManager, DBTraceManager { + + public static class DBTraceObjectSchemaDBFieldCodec extends + AbstractDBFieldCodec { + public DBTraceObjectSchemaDBFieldCodec(Class objectType, + Field field, int column) { + super(SchemaContext.class, objectType, StringField.class, field, column); + } + + protected String encode(SchemaContext value) { + return value == null ? null : XmlSchemaContext.serialize(value); + } + + protected SchemaContext decode(String xml) { + try { + return xml == null ? null : XmlSchemaContext.deserialize(xml); + } + catch (JDOMException e) { + throw new IllegalArgumentException("Invalid XML-encoded schema context"); + } + } + + @Override + public void store(SchemaContext value, StringField f) { + f.setString(encode(value)); + } + + @Override + protected void doStore(DBTraceObjectSchemaEntry obj, DBRecord record) + throws IllegalAccessException { + record.setString(column, encode(getValue(obj))); + } + + @Override + protected void doLoad(DBTraceObjectSchemaEntry obj, DBRecord record) + throws IllegalAccessException { + setValue(obj, decode(record.getString(column))); + } + } + + @DBAnnotatedObjectInfo(version = 0) + protected static final class DBTraceObjectSchemaEntry extends DBAnnotatedObject { + public static final String TABLE_NAME = "ObjectSchema"; + + static final String CONTEXT_COLUMN_NAME = "Context"; + static final String SCHEMA_COLUMN_NAME = "Schema"; + + @DBAnnotatedColumn(CONTEXT_COLUMN_NAME) + static DBObjectColumn CONTEXT_COLUMN; + @DBAnnotatedColumn(SCHEMA_COLUMN_NAME) + static DBObjectColumn SCHEMA_COLUMN; + + @DBAnnotatedField( + column = CONTEXT_COLUMN_NAME, + codec = DBTraceObjectSchemaDBFieldCodec.class) + private SchemaContext context; + @DBAnnotatedField(column = SCHEMA_COLUMN_NAME) + private String schemaName; + + private TargetObjectSchema schema; + + public DBTraceObjectSchemaEntry(DBCachedObjectStore store, DBRecord record) { + super(store, record); + } + + @Override + protected void fresh(boolean created) throws IOException { + if (created) { + return; + } + schema = context.getSchema(new SchemaName(schemaName)); + } + + protected void set(TargetObjectSchema schema) { + context = schema.getContext(); + schemaName = schema.getName().toString(); + update(CONTEXT_COLUMN, SCHEMA_COLUMN); + } + } + + protected final ReadWriteLock lock; + protected final DBTrace trace; + + protected final DBCachedObjectStore schemaStore; + protected final DBCachedObjectStore objectStore; + protected final DBCachedObjectStore valueStore; + + protected final DBTraceAddressSnapRangePropertyMap rangeValueMap; + + protected final DBCachedObjectIndex objectsByPath; + protected final DBCachedObjectIndex valuesByTriple; + protected final DBCachedObjectIndex valuesByChild; + + protected final Collection objectsView; + + protected TargetObjectSchema rootSchema; + + public DBTraceObjectManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock, + TaskMonitor monitor, Language baseLanguage, DBTrace trace) + throws IOException, VersionException { + this.lock = lock; + this.trace = trace; + + DBCachedObjectStoreFactory factory = trace.getStoreFactory(); + schemaStore = factory.getOrCreateCachedStore(DBTraceObjectSchemaEntry.TABLE_NAME, + DBTraceObjectSchemaEntry.class, DBTraceObjectSchemaEntry::new, true); + loadRootSchema(); + objectStore = factory.getOrCreateCachedStore(DBTraceObject.TABLE_NAME, + DBTraceObject.class, (s, r) -> new DBTraceObject(this, s, r), true); + valueStore = factory.getOrCreateCachedStore(DBTraceObjectValue.TABLE_NAME, + DBTraceObjectValue.class, (s, r) -> new DBTraceObjectValue(this, s, r), true); + rangeValueMap = new DBTraceAddressSnapRangePropertyMap<>( + DBTraceObjectAddressRangeValue.TABLE_NAME, dbh, openMode, lock, monitor, baseLanguage, + trace, null, DBTraceObjectAddressRangeValue.class, + (t, s, r) -> new DBTraceObjectAddressRangeValue(this, t, s, r)); + + objectsByPath = + objectStore.getIndex(TraceObjectKeyPath.class, DBTraceObject.PATH_COLUMN); + valuesByTriple = + valueStore.getIndex(PrimaryTriple.class, DBTraceObjectValue.TRIPLE_COLUMN); + valuesByChild = + valueStore.getIndex(DBTraceObject.class, DBTraceObjectValue.CHILD_COLUMN); + + objectsView = Collections.unmodifiableCollection(objectStore.asMap().values()); + } + + protected void loadRootSchema() { + if (schemaStore.asMap().isEmpty()) { + rootSchema = null; + return; + } + assert schemaStore.asMap().size() == 1; + DBTraceObjectSchemaEntry schemaEntry = schemaStore.getObjectAt(0); + rootSchema = schemaEntry.schema; + } + + @Override + public void dbError(IOException e) { + trace.dbError(e); + } + + @Override + public void invalidateCache(boolean all) { + objectStore.invalidateCache(); + valueStore.invalidateCache(); + rangeValueMap.invalidateCache(all); + schemaStore.invalidateCache(); + loadRootSchema(); + } + + @Internal + protected DBTraceObject assertIsMine(TraceObject object) { + if (!(object instanceof DBTraceObject)) { + throw new IllegalArgumentException("Object " + object + " is not part of this trace"); + } + DBTraceObject dbObject = (DBTraceObject) object; + if (dbObject.manager != this) { + throw new IllegalArgumentException("Object " + object + " is not part of this trace"); + } + if (!getAllObjects().contains(dbObject)) { + throw new IllegalArgumentException("Object " + object + " is not part of this trace"); + } + return dbObject; + } + + protected Object validatePrimitive(Object child) { + PrimitiveCodec.getCodec(child.getClass()); + return child; + } + + @Override + public Trace getTrace() { + return trace; + } + + protected void setSchema(TargetObjectSchema schema) { + if (rootSchema != null) { + throw new IllegalStateException("There is already a root object"); + } + DBTraceObjectSchemaEntry schemaEntry = schemaStore.create(0); + schemaEntry.set(schema); + rootSchema = schema; + } + + protected InternalTraceObjectValue doCreateValue(Range lifespan, + DBTraceObject parent, String key, Object value) { + if (value instanceof AddressRange) { + DBTraceObjectAddressRangeValue entry = rangeValueMap + .put(new ImmutableTraceAddressSnapRange((AddressRange) value, lifespan), null); + entry.set(parent, key, false); + return entry; + } + else if (value instanceof Address) { + Address address = (Address) value; + AddressRange singleton = new AddressRangeImpl(address, address); + DBTraceObjectAddressRangeValue entry = rangeValueMap + .put(new ImmutableTraceAddressSnapRange(singleton, lifespan), null); + entry.set(parent, key, true); + return entry; + } + DBTraceObjectValue entry = valueStore.create(); + entry.set(lifespan, parent, key, value); + return entry; + } + + protected DBTraceObject doCreateObject(TraceObjectKeyPath path, Range lifespan) { + DBTraceObject obj = objectStore.create(); + obj.set(path, lifespan); + obj.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.CREATED, null, obj)); + return obj; + } + + @Override + public DBTraceObject createObject(TraceObjectKeyPath path, Range lifespan) { + if (path.isRoot()) { + throw new IllegalArgumentException("Cannot create non-root object with root path"); + } + try (LockHold hold = trace.lockWrite()) { + if (rootSchema == null) { + throw new IllegalStateException("No schema! Create the root object, first."); + } + return doCreateObject(path, lifespan); + } + } + + @Override + public TraceObjectValue createRootObject(TargetObjectSchema schema) { + try (LockHold hold = trace.lockWrite()) { + setSchema(schema); + DBTraceObject root = doCreateObject(TraceObjectKeyPath.of(), Range.all()); + assert root.getKey() == 0; + InternalTraceObjectValue val = doCreateValue(Range.all(), null, null, root); + assert val.getKey() == 0; + return val; + } + } + + @Override + public TargetObjectSchema getRootSchema() { + try (LockHold hold = trace.lockRead()) { + return rootSchema; + } + } + + @Override + public DBTraceObject getRootObject() { + return getObjectById(0); + } + + @Override + public DBTraceObject getObjectById(long key) { + try (LockHold hold = trace.lockRead()) { + return objectStore.getObjectAt(key); + } + } + + @Override + public Collection getObjectsByCanonicalPath( + TraceObjectKeyPath path) { + return objectsByPath.get(path); + } + + @Override + public Stream getObjectsByPath(Range span, + TraceObjectKeyPath path) { + return getValuePaths(span, new PathPattern(path.getKeyList())) + .map(p -> p.getLastChild(getRootObject())) + .filter(DBTraceObject.class::isInstance) + .map(DBTraceObject.class::cast); + } + + @Override + public Stream getValuePaths( + Range span, PathPredicates predicates) { + try (LockHold hold = trace.lockRead()) { + DBTraceObjectValue rootVal = valueStore.getObjectAt(0); + if (rootVal == null) { + return Stream.of(); + } + return rootVal.doGetSuccessors(span, null, predicates); + } + } + + @Override + public Collection getAllObjects() { + return objectsView; + } + + @Override + public Collection getValuesIntersecting(Range span, + AddressRange range) { + return Collections.unmodifiableCollection( + rangeValueMap.reduce(TraceAddressSnapRangeQuery.intersecting(range, span)).values()); + } + + public Collection getValuesAt(long snap, Address address) { + return Collections.unmodifiableCollection( + rangeValueMap.reduce(TraceAddressSnapRangeQuery.at(address, snap)).values()); + } + + @Override + public Stream queryAllInterface(Range span, + Class ifClass) { + if (rootSchema == null) { + throw new IllegalStateException("There is no schema. Create a root object."); + } + Class targetIf = TraceObjectInterfaceUtils.toTargetIf(ifClass); + PathMatcher matcher = rootSchema.searchFor(targetIf, true); + return getValuePaths(span, matcher) + .map(p -> p.getLastChild(getRootObject()).queryInterface(ifClass)); + } + + @Override + public void clear() { + try (LockHold hold = trace.lockWrite()) { + valueStore.deleteAll(); + rangeValueMap.clear(); + objectStore.deleteAll(); + schemaStore.deleteAll(); + rootSchema = null; + } + } + + protected void doDeleteObject(DBTraceObject object) { + objectStore.delete(object); + object.emitEvents(new TraceChangeRecord<>(TraceObjectChangeType.DELETED, null, object)); + } + + protected void doDeleteEdge(DBTraceObjectValue edge) { + valueStore.delete(edge); + } + + public boolean hasSchema() { + return rootSchema != null; + } + + protected I doAddWithInterface(List keyList, + Range lifespan, Class iface, ConflictResolution resolution) { + Class targetIf = TraceObjectInterfaceUtils.toTargetIf(iface); + TargetObjectSchema schema = rootSchema.getSuccessorSchema(keyList); + if (!schema.getInterfaces().contains(targetIf)) { + throw new IllegalStateException( + "Schema " + schema + " at " + PathUtils.toString(keyList) + + " does not provide interface " + iface.getSimpleName()); + } + DBTraceObject obj = createObject(TraceObjectKeyPath.of(keyList), lifespan); + obj.insert(resolution); + return obj.queryInterface(iface); + } + + protected I doAddWithInterface(String path, + Range lifespan, Class iface, ConflictResolution resolution) { + return doAddWithInterface(PathUtils.parse(path), lifespan, iface, resolution); + } + + public Collection getAllObjects(Class iface) { + try (LockHold hold = trace.lockRead()) { + return queryAllInterface(Range.all(), iface).collect(Collectors.toSet()); + } + } + + public Collection getObjectsByPath(String path, + Class iface) { + try (LockHold hold = trace.lockRead()) { + return getObjectsByPath(Range.all(), TraceObjectKeyPath.parse(path)) + .map(o -> o.queryInterface(iface)) + .filter(i -> i != null) + .collect(Collectors.toSet()); + } + } + + public I getObjectByPath(long snap, String path, + Class iface) { + try (LockHold hold = trace.lockRead()) { + return getObjectsByPath(Range.singleton(snap), TraceObjectKeyPath.parse(path)).findAny() + .map(o -> o.queryInterface(iface)) + .orElse(null); + } + } + + protected Stream doParentsWithKeyHaving( + Stream values, String key, Class iface) { + return values.filter(v -> key.equals(v.getEntryKey())) + .map(v -> v.getParent()) + .map(o -> o.queryInterface(iface)) + .filter(i -> i != null); + } + + public Collection getObjectsContaining(long snap, + Address address, String key, Class iface) { + try (LockHold hold = trace.lockRead()) { + return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key, + iface).collect(Collectors.toSet()); + } + } + + public I getObjectContaining(long snap, Address address, + String key, Class iface) { + try (LockHold hold = trace.lockRead()) { + return doParentsWithKeyHaving(getValuesAt(snap, address).stream(), key, + iface).findAny().orElse(null); + } + } + + public Collection getObjectsIntersecting( + Range lifespan, AddressRange range, String key, Class iface) { + try (LockHold hold = trace.lockRead()) { + return doParentsWithKeyHaving(getValuesIntersecting(lifespan, range).stream(), key, + iface).collect(Collectors.toSet()); + } + } + + public Collection getObjectsAtSnap(long snap, + Class iface) { + try (LockHold hold = trace.lockRead()) { + return queryAllInterface(Range.singleton(snap), iface).collect(Collectors.toSet()); + } + } + + public AddressSetView getObjectsAddressSet(long snap, + String key, Class ifaceCls, Predicate predicate) { + return rangeValueMap.getAddressSetView(Range.singleton(snap), v -> { + if (!key.equals(v.getEntryKey())) { + return false; + } + TraceObject parent = v.getParent(); + I iface = parent.queryInterface(ifaceCls); + if (iface == null) { + return false; + } + if (!predicate.test(iface)) { + return false; + } + return true; + }); + } + + public I getSuccessor(TraceObject seed, + PathPredicates predicates, long snap, Class iface) { + try (LockHold hold = trace.lockRead()) { + return seed.getSuccessors(Range.singleton(snap), predicates) + .map(p -> p.getLastChild(seed).queryInterface(iface)) + .filter(i -> i != null) + .findAny() + .orElse(null); + } + } + + public I getLatestSuccessor(TraceObject seed, + TraceObjectKeyPath path, long snap, Class iface) { + try (LockHold hold = trace.lockRead()) { + return seed.getOrderedSuccessors(Range.atMost(snap), path, false) + .map(p -> p.getLastChild(seed).queryInterface(iface)) + .filter(i -> i != null) + .findAny() + .orElse(null); + } + } + + public TraceObjectBreakpointLocation addBreakpoint(String path, Range lifespan, + AddressRange range, Collection threads, + Collection kinds, boolean enabled, String comment) + throws DuplicateNameException { + // First verify that the schema accommodates + List specPath = + getRootSchema().searchForAncestor(TargetBreakpointSpec.class, PathUtils.parse(path)); + if (specPath == null) { + throw new IllegalStateException("The schema does not provide an implicit " + + "breakpoint specification on the given path."); + } + try (LockHold hold = trace.lockWrite()) { + TraceObjectBreakpointLocation loc = doAddWithInterface(path, lifespan, + TraceObjectBreakpointLocation.class, ConflictResolution.DENY); + loc.setName(path); + loc.setRange(range); + // NB. Ignore threads. I'd like to deprecate that field, anyway. + loc.setKinds(kinds); + loc.setEnabled(enabled); + loc.setComment(comment); + return loc; + } + catch (DuplicateKeyException e) { + throw new DuplicateNameException(e.getMessage()); + } + } + + public TraceObjectMemoryRegion addMemoryRegion(String path, Range lifespan, + AddressRange range, Collection flags) + throws TraceOverlappedRegionException { + try (LockHold hold = trace.lockWrite()) { + TraceObjectMemoryRegion region = doAddWithInterface(path, lifespan, + TraceObjectMemoryRegion.class, ConflictResolution.TRUNCATE); + /** + * TODO: Test that when the ADDED events hits, that it actually appears in queries. I + * suspect this will work since the events and/or event processors should be delayed + * until the write lock is released. Certainly, a query would require the read lock. + */ + region.setName(path); + region.setRange(range); + region.setFlags(flags); + trace.updateViewsAddRegionBlock(region); + return region; + } + } + + public TraceObjectModule addModule(String path, String name, Range lifespan, + AddressRange range) throws DuplicateNameException { + try (LockHold hold = trace.lockWrite()) { + TraceObjectModule module = doAddWithInterface(path, lifespan, TraceObjectModule.class, + ConflictResolution.DENY); + module.setName(name); + module.setRange(range); + return module; + } + catch (DuplicateKeyException e) { + throw new DuplicateNameException(e.getMessage()); + } + } + + public TraceObjectSection addSection(String path, String name, Range lifespan, + AddressRange range) throws DuplicateNameException { + try (LockHold hold = trace.lockWrite()) { + TraceObjectSection section = doAddWithInterface(path, lifespan, + TraceObjectSection.class, ConflictResolution.DENY); + section.setName(name); + section.setRange(range); + return section; + } + catch (DuplicateKeyException e) { + throw new DuplicateNameException(e.getMessage()); + } + } + + public TraceObjectStack addStack(List keyList, long snap) { + try (LockHold hold = trace.lockWrite()) { + return doAddWithInterface(keyList, Range.singleton(snap), TraceObjectStack.class, + ConflictResolution.DENY); + } + } + + public TraceObjectStackFrame addStackFrame(List keyList, long snap) { + try (LockHold hold = trace.lockWrite()) { + return doAddWithInterface(keyList, Range.singleton(snap), TraceObjectStackFrame.class, + ConflictResolution.DENY); + } + } + + public TraceObjectThread addThread(String path, String display, Range lifespan) + throws DuplicateNameException { + try (LockHold hold = trace.lockWrite()) { + TraceObjectThread thread = doAddWithInterface(path, lifespan, TraceObjectThread.class, + ConflictResolution.DENY); + thread.setName(display); + return thread; + } + catch (DuplicateKeyException e) { + throw new DuplicateNameException(e.getMessage()); + } + } + + public boolean checkMyObject(DBTraceObject object) { + if (object.manager != this) { + return false; + } + if (!getAllObjects().contains(object)) { + return false; + } + return true; + } + + public TraceThread assertMyThread(TraceThread thread) { + if (!(thread instanceof DBTraceObjectThread)) { + throw new AssertionError("Thread " + thread + " is not an object in this trace"); + } + DBTraceObjectThread dbThread = (DBTraceObjectThread) thread; + if (!checkMyObject(dbThread.getObject())) { + throw new AssertionError("Thread " + thread + " is not an object in this trace"); + } + return dbThread; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java new file mode 100644 index 0000000000..e898962a70 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValPath.java @@ -0,0 +1,120 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import java.util.*; +import java.util.stream.Collectors; + +import ghidra.dbg.util.PathUtils.PathComparator; +import ghidra.trace.model.target.*; + +public class DBTraceObjectValPath implements TraceObjectValPath { + static DBTraceObjectValPath of(Collection entryList) { + return new DBTraceObjectValPath(List.copyOf(entryList)); + } + + static DBTraceObjectValPath of(InternalTraceObjectValue... entries) { + return DBTraceObjectValPath.of(Arrays.asList(entries)); + } + + private final List entryList; + private List keyList; // lazily computed + + private DBTraceObjectValPath(List entryList) { + this.entryList = entryList; + } + + @Override + public int compareTo(TraceObjectValPath o) { + return PathComparator.KEYED.compare(getKeyList(), o.getKeyList()); + } + + @Override + public List getEntryList() { + return entryList; + } + + protected List computeKeyList() { + return entryList.stream() + .map(e -> e.getEntryKey()) + .collect(Collectors.toUnmodifiableList()); + } + + @Override + public List getKeyList() { + if (keyList == null) { + keyList = computeKeyList(); + } + return keyList; + } + + @Override + public boolean contains(TraceObjectValue entry) { + return entryList.contains(entry); + } + + public DBTraceObjectValPath prepend(TraceObjectValue entry) { + InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()]; + arr[0] = (DBTraceObjectValue) entry; + for (int i = 1; i < arr.length; i++) { + arr[i] = entryList.get(i - 1); + } + return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr))); + } + + public DBTraceObjectValPath append(TraceObjectValue entry) { + InternalTraceObjectValue[] arr = new InternalTraceObjectValue[1 + entryList.size()]; + for (int i = 0; i < arr.length - 1; i++) { + arr[i] = entryList.get(i); + } + arr[arr.length - 1] = (InternalTraceObjectValue) entry; + return new DBTraceObjectValPath(Collections.unmodifiableList(Arrays.asList(arr))); + } + + @Override + public InternalTraceObjectValue getFirstEntry() { + if (entryList.isEmpty()) { + return null; + } + return entryList.get(0); + } + + @Override + public TraceObject getFirstParent(TraceObject ifEmpty) { + InternalTraceObjectValue first = getFirstEntry(); + return first == null ? ifEmpty : first.getParent(); + } + + @Override + public InternalTraceObjectValue getLastEntry() { + if (entryList.isEmpty()) { + return null; + } + return entryList.get(entryList.size() - 1); + } + + @Override + public Object getLastValue(Object ifEmpty) { + InternalTraceObjectValue last = getLastEntry(); + return last == null ? ifEmpty : last.getValue(); + } + + @Override + public TraceObject getLastChild(TraceObject ifEmpty) { + InternalTraceObjectValue last = getLastEntry(); + return last == null ? ifEmpty : last.getChild(); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java new file mode 100644 index 0000000000..2968aa7496 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/DBTraceObjectValue.java @@ -0,0 +1,420 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.stream.Stream; + +import org.apache.commons.lang3.ArrayUtils; + +import com.google.common.collect.Range; + +import db.*; +import ghidra.dbg.util.PathPredicates; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObjectValPath; +import ghidra.util.LockHold; +import ghidra.util.database.*; +import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; +import ghidra.util.database.DBCachedObjectStoreFactory.VariantDBFieldCodec; +import ghidra.util.database.annot.*; + +@DBAnnotatedObjectInfo(version = 0) +public class DBTraceObjectValue extends DBAnnotatedObject implements InternalTraceObjectValue { + protected static final String TABLE_NAME = "ObjectValue"; + + protected static class PrimaryTriple { + private final DBTraceObject parent; + private final String key; + private final long minSnap; + + protected PrimaryTriple(DBTraceObject parent, String key, long minSnap) { + this.parent = parent; + this.key = key; + this.minSnap = minSnap; + } + + @Override + public String toString() { + return ""; + } + + public PrimaryTriple withMinSnap(long minSnap) { + return new PrimaryTriple(parent, key, minSnap); + } + } + + public static class PrimaryTripleDBFieldCodec + extends AbstractDBFieldCodec { + static final Charset cs = Charset.forName("UTF-8"); + + public PrimaryTripleDBFieldCodec(Class objectType, Field field, + int column) { + super(PrimaryTriple.class, objectType, BinaryField.class, field, column); + } + + protected static byte[] encode(PrimaryTriple value) { + if (value == null) { + return null; + } + if (value.key == null) { + ByteBuffer buf = ByteBuffer.allocate(Long.BYTES * 2); + buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE); + buf.putLong(value.minSnap ^ Long.MIN_VALUE); + return buf.array(); + } + + byte[] keyBytes = value.key.getBytes(cs); + ByteBuffer buf = ByteBuffer.allocate(keyBytes.length + 1 + Long.BYTES * 2); + + buf.putLong(DBTraceObjectDBFieldCodec.encode(value.parent) ^ Long.MIN_VALUE); + buf.put(keyBytes); + buf.put((byte) 0); + buf.putLong(value.minSnap ^ Long.MIN_VALUE); + + return buf.array(); + } + + protected static PrimaryTriple decode(DBTraceObjectValue ent, byte[] enc) { + if (enc == null) { + return null; + } + ByteBuffer buf = ByteBuffer.wrap(enc); + + DBTraceObject parent = + DBTraceObjectDBFieldCodec.decode(ent, buf.getLong() ^ Long.MIN_VALUE); + String key; + if (enc.length > Long.BYTES * 2) { + int nullPos = ArrayUtils.indexOf(enc, (byte) 0, buf.position()); + assert nullPos != -1; + key = new String(enc, buf.position(), nullPos - buf.position(), cs); + buf.position(nullPos + 1); + } + else { + key = null; + } + long minSnap = buf.getLong() ^ Long.MIN_VALUE; + + return new PrimaryTriple(parent, key, minSnap); + } + + @Override + public void store(PrimaryTriple value, BinaryField f) { + f.setBinaryData(encode(value)); + } + + @Override + protected void doStore(DBTraceObjectValue obj, DBRecord record) + throws IllegalArgumentException, IllegalAccessException { + record.setBinaryData(column, encode(getValue(obj))); + } + + @Override + protected void doLoad(DBTraceObjectValue obj, DBRecord record) + throws IllegalArgumentException, IllegalAccessException { + setValue(obj, decode(obj, record.getBinaryData(column))); + } + } + + public static class DBTraceObjectDBFieldCodec + extends AbstractDBFieldCodec { + public DBTraceObjectDBFieldCodec(Class objectType, Field field, + int column) { + super(DBTraceObject.class, objectType, LongField.class, field, column); + } + + protected static long encode(DBTraceObject value) { + return value == null ? -1 : value.getKey(); + } + + protected static DBTraceObject decode(InternalTraceObjectValue ent, long enc) { + return enc == -1 ? null : ent.getManager().getObjectById(enc); + } + + @Override + public void store(DBTraceObject value, LongField f) { + f.setLongValue(encode(value)); + } + + @Override + protected void doStore(OV obj, DBRecord record) + throws IllegalArgumentException, IllegalAccessException { + record.setLongValue(column, encode(getValue(obj))); + } + + @Override + protected void doLoad(OV obj, DBRecord record) + throws IllegalArgumentException, IllegalAccessException { + setValue(obj, decode(obj, record.getLongValue(column))); + } + } + + static final String TRIPLE_COLUMN_NAME = "Triple"; + static final String MAX_SNAP_COLUMN_NAME = "MaxSnap"; + static final String CHILD_COLUMN_NAME = "Child"; + static final String PRIMITIVE_COLUMN_NAME = "Primitive"; + + @DBAnnotatedColumn(TRIPLE_COLUMN_NAME) + static DBObjectColumn TRIPLE_COLUMN; + @DBAnnotatedColumn(MAX_SNAP_COLUMN_NAME) + static DBObjectColumn MAX_SNAP_COLUMN; + @DBAnnotatedColumn(CHILD_COLUMN_NAME) + static DBObjectColumn CHILD_COLUMN; + @DBAnnotatedColumn(PRIMITIVE_COLUMN_NAME) + static DBObjectColumn PRIMITIVE_COLUMN; + + @DBAnnotatedField( + column = TRIPLE_COLUMN_NAME, + indexed = true, + codec = PrimaryTripleDBFieldCodec.class) + private PrimaryTriple triple; + @DBAnnotatedField(column = MAX_SNAP_COLUMN_NAME) + private long maxSnap; + @DBAnnotatedField( + column = CHILD_COLUMN_NAME, + indexed = true, + codec = DBTraceObjectDBFieldCodec.class) + private DBTraceObject child; + @DBAnnotatedField(column = PRIMITIVE_COLUMN_NAME, codec = VariantDBFieldCodec.class) + private Object primitive; + + protected final DBTraceObjectManager manager; + + private Range lifespan; + + public DBTraceObjectValue(DBTraceObjectManager manager, DBCachedObjectStore store, + DBRecord record) { + super(store, record); + this.manager = manager; + } + + @Override + protected void fresh(boolean created) throws IOException { + if (created) { + return; + } + lifespan = DBTraceUtils.toRange(triple.minSnap, maxSnap); + } + + protected void set(Range lifespan, DBTraceObject parent, String key, Object value) { + this.triple = new PrimaryTriple(parent, key, DBTraceUtils.lowerEndpoint(lifespan)); + this.maxSnap = DBTraceUtils.upperEndpoint(lifespan); + this.lifespan = DBTraceUtils.toRange(triple.minSnap, maxSnap); + if (value instanceof TraceObject) { + DBTraceObject child = manager.assertIsMine((TraceObject) value); + this.child = child; + this.primitive = null; + } + else { + this.primitive = manager.validatePrimitive(value); + this.child = null; + } + update(TRIPLE_COLUMN, MAX_SNAP_COLUMN, CHILD_COLUMN, PRIMITIVE_COLUMN); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ": parent=" + triple.parent + ", key=" + triple.key + + ", lifespan=" + getLifespan() + ", value=" + getValue(); + } + + @Override + public void doSetLifespan(Range lifespan) { + long minSnap = DBTraceUtils.lowerEndpoint(lifespan); + if (this.triple.minSnap != minSnap) { + this.triple = triple.withMinSnap(minSnap); + update(TRIPLE_COLUMN); + } + this.maxSnap = DBTraceUtils.upperEndpoint(lifespan); + update(MAX_SNAP_COLUMN); + this.lifespan = DBTraceUtils.toRange(minSnap, maxSnap); + } + + @Override + public Trace getTrace() { + return manager.trace; + } + + @Override + public DBTraceObjectManager getManager() { + return manager; + } + + @Override + public DBTraceObject getParent() { + return triple.parent; + } + + @Override + public String getEntryKey() { + return triple.key; + } + + @Override + public Object getValue() { + try (LockHold hold = manager.trace.lockRead()) { + return child != null ? child : primitive; + } + } + + @Override + public DBTraceObject getChild() { + return (DBTraceObject) getValue(); + } + + @Override + public Range getLifespan() { + try (LockHold hold = manager.trace.lockRead()) { + return lifespan; + } + } + + @Override + public void setMinSnap(long minSnap) { + try (LockHold hold = manager.trace.lockWrite()) { + setLifespan(DBTraceUtils.toRange(minSnap, maxSnap)); + } + } + + @Override + public long getMinSnap() { + try (LockHold hold = manager.trace.lockRead()) { + return triple.minSnap; + } + } + + @Override + public void setMaxSnap(long maxSnap) { + try (LockHold hold = manager.trace.lockWrite()) { + setLifespan(DBTraceUtils.toRange(triple.minSnap, maxSnap)); + } + } + + @Override + public long getMaxSnap() { + try (LockHold hold = manager.trace.lockRead()) { + return maxSnap; + } + } + + protected Stream doGetAllPaths(Range span, + DBTraceObjectValPath post) { + return triple.parent.doGetAllPaths(span, post.prepend(this)); + } + + protected Stream doGetAncestors(Range span, + DBTraceObjectValPath post, PathPredicates predicates) { + return triple.parent.doGetAncestors(span, post.prepend(this), predicates); + } + + @Override + public Stream doGetSuccessors( + Range span, DBTraceObjectValPath pre, PathPredicates predicates) { + DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this); + boolean includeMe = predicates.matches(path.getKeyList()); + boolean descend = child != null; + if (includeMe && descend) { + return Stream.concat(Stream.of(path), child.doGetSuccessors(span, path, predicates)); + } + if (includeMe) { + return Stream.of(path); + } + if (descend) { + return child.doGetSuccessors(span, path, predicates); + } + return Stream.empty(); + } + + @Override + public Stream doGetOrderedSuccessors(Range span, + DBTraceObjectValPath pre, PathPredicates predicates, boolean forward) { + DBTraceObjectValPath path = pre == null ? DBTraceObjectValPath.of() : pre.append(this); + if (predicates.matches(path.getKeyList())) { + // Singleton path, so if I match, nothing below can + return Stream.of(path); + } + if (child == null) { + return Stream.of(); + } + return child.doGetOrderedSuccessors(span, path, predicates, forward); + } + + protected boolean doIsCanonical() { + if (child == null) { + return false; + } + if (triple.parent == null) { + return true; + } + return triple.parent.getCanonicalPath().extend(triple.key).equals(child.getCanonicalPath()); + } + + @Override + public boolean isCanonical() { + try (LockHold hold = LockHold.lock(manager.lock.readLock())) { + return doIsCanonical(); + } + } + + protected void doDeleteSuccessors() { + if (!doIsCanonical()) { + return; + } + child.doDeleteTree(); + } + + @Override + public void doDelete() { + manager.doDeleteEdge(this); + } + + @Override + public void delete() { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + if (triple.parent == null) { + throw new IllegalArgumentException("Cannot delete root value"); + } + doDelete(); + } + } + + protected void doDeleteTree() { + doDeleteSuccessors(); + doDelete(); + } + + @Override + public void deleteTree() { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + doDeleteTree(); + } + } + + @Override + public InternalTraceObjectValue truncateOrDelete(Range span) { + try (LockHold hold = LockHold.lock(manager.lock.writeLock())) { + if (triple.parent == null) { + throw new IllegalArgumentException("Cannot truncate or delete root value"); + } + return doTruncateOrDelete(span); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java new file mode 100644 index 0000000000..ed1fcc5301 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/InternalTraceObjectValue.java @@ -0,0 +1,173 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import java.util.*; +import java.util.stream.Stream; + +import org.apache.commons.collections4.IterableUtils; + +import com.google.common.collect.Range; + +import ghidra.dbg.util.PathPredicates; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.DBTraceUtils.LifespanMapSetter; +import ghidra.trace.model.Trace.TraceObjectChangeType; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.target.TraceObjectValue; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.util.LockHold; + +interface InternalTraceObjectValue extends TraceObjectValue { + abstract class ValueLifespanSetter + extends LifespanMapSetter { + protected final Range range; + protected final Object value; + protected InternalTraceObjectValue keep = null; + protected Collection kept = new ArrayList<>(2); + + public ValueLifespanSetter(Range range, Object value) { + this.range = range; + this.value = value; + } + + public ValueLifespanSetter(Range range, Object value, + InternalTraceObjectValue keep) { + this(range, value); + this.keep = keep; + } + + @Override + protected Range getRange(InternalTraceObjectValue entry) { + return entry.getLifespan(); + } + + @Override + protected Object getValue(InternalTraceObjectValue entry) { + return entry.getValue(); + } + + @Override + protected void remove(InternalTraceObjectValue entry) { + if (Objects.equals(entry.getValue(), value)) { + if (keep == null) { + keep = entry; + } + else { + entry.doDelete(); + } + } + else { + entry.doTruncateOrDelete(range); + if (!entry.isDeleted()) { + kept.add(entry); + } + } + } + + @Override + protected InternalTraceObjectValue put(Range range, Object value) { + if (value == null) { + return null; + } + if (keep != null && Objects.equals(this.value, value)) { + keep.doSetLifespan(range); + return keep; + } + for (InternalTraceObjectValue k : kept) { + if (Objects.equals(value, k.getValue()) && Objects.equals(range, k.getLifespan())) { + kept.remove(k); + return k; + } + } + return create(range, value); + } + + protected abstract InternalTraceObjectValue create(Range range, Object value); + } + + DBTraceObjectManager getManager(); + + /** + * Get the database key + * + * @return the key + */ + long getKey(); + + @Override + DBTraceObject getChild(); + + void doSetLifespan(Range range); + + @Override + default void setLifespan(Range lifespan) { + setLifespan(lifespan, ConflictResolution.TRUNCATE); + } + + @Override + default void setLifespan(Range lifespan, ConflictResolution resolution) { + try (LockHold hold = getTrace().lockWrite()) { + Range oldLifespan = getLifespan(); + if (getParent() == null) { + throw new IllegalArgumentException("Cannot set lifespan of root value"); + } + if (resolution == ConflictResolution.DENY) { + getParent().doCheckConflicts(lifespan, getEntryKey(), getValue()); + } + new ValueLifespanSetter(lifespan, getValue(), this) { + @Override + protected Iterable getIntersecting(Long lower, + Long upper) { + Collection col = Collections.unmodifiableCollection( + getParent().doGetValues(lower, upper, getEntryKey())); + return IterableUtils.filteredIterable(col, v -> v != keep); + } + + @Override + protected InternalTraceObjectValue create(Range range, Object value) { + return getParent().doCreateValue(range, getEntryKey(), value); + } + }.set(lifespan, getValue()); + getParent().emitEvents(new TraceChangeRecord<>( + TraceObjectChangeType.VALUE_LIFESPAN_CHANGED, null, this, oldLifespan, lifespan)); + } + } + + Stream doGetSuccessors(Range span, + DBTraceObjectValPath pre, PathPredicates predicates); + + Stream doGetOrderedSuccessors(Range span, + DBTraceObjectValPath pre, PathPredicates predicates, boolean forward); + + void doDelete(); + + @Override + DBTraceObject getParent(); + + default InternalTraceObjectValue doTruncateOrDelete(Range span) { + List> removed = DBTraceUtils.subtract(getLifespan(), span); + if (removed.isEmpty()) { + doDelete(); + return null; + } + doSetLifespan(removed.get(0)); + if (removed.size() == 2) { + return getParent().doCreateValue(removed.get(1), getEntryKey(), getValue()); + } + return this; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/LifespanCorrector.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/LifespanCorrector.java new file mode 100644 index 0000000000..d39327871d --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/target/LifespanCorrector.java @@ -0,0 +1,222 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import com.google.common.collect.Range; + +import ghidra.lifecycle.Experimental; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.model.target.TraceObject; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.target.TraceObjectValue; + +@Experimental +public class LifespanCorrector { + /** + * A visitor for lifespan correction + * + *

+ * Implementors must implement only the upward pair, or only the downward pair + */ + public interface Visitor { + /** + * Visit an object on the upward side of traversal + * + * @param object the object + */ + default void visitObjectUpward(TraceObject object) { + } + + /** + * Visit an object on the downward side of traversal + * + * @param object the object + */ + default void visitObjectDownward(TraceObject object) { + } + + /** + * Visit a value on the upward side of traversal + * + * @param value the value, guaranteed to have a child + */ + default void visitValueUpward(TraceObjectValue value) { + } + + /** + * Visit a value on the downward side of traversal + * + * @param value the value, guaranteed to have a child + */ + default void visitValueDownward(TraceObjectValue value) { + } + } + + public enum Direction { + ANCESTORS { + @Override + public void visit(TraceObject seed, Visitor visitor) { + visitObjectAncestors(seed, visitor, UP | DOWN); + } + + }, + SUCCESSORS { + @Override + public void visit(TraceObject seed, Visitor visitor) { + visitObjectSuccessors(seed, visitor, true); + } + }, + BOTH { + @Override + public void visit(TraceObject seed, Visitor visitor) { + visitObjectAncestors(seed, visitor, DOWN); + visitObjectSuccessors(seed, visitor, false); + visitObjectAncestors(seed, visitor, UP); + } + }; + + static final int UP = 1; + static final int DOWN = 2; + + public abstract void visit(TraceObject seed, Visitor visitor); + + void visitObjectAncestors(TraceObject object, Visitor visitor, int mode) { + if ((mode & UP) == UP) { + visitor.visitObjectUpward(object); + } + if (!object.isRoot()) { + for (TraceObjectValue value : object.getParents()) { + // Yes, over time, there may be multiple canonical values + if (value.isCanonical() && !value.isDeleted()) { + visitValueAncestors(value, visitor, mode); + } + } + } + if ((mode & DOWN) == DOWN) { + visitor.visitObjectDownward(object); + } + } + + void visitValueAncestors(TraceObjectValue value, Visitor visitor, int mode) { + visitor.visitValueUpward(value); + visitObjectAncestors(value.getParent(), visitor, mode); + visitor.visitValueDownward(value); + } + + void visitObjectSuccessors(TraceObject object, Visitor visitor, boolean includeCur) { + if (includeCur) { + visitor.visitObjectDownward(object); + } + for (TraceObjectValue value : object.getValues()) { + if (value.isCanonical() && !value.isDeleted()) { + visitValueSuccessors(value, visitor); + } + } + if (includeCur) { + visitor.visitObjectUpward(object); + } + } + + void visitValueSuccessors(TraceObjectValue value, Visitor visitor) { + if (!(value.getValue() instanceof TraceObject)) { + return; + } + visitor.visitValueDownward(value); + visitObjectSuccessors(value.getChild(), visitor, true); + visitor.visitValueUpward(value); + } + } + + // TODO: Consider non-canonical paths? + + public enum Operation { + EXPAND { + @Override + Visitor getVisitor(ConflictResolution resolution) { + return new Visitor() { + @Override + public void visitObjectUpward(TraceObject object) { + Range span = object.getLifespan(); + for (TraceObjectValue value : object.getValues()) { + span = span.span(value.getLifespan()); + } + object.setLifespan(span); + } + + @Override + public void visitValueUpward(TraceObjectValue value) { + Range newLife = + value.getLifespan().span(value.getChild().getLifespan()); + value.setLifespan(newLife, resolution); + } + }; + } + }, + SHRINK { + @Override + Visitor getVisitor(ConflictResolution resolution) { + return new Visitor() { + @Override + public void visitObjectDownward(TraceObject object) { + for (TraceObjectValue value : object.getValues()) { + if (!DBTraceUtils.intersect(object.getLifespan(), + value.getLifespan())) { + value.delete(); + continue; + } + value.setLifespan( + value.getLifespan().intersection(object.getLifespan()), resolution); + } + } + + @Override + public void visitValueDownward(TraceObjectValue value) { + /** + * It'd be an odd circumstance for two canonical entries to exist for the + * same parent and child. If that happens, this will cause the child to + * become detached, since those entries cannot intersect. + */ + if (!DBTraceUtils.intersect(value.getLifespan(), + value.getChild().getLifespan())) { + value.getChild().delete(); + return; + } + Range newLife = + value.getLifespan().intersection(value.getChild().getLifespan()); + value.getChild().setLifespan(newLife); + } + }; + } + }; + + abstract Visitor getVisitor(ConflictResolution resolution); + } + + private final Direction direction; + private final Operation operation; + private final ConflictResolution resolution; + + public LifespanCorrector(Direction direction, Operation operation, + ConflictResolution resolution) { + this.direction = direction; + this.operation = operation; + this.resolution = resolution; + } + + public void correctLifespans(TraceObject seed) { + direction.visit(seed, operation.getVisitor(resolution)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java new file mode 100644 index 0000000000..de7cca4ca2 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceObjectThread.java @@ -0,0 +1,166 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.thread; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetObject; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.database.target.DBTraceObject; +import ghidra.trace.database.target.DBTraceObjectInterface; +import ghidra.trace.model.Trace; +import ghidra.trace.model.Trace.TraceThreadChangeType; +import ghidra.trace.model.target.annot.TraceObjectInterfaceUtils; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceChangeRecord; +import ghidra.trace.util.TraceChangeType; +import ghidra.util.LockHold; +import ghidra.util.exception.DuplicateNameException; + +public class DBTraceObjectThread implements TraceObjectThread, DBTraceObjectInterface { + + protected class ThreadChangeTranslator extends Translator { + protected ThreadChangeTranslator(DBTraceObject object, TraceThread iface) { + super(null, object, iface); + } + + @Override + protected TraceChangeType getAddedType() { + return TraceThreadChangeType.ADDED; + } + + @Override + protected TraceChangeType> getLifespanChangedType() { + return TraceThreadChangeType.LIFESPAN_CHANGED; + } + + @Override + protected TraceChangeType getChangedType() { + return TraceThreadChangeType.CHANGED; + } + + @Override + protected boolean appliesToKey(String key) { + return KEY_COMMENT.equals(key) || + TargetObject.DISPLAY_ATTRIBUTE_NAME.equals(key); + } + + @Override + protected TraceChangeType getDeletedType() { + return TraceThreadChangeType.DELETED; + } + } + + private final DBTraceObject object; + private final ThreadChangeTranslator translator; + + public DBTraceObjectThread(DBTraceObject object) { + this.object = object; + + translator = new ThreadChangeTranslator(object, this); + } + + @Override + public DBTraceObject getObject() { + return object; + } + + @Override + public Trace getTrace() { + return object.getTrace(); + } + + @Override + public long getKey() { + return object.getKey(); + } + + @Override + public String getPath() { + return object.getCanonicalPath().toString(); + } + + @Override + public String getName() { + return TraceObjectInterfaceUtils.getValue(object, getCreationSnap(), + TargetObject.DISPLAY_ATTRIBUTE_NAME, String.class, ""); + } + + @Override + public void setName(String name) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), TargetObject.DISPLAY_ATTRIBUTE_NAME, name); + } + } + + @Override + public void setCreationSnap(long creationSnap) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + setLifespan(DBTraceUtils.toRange(creationSnap, getDestructionSnap())); + } + } + + @Override + public long getCreationSnap() { + return object.getMinSnap(); + } + + @Override + public void setDestructionSnap(long destructionSnap) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + setLifespan(DBTraceUtils.toRange(getCreationSnap(), destructionSnap)); + } + } + + @Override + public long getDestructionSnap() { + return object.getMaxSnap(); + } + + @Override + public void setLifespan(Range lifespan) throws DuplicateNameException { + TraceObjectInterfaceUtils.setLifespan(TraceObjectThread.class, object, lifespan); + } + + @Override + public Range getLifespan() { + return object.getLifespan(); + } + + @Override + public void setComment(String comment) { + try (LockHold hold = object.getTrace().lockWrite()) { + object.setValue(getLifespan(), KEY_COMMENT, comment); + } + } + + @Override + public String getComment() { + return TraceObjectInterfaceUtils.getValue(object, getCreationSnap(), KEY_COMMENT, + String.class, ""); + } + + @Override + public void delete() { + object.deleteTree(); + } + + @Override + public TraceChangeRecord translateEvent(TraceChangeRecord rec) { + return translator.translate(rec); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceThreadManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceThreadManager.java index e5c6dd679e..05da613b33 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceThreadManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/thread/DBTraceThreadManager.java @@ -18,14 +18,15 @@ package ghidra.trace.database.thread; import java.io.IOException; import java.util.*; import java.util.concurrent.locks.ReadWriteLock; +import java.util.stream.Collectors; import com.google.common.collect.Range; import db.DBHandle; import ghidra.trace.database.*; +import ghidra.trace.database.target.DBTraceObjectManager; import ghidra.trace.model.Trace.TraceThreadChangeType; -import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.model.thread.TraceThreadManager; +import ghidra.trace.model.thread.*; import ghidra.trace.util.TraceChangeRecord; import ghidra.util.LockHold; import ghidra.util.database.*; @@ -37,14 +38,19 @@ public class DBTraceThreadManager implements TraceThreadManager, DBTraceManager protected final ReadWriteLock lock; protected final DBTrace trace; + protected final DBTraceObjectManager objectManager; + protected final DBCachedObjectStore threadStore; protected final DBCachedObjectIndex threadsByPath; public DBTraceThreadManager(DBHandle dbh, DBOpenMode openMode, ReadWriteLock lock, - TaskMonitor monitor, DBTrace trace) throws IOException, VersionException { + TaskMonitor monitor, DBTrace trace, DBTraceObjectManager objectManager) + throws IOException, VersionException { this.lock = lock; this.trace = trace; + this.objectManager = objectManager; + DBCachedObjectStoreFactory factory = trace.getStoreFactory(); threadStore = factory.getOrCreateCachedStore(DBTraceThread.TABLE_NAME, DBTraceThread.class, @@ -63,7 +69,13 @@ public class DBTraceThreadManager implements TraceThreadManager, DBTraceManager } // Internal - public DBTraceThread assertIsMine(TraceThread thread) { + public TraceThread assertIsMine(TraceThread thread) { + if (thread == null) { + return null; + } + if (objectManager.hasSchema()) { + return objectManager.assertMyThread(thread); + } if (!(thread instanceof DBTraceThread)) { throw new IllegalArgumentException("Thread " + thread + " is not part of this trace"); } @@ -92,14 +104,17 @@ public class DBTraceThreadManager implements TraceThreadManager, DBTraceManager } @Override - public DBTraceThread addThread(String path, Range lifespan) + public TraceThread addThread(String path, Range lifespan) throws DuplicateNameException { return addThread(path, path, lifespan); } @Override - public DBTraceThread addThread(String path, String display, Range lifespan) + public TraceThread addThread(String path, String display, Range lifespan) throws DuplicateNameException { + if (objectManager.hasSchema()) { + return objectManager.addThread(path, display, lifespan); + } DBTraceThread thread; try (LockHold hold = LockHold.lock(lock.writeLock())) { checkConflictingPath(null, path, lifespan); @@ -111,17 +126,26 @@ public class DBTraceThreadManager implements TraceThreadManager, DBTraceManager } @Override - public Collection getAllThreads() { + public Collection getAllThreads() { + if (objectManager.hasSchema()) { + return objectManager.getAllObjects(TraceObjectThread.class); + } return Collections.unmodifiableCollection(threadStore.asMap().values()); } @Override - public Collection getThreadsByPath(String path) { + public Collection getThreadsByPath(String path) { + if (objectManager.hasSchema()) { + return objectManager.getObjectsByPath(path, TraceObjectThread.class); + } return Collections.unmodifiableCollection(threadsByPath.get(path)); } @Override - public DBTraceThread getLiveThreadByPath(long snap, String path) { + public TraceThread getLiveThreadByPath(long snap, String path) { + if (objectManager.hasSchema()) { + return objectManager.getObjectByPath(snap, path, TraceObjectThread.class); + } try (LockHold hold = LockHold.lock(lock.readLock())) { return threadsByPath.get(path) .stream() @@ -132,16 +156,32 @@ public class DBTraceThreadManager implements TraceThreadManager, DBTraceManager } @Override - public DBTraceThread getThread(long key) { + public TraceThread getThread(long key) { + if (objectManager.hasSchema()) { + return objectManager + .getObjectById(key) + .queryInterface(TraceObjectThread.class); + } return threadStore.getObjectAt(key); } @Override - public Collection getLiveThreads(long snap) { + public Collection getLiveThreads(long snap) { + if (objectManager.hasSchema()) { + try (LockHold hold = LockHold.lock(lock.readLock())) { + return objectManager + .queryAllInterface(Range.singleton(snap), TraceObjectThread.class) + // Exclude the destruction + .filter(thread -> thread.getCreationSnap() <= snap && + snap < thread.getDestructionSnap()) + .collect(Collectors.toSet()); + } + } try (LockHold hold = LockHold.lock(lock.readLock())) { // NOTE: Should be few enough threads that this is fast Collection result = new LinkedHashSet<>(); for (DBTraceThread thread : threadStore.asMap().values()) { + // Don't use .getLifespan().contains(snap). Exclude the destruction. if (thread.getCreationSnap() <= snap && snap < thread.getDestructionSnap()) { result.add(thread); } 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 fff3729036..c2a0274950 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 @@ -18,7 +18,6 @@ package ghidra.trace.database.time; import java.io.IOException; import db.DBRecord; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.Trace; import ghidra.trace.model.Trace.TraceSnapshotChangeType; import ghidra.trace.model.thread.TraceThread; @@ -59,7 +58,7 @@ public class DBTraceSnapshot extends DBAnnotatedObject implements TraceSnapshot public final DBTraceTimeManager manager; - private DBTraceThread eventThread; + private TraceThread eventThread; private TraceSchedule schedule; public DBTraceSnapshot(DBTraceTimeManager manager, DBCachedObjectStore store, diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/ImmutableTraceAddressSnapRange.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/ImmutableTraceAddressSnapRange.java index 8d21f39c66..efffa0c50b 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/ImmutableTraceAddressSnapRange.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/ImmutableTraceAddressSnapRange.java @@ -73,6 +73,12 @@ public class ImmutableTraceAddressSnapRange implements TraceAddressSnapRange { this.space = TraceAddressSnapSpace.forAddressSpace(range.getAddressSpace()); } + public ImmutableTraceAddressSnapRange(AddressRange range, long snap) { + this.range = range; + this.lifespan = DBTraceUtils.toRange(snap, snap); + this.space = TraceAddressSnapSpace.forAddressSpace(range.getAddressSpace()); + } + public ImmutableTraceAddressSnapRange(Address minAddress, Address maxAddress, Range lifespan, EuclideanSpace2D space) { this.range = new AddressRangeImpl(minAddress, maxAddress); 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 3b524d2931..981d399d35 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 @@ -41,6 +41,7 @@ import ghidra.trace.model.program.TraceVariableSnapProgramView; import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.stack.TraceStackManager; import ghidra.trace.model.symbol.*; +import ghidra.trace.model.target.*; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.model.thread.TraceThreadManager; import ghidra.trace.model.time.TraceSnapshot; @@ -53,6 +54,47 @@ import resources.ResourceManager; public interface Trace extends DataTypeManagerDomainObject { ImageIcon TRACE_ICON = ResourceManager.loadImage("images/video-x-generic16.png"); + public static final class TraceObjectChangeType + extends DefaultTraceChangeType { + /** + * An object was created, but not necessarily inserted. + */ + public static final TraceObjectChangeType CREATED = + new TraceObjectChangeType<>(); + /** + * An object's lifespan changed. + */ + public static final TraceObjectChangeType> LIFESPAN_CHANGED = + new TraceObjectChangeType<>(); + /** + * An object was deleted. + */ + public static final TraceObjectChangeType DELETED = + new TraceObjectChangeType<>(); + /** + * An object's value changed. + * + *

+ * If the old value is uniform for the new value's lifespan, that value is passed as the old + * value. Otherwise, {@code null} is passed for the old value. If the value was cleared, + * {@code null} is passed for the new value. + */ + public static final TraceObjectChangeType VALUE_CHANGED = + new TraceObjectChangeType<>(); + /** + * An object's value changed in lifespan. + * + *

+ * This is only called for the value on which {@link TraceObjectValue#setLifespan(Range)} or + * similar is called. If other values are truncated or deleted, there is no event. Listeners + * concerned about a single snap need only check if the snap is contained in the new and old + * lifespans. Listeners concerned about the full timeline can refresh the parent object's + * values, or compute the coalescing and truncation manually. + */ + public static final TraceObjectChangeType> // + VALUE_LIFESPAN_CHANGED = new TraceObjectChangeType<>(); + } + public static final class TraceBookmarkChangeType extends DefaultTraceChangeType { public static final TraceBookmarkChangeType TYPE_ADDED = new TraceBookmarkChangeType<>(); @@ -350,6 +392,8 @@ public interface Trace extends DataTypeManagerDomainObject { TraceModuleManager getModuleManager(); + TraceObjectManager getObjectManager(); + TraceReferenceManager getReferenceManager(); TraceRegisterContextManager getRegisterContextManager(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceUniqueObject.java similarity index 85% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceObject.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceUniqueObject.java index e35c383f1b..bf4dab0bdd 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceObject.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/TraceUniqueObject.java @@ -17,11 +17,18 @@ package ghidra.trace.model; import ghidra.util.database.ObjectKey; -public interface TraceObject { +public interface TraceUniqueObject { /** * Get an opaque unique id for this object, whose hash is immutable * * @return the opaque object id */ ObjectKey getObjectKey(); + + /** + * Check if this object is deleted + * + * @return true if deleted + */ + boolean isDeleted(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java index 0bf904352c..0a8a9d1398 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceBreakpoint.java @@ -23,14 +23,14 @@ import com.google.common.collect.Range; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; import ghidra.trace.model.Trace; -import ghidra.trace.model.TraceObject; +import ghidra.trace.model.TraceUniqueObject; import ghidra.trace.model.thread.TraceThread; import ghidra.util.exception.DuplicateNameException; /** * A breakpoint in a trace */ -public interface TraceBreakpoint extends TraceObject { +public interface TraceBreakpoint extends TraceUniqueObject { /** * Get the trace containing this breakpoint @@ -162,11 +162,11 @@ public interface TraceBreakpoint extends TraceObject { void setEnabled(boolean enabled); /** - * Check whether this breakpoint is enabled or disabled + * Check whether this breakpoint is enabled or disabled at the given snap * * @return true if enabled, false if disabled */ - boolean isEnabled(); + boolean isEnabled(long snap); /** * Set the kinds included in this breakpoint diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java new file mode 100644 index 0000000000..16227eb082 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointLocation.java @@ -0,0 +1,46 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.breakpoint; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetBreakpointLocation; +import ghidra.dbg.target.TargetObject; +import ghidra.program.model.address.AddressRange; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; +import ghidra.util.exception.DuplicateNameException; + +@TraceObjectInfo( + targetIf = TargetBreakpointLocation.class, + shortName = "breakpoint location", + fixedKeys = { + TargetObject.DISPLAY_ATTRIBUTE_NAME, + TargetBreakpointLocation.ADDRESS_ATTRIBUTE_NAME, + TargetBreakpointLocation.LENGTH_ATTRIBUTE_NAME, + TraceObjectBreakpointLocation.KEY_COMMENT, + TraceObjectBreakpointLocation.KEY_RANGE, + }) +public interface TraceObjectBreakpointLocation extends TraceBreakpoint, TraceObjectInterface { + String KEY_COMMENT = "_comment"; + String KEY_RANGE = "_range"; // Duplicates address,length + + TraceObjectBreakpointSpec getSpecification(); + + void setLifespan(Range lifespan) throws DuplicateNameException; + + void setRange(AddressRange range); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java new file mode 100644 index 0000000000..3b01ee821c --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/breakpoint/TraceObjectBreakpointSpec.java @@ -0,0 +1,46 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.breakpoint; + +import java.util.Collection; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetBreakpointSpec; +import ghidra.dbg.target.TargetObject; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; +import ghidra.util.exception.DuplicateNameException; + +/** + * TODO: + * + *

+ * NOTE: When enumerating trace breakpoints, use the locations, not the specifications. + */ +@TraceObjectInfo( + targetIf = TargetBreakpointSpec.class, + shortName = "breakpoint specification", + fixedKeys = { + TargetObject.DISPLAY_ATTRIBUTE_NAME, + TargetBreakpointSpec.EXPRESSION_ATTRIBUTE_NAME, + TargetBreakpointSpec.KINDS_ATTRIBUTE_NAME, + }) +public interface TraceObjectBreakpointSpec extends TraceBreakpoint, TraceObjectInterface { + void setLifespan(Range lifespan) throws DuplicateNameException; + + Collection getLocations(); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryFlag.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryFlag.java index 8cda648739..b485d299e1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryFlag.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryFlag.java @@ -15,6 +15,9 @@ */ package ghidra.trace.model.memory; +import java.util.Collection; +import java.util.EnumSet; + import ghidra.program.model.mem.MemoryBlock; public enum TraceMemoryFlag { @@ -23,6 +26,27 @@ public enum TraceMemoryFlag { READ(MemoryBlock.READ), VOLATILE(MemoryBlock.VOLATILE); + public static EnumSet fromBits(EnumSet flags, int mask) { + for (TraceMemoryFlag f : TraceMemoryFlag.values()) { + if ((mask & f.getBits()) != 0) { + flags.add(f); + } + } + return flags; + } + + public static Collection fromBits(int mask) { + return fromBits(EnumSet.noneOf(TraceMemoryFlag.class), mask); + } + + public static byte toBits(Collection flags) { + byte bits = 0; + for (TraceMemoryFlag f : flags) { + bits |= f.getBits(); + } + return bits; + } + private final byte bits; TraceMemoryFlag(int mask) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryRegion.java index b0d6b49090..ec65255035 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryRegion.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceMemoryRegion.java @@ -21,13 +21,13 @@ import com.google.common.collect.Range; import ghidra.program.model.address.*; import ghidra.trace.model.Trace; -import ghidra.trace.model.TraceObject; +import ghidra.trace.model.TraceUniqueObject; import ghidra.util.exception.DuplicateNameException; /** * A region of mapped target memory in a trace */ -public interface TraceMemoryRegion extends TraceObject { +public interface TraceMemoryRegion extends TraceUniqueObject { /** * Get the trace containing this region diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectMemoryRegion.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectMemoryRegion.java new file mode 100644 index 0000000000..e60dec23a1 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectMemoryRegion.java @@ -0,0 +1,45 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.memory; + +import java.util.Collection; +import java.util.Set; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetMemoryRegion; +import ghidra.dbg.target.TargetObject; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; + +@TraceObjectInfo( + targetIf = TargetMemoryRegion.class, + shortName = "region", + fixedKeys = { + TargetObject.DISPLAY_ATTRIBUTE_NAME, + TargetMemoryRegion.RANGE_ATTRIBUTE_NAME + }) +public interface TraceObjectMemoryRegion extends TraceMemoryRegion, TraceObjectInterface { + String KEY_VOLATILE = "_volatile"; + + void setFlags(Range lifespan, Collection flags); + + void addFlags(Range lifespan, Collection flags); + + void clearFlags(Range lifespan, Collection flags); + + Set getFlags(long snap); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java new file mode 100644 index 0000000000..eccfa873fa --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/memory/TraceObjectRegister.java @@ -0,0 +1,52 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.memory; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetRegister; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; +import ghidra.trace.model.thread.TraceObjectThread; + +@TraceObjectInfo( + // NB. Originally meant to describe the register, it now also describes its value + targetIf = TargetRegister.class, + shortName = "register", + fixedKeys = { + TargetRegister.LENGTH_ATTRIBUTE_NAME + }) +public interface TraceObjectRegister extends TraceObjectInterface { + String KEY_STATE = "_state"; + + TraceObjectThread getThread(); + + String getName(); + + int getLength(); + + void setValue(Range lifespan, byte[] value); + + byte[] getValue(long snap); + + void setState(Range lifespan, TraceMemoryState state); + + TraceMemoryState getState(long snap); + + // TODO: getAddress()? + // Would provide info for memory-mapped registers. + // Could also communicate structure / aliasing. +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceModule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceModule.java index 2382c3018c..20cc839100 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceModule.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceModule.java @@ -21,7 +21,7 @@ import com.google.common.collect.Range; import ghidra.program.model.address.*; import ghidra.trace.model.Trace; -import ghidra.trace.model.TraceObject; +import ghidra.trace.model.TraceUniqueObject; import ghidra.util.exception.DuplicateNameException; /** @@ -30,7 +30,7 @@ import ghidra.util.exception.DuplicateNameException; *

* This also serves as a namespace for storing the module's sections. */ -public interface TraceModule extends TraceObject { +public interface TraceModule extends TraceUniqueObject { /** * Get the trace containing this module diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceObjectModule.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceObjectModule.java new file mode 100644 index 0000000000..90ca1a04e5 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceObjectModule.java @@ -0,0 +1,39 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.modules; + +import java.util.Collection; + +import ghidra.dbg.target.TargetModule; +import ghidra.dbg.target.TargetObject; +import ghidra.trace.database.module.TraceObjectSection; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; + +@TraceObjectInfo( + targetIf = TargetModule.class, + shortName = "module", + fixedKeys = { + TargetObject.DISPLAY_ATTRIBUTE_NAME, + TargetModule.RANGE_ATTRIBUTE_NAME + }) +public interface TraceObjectModule extends TraceModule, TraceObjectInterface { + @Override + Collection getSections(); + + @Override + TraceObjectSection getSectionByName(String sectionName); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceSection.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceSection.java index e920698c55..490f3d5b9d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceSection.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceSection.java @@ -18,13 +18,13 @@ package ghidra.trace.model.modules; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; import ghidra.trace.model.Trace; -import ghidra.trace.model.TraceObject; +import ghidra.trace.model.TraceUniqueObject; import ghidra.util.exception.DuplicateNameException; /** * A section of a module in a trace */ -public interface TraceSection extends TraceObject { +public interface TraceSection extends TraceUniqueObject { /** * Get the trace containing this section @@ -85,14 +85,16 @@ public interface TraceSection extends TraceObject { * @see #getRange() */ default Address getStart() { - return getRange().getMinAddress(); + AddressRange range = getRange(); + return range == null ? null : range.getMinAddress(); } /** * @see #getRange() */ default Address getEnd() { - return getRange().getMaxAddress(); + AddressRange range = getRange(); + return range == null ? null : range.getMaxAddress(); } /** diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceStaticMapping.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceStaticMapping.java index 43c031b77a..1582416b49 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceStaticMapping.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/modules/TraceStaticMapping.java @@ -23,12 +23,12 @@ import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRange; import ghidra.program.model.listing.Program; import ghidra.trace.model.Trace; -import ghidra.trace.model.TraceObject; +import ghidra.trace.model.TraceUniqueObject; /** * A mapped range from this trace to a Ghidra {@link Program} */ -public interface TraceStaticMapping extends TraceObject { +public interface TraceStaticMapping extends TraceUniqueObject { /** * Get the "from" trace, i.e., the trace containing this mapping diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/program/TraceProgramView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/program/TraceProgramView.java index 800d45650d..dfb2ee86f7 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/program/TraceProgramView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/program/TraceProgramView.java @@ -50,7 +50,7 @@ public interface TraceProgramView extends Program { TraceTimeViewport getViewport(); /** - * Get the latest snap + * Get the trace's latest snap * * @return the maximum snap */ diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceObjectStack.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceObjectStack.java new file mode 100644 index 0000000000..f380e52728 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceObjectStack.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.stack; + +import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.TargetStack; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; + +@TraceObjectInfo( + targetIf = TargetStack.class, + shortName = "stack", + fixedKeys = { + TargetObject.DISPLAY_ATTRIBUTE_NAME, + }) +public interface TraceObjectStack extends TraceStack, TraceObjectInterface { + +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceObjectStackFrame.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceObjectStackFrame.java new file mode 100644 index 0000000000..2c4063e8a3 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceObjectStackFrame.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.stack; + +import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.TargetStackFrame; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; + +@TraceObjectInfo( + targetIf = TargetStackFrame.class, + shortName = "frame", + fixedKeys = { + TargetObject.DISPLAY_ATTRIBUTE_NAME, + }) +public interface TraceObjectStackFrame extends TraceStackFrame, TraceObjectInterface { + +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStack.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStack.java index e71b0cd615..a2b2c37bcb 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStack.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/stack/TraceStack.java @@ -17,6 +17,7 @@ package ghidra.trace.model.stack; import java.util.List; +import ghidra.trace.model.TraceUniqueObject; import ghidra.trace.model.thread.TraceThread; /** @@ -29,7 +30,7 @@ import ghidra.trace.model.thread.TraceThread; * for that analysis. If this information wasn't recorded during a session, this can store the * result of that analysis. */ -public interface TraceStack { +public interface TraceStack extends TraceUniqueObject { /** * Get the thread whose stack this is diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/DuplicateKeyException.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/DuplicateKeyException.java new file mode 100644 index 0000000000..0aad18719b --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/DuplicateKeyException.java @@ -0,0 +1,22 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target; + +public class DuplicateKeyException extends RuntimeException { + public DuplicateKeyException(String key) { + super(key); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java new file mode 100644 index 0000000000..bfc75fb49a --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObject.java @@ -0,0 +1,444 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target; + +import java.util.Collection; +import java.util.stream.Stream; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.dbg.util.PathPredicates; +import ghidra.trace.model.Trace; +import ghidra.trace.model.TraceUniqueObject; + +/** + * The trace record of an observed {@link TargetObject} + * + *

+ * See {@link TargetObject} for information about how objects and the model schema are related in a + * debugger model. This trace object records a target object and a subset of its children into the + * database with additional timing information. For objects implementing specific + * {@link TargetObject} interfaces, a corresponding {@link TraceObjectInterface} can be retrieved. + * In many cases, such interfaces are just wrappers. + */ +public interface TraceObject extends TraceUniqueObject { + /** + * Get the trace containing this object + * + * @return the trace + */ + Trace getTrace(); + + /** + * Get the root of the tree containing this object + * + * @return the root + */ + TraceObject getRoot(); + + /** + * Get the canonical path of this object + * + * @return the path + */ + TraceObjectKeyPath getCanonicalPath(); + + /** + * Inserts this object at its canonical path for its lifespan + * + *

+ * Any ancestor which does not exist is created with the same lifespan as this object. Values + * are set with the same lifespan. Only the canonical path is considered when looking for + * existing ancestry. Any whose lifespan intersects that of this object is considered + * "existing." If an existing ancestor is detached, this object will still become its successor, + * and the resulting subtree will remain detached. + * + * @param resolution the rule for handling duplicate keys when setting values. + */ + void insert(ConflictResolution resolution); + + /** + * Check if this object is the root + * + * @return true if root + */ + boolean isRoot(); + + /** + * Get all paths actually leading to this object, from the root, within the given span + * + * @param span the span which every value entry on each path must intersect + * @return the paths + */ + Stream getAllPaths(Range span); + + /** + * Specifies a strategy for resolving duplicate keys + * + *

+ * Values are not permitted to have intersecting lifespans if they have the same parent and key, + * since this would imply the value is not unique for a given parent, key, and snap. Thus, when + * values and lifespans are being set that would result in conflicting entries, the conflict + * must be resolved, either by clearing the span or by denying the change. + */ + enum ConflictResolution { + /** + * Truncate, split, or delete conflicting entries to make way for the specified lifespan + */ + TRUNCATE, + /** + * Throw an exception if the specified lifespan would result in conflicting entries + */ + DENY; + } + + /** + * Set the lifespan of this object + * + *

+ * NOTE: Objects with intersecting lifespans are not checked for duplicate canonical paths. + * However, their parent value entries are checked for conflicts. Thus, at any snap, it is + * impossible for any two objects with equal canonical paths to both exist at their canonical + * locations. + * + * @param lifespan the new lifespan + */ + void setLifespan(Range lifespan); + + /** + * Get the lifespan of this object + * + * @return the lifespan + */ + Range getLifespan(); + + /** + * Set the minimum snap of this object + * + * @see #setLifespan(Range) + * @param minSnap the minimum snap, or {@link Long#MIN_VALUE} for "since the beginning of time" + */ + void setMinSnap(long minSnap); + + /** + * Get the minimum snap of this object + * + * @return the minimum snap, or {@link Long#MIN_VALUE} for "since the beginning of time" + */ + long getMinSnap(); + + /** + * Set the maximum snap of this object + * + * @see #setLifespan(Range) + * @param maxSnap the maximum snap, or {@link Long#MAX_VALUE} for "to the end of time" + */ + void setMaxSnap(long maxSnap); + + /** + * Get the maximum snap of this object + * + * @return the maximum snap, or {@link Long#MAX_VALUE} for "to the end of time" + */ + long getMaxSnap(); + + /** + * Get all the interface classes provided by this object, according to the schema + * + * @return the collection of interface classes + */ + Collection> getInterfaces(); + + /** + * Request the specified interface provided by this object + * + * @param the type of the interface + * @param ifClass the class of the interface + * @return the interface, or null if not provided + */ + I queryInterface(Class ifClass); + + /** + * Get all values whose child is this object + * + * @return the parent values + */ + Collection getParents(); + + /** + * Get all values (elements and attributes) of this object + * + * @return the values + */ + Collection getValues(); + + /** + * Get values with the given key intersecting the given span ordered by time + * + * @param span the span + * @param key the key + * @param forward true to order from least- to most-recent, false for most- to least-recent + * @return the stream of values + */ + Stream getOrderedValues(Range span, String key, + boolean forward); + + /** + * Get all elements of this object + * + * @return the element values + */ + Collection getElements(); + + /** + * Get all attributes of this object + * + * @return the attribute values + */ + Collection getAttributes(); + + /** + * Get the value for the given snap and key + * + * @param snap the snap + * @param key the key + * @return the value entry + */ + TraceObjectValue getValue(long snap, String key); + + /** + * Get the value for the given snap and element index + * + *

+ * This is equivalent to {@link #getValue(long, String)}, but converts index to a key, i.e., + * adds brackets. + * + * @param snap the snap + * @param index the index + * @return the value entry + */ + TraceObjectValue getElement(long snap, String index); + + /** + * Get the value for the given snap and element index + * + *

+ * This is equivalent to {@link #getElement(long, String)}, but converts index to a string in + * decimal. + * + * @param snap the snap + * @param index the index + * @return the value entry + */ + TraceObjectValue getElement(long snap, long index); + + /** + * Get the value for the given snap and attribute name + * + *

+ * This is equivalent to {@link #getValue(long, String)}, except it validates that name is not + * an index. + * + * @param snap the snap + * @param name the name + * @return the value entry + */ + TraceObjectValue getAttribute(long snap, String name); + + /** + * Stream all ancestor values of this object matching the given predicates, intersecting the + * given span + * + * @param span a span which values along the path must intersect + * @param rootPredicates the predicates for matching path keys, relative to the root + * @return the stream of matching paths to values + */ + Stream getAncestors(Range span, + PathPredicates rootPredicates); + + /** + * Stream all successor values of this object matching the given predicates, intersecting the + * given span + * + * @param span a span which values along the path must intersect + * @param relativePredicates the predicates for matching path keys, relative to this object + * @return the stream of matching paths to values + */ + Stream getSuccessors(Range span, + PathPredicates relativePredicates); + + /** + * Stream all successor values of this object at the given relative path, intersecting the given + * span, ordered by time. + * + * @param span the span which values along the path must intersect + * @param relativePath the path relative to this object + * @param forward true to order from least- to most-recent, false for most- to least-recent + * @return the stream of value paths + */ + Stream getOrderedSuccessors(Range span, + TraceObjectKeyPath relativePath, boolean forward); + + /** + * Set a value for the given lifespan + * + * @param lifespan the lifespan of the value + * @param key the key to set + * @param value the new value + * @param resolution determines how to resolve conflicting keys with intersecting lifespans + * @return the created value entry + */ + TraceObjectValue setValue(Range lifespan, String key, Object value, + ConflictResolution resolution); + + /** + * Set a value for the given lifespan, truncating existing entries + * + * @param lifespan the lifespan of the value + * @param key the key to set + * @param value the new value + * @return the created value entry + */ + TraceObjectValue setValue(Range lifespan, String key, Object value); + + /** + * Set an attribute for the given lifespan + * + *

+ * This is equivalent to {@link #setValue(Range, String, Object)}, except it verifies the key is + * an attribute name. + * + * @param lifespan the lifespan of the attribute + * @param name the name to set + * @param value the new value + * @return the created value entry + */ + TraceObjectValue setAttribute(Range lifespan, String name, Object value); + + /** + * Set an element for the given lifespan + * + *

+ * This is equivalent to {@link #setValue(Range, String, Object)}, except it converts the index + * to a key, i.e., add brackets. + * + * @param lifespan the lifespan of the element + * @param index the index to set + * @param value the new value + * @return the created value entry + */ + TraceObjectValue setElement(Range lifespan, String index, Object value); + + /** + * Set an element for the given lifespan + * + * @param lifespan the lifespan of the element + * @param index the index to set + * @param value the new value + * @return the created value entry + */ + TraceObjectValue setElement(Range lifespan, long index, Object value); + + /** + * Get the (target) schema for this object + * + * @return the schema + */ + TargetObjectSchema getTargetSchema(); + + /** + * Search for ancestors providing the given interface and retrieve those interfaces + * + * @param the interface type + * @param span the span which the found objects must intersect + * @param ifClass the interface class + * @return the stream of interfaces + */ + Stream queryAncestorsInterface(Range span, + Class ifClass); + + /** + * Search for ancestors on the canonical path providing the given interface + * + *

+ * The object may not yet be inserted at its canonical path + * + * @param the interface type + * @param span the span which the found objects must intersect + * @param ifClass the interface class + * @return the stream of interfaces + */ + Stream queryCanonicalAncestorsInterface( + Range span, Class ifClass); + + /** + * Search for successors providing the given interface and retrieve those interfaces + * + * @param the interface type + * @param span the span which the found objects must intersect + * @param ifClass the interface class + * @return the stream of interfaces + */ + Stream querySuccessorsInterface(Range span, + Class ifClass); + + /** + * Delete this object along with parent and child value entries referring to it + * + *

+ * Note, this does not delete the children or any successors. Use {@link #deleteTree()} to + * delete an entire subtree, regardless of lifespan. It is not recommended to invoke this on the + * root object, since it cannot be replaced without first clearing the manager. + */ + void delete(); + + /** + * Delete this object and its successors along with value entries referring to any + * + *

+ * It is not recommended to invoke this on the root object. Instead, use + * {@link TraceObjectManager#clear()}. The root object cannot be replaced without first clearing + * the manager. + */ + void deleteTree(); + + /** + * Check if this object has been deleted + * + * @return true if the object has been deleted + */ + @Override + boolean isDeleted(); + + /** + * Modify the lifespan or delete this object, such that it no longer intersects the given span. + * + *

+ * If the given span and the current lifespan are already disjoint, this does nothing. If the + * given span splits the current lifespan in two, an exception is thrown. This is because the + * two resulting objects ought to be identical, but they cannot be. Instead the one object + * should remain alive, and the edge(s) pointing to it should be truncated. In other words, a + * single object cannot vanish and then later re-appear, but it can be unlinked and then later + * become relinked. + * + * @param span the span to clear + * @return this if the one object remains, null if the object is deleted. + * @throws IllegalArgumentException if the given span splits the current lifespan in two + */ + TraceObject truncateOrDelete(Range span); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectInterface.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectInterface.java new file mode 100644 index 0000000000..8fb1ff6c2e --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectInterface.java @@ -0,0 +1,20 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target; + +public interface TraceObjectInterface { + TraceObject getObject(); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java new file mode 100644 index 0000000000..58f3d77061 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectKeyPath.java @@ -0,0 +1,115 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target; + +import java.util.*; + +import ghidra.dbg.util.PathUtils; +import ghidra.dbg.util.PathUtils.PathComparator; + +public final class TraceObjectKeyPath implements Comparable { + + public static TraceObjectKeyPath of(List keyList) { + return new TraceObjectKeyPath(List.copyOf(keyList)); + } + + public static TraceObjectKeyPath of(String... keys) { + return new TraceObjectKeyPath(List.of(keys)); + } + + public static TraceObjectKeyPath parse(String path) { + return new TraceObjectKeyPath(PathUtils.parse(path)); + } + + private final List keyList; + private final int hash; + + private TraceObjectKeyPath(List keyList) { + this.keyList = keyList; + this.hash = Objects.hash(keyList); + } + + @Override + public int hashCode() { + return hash; + } + + @Override + public int compareTo(TraceObjectKeyPath that) { + if (this == that) { + return 0; + } + return PathComparator.KEYED.compare(this.keyList, that.keyList); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TraceObjectKeyPath)) { + return false; + } + TraceObjectKeyPath that = (TraceObjectKeyPath) obj; + return this.keyList.equals(that.keyList); + } + + public List getKeyList() { + return keyList; + } + + public boolean isRoot() { + return keyList.isEmpty(); + } + + public TraceObjectKeyPath key(String name) { + return new TraceObjectKeyPath(PathUtils.extend(keyList, name)); + } + + public String key() { + return PathUtils.getKey(keyList); + } + + public TraceObjectKeyPath index(long index) { + return index(PathUtils.makeIndex(index)); + } + + public TraceObjectKeyPath index(String index) { + return new TraceObjectKeyPath(PathUtils.index(keyList, index)); + } + + public String index() { + return PathUtils.getIndex(keyList); + } + + @Override + public String toString() { + return PathUtils.toString(keyList); + } + + public TraceObjectKeyPath parent() { + List pkl = PathUtils.parent(keyList); + return pkl == null ? null : new TraceObjectKeyPath(pkl); + } + + public TraceObjectKeyPath extend(List subKeyList) { + return new TraceObjectKeyPath(PathUtils.extend(keyList, subKeyList)); + } + + public TraceObjectKeyPath extend(String... subKeyList) { + return extend(Arrays.asList(subKeyList)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java new file mode 100644 index 0000000000..6edd65febe --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectManager.java @@ -0,0 +1,163 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target; + +import java.util.Collection; +import java.util.stream.Stream; + +import com.google.common.collect.Range; + +import ghidra.dbg.DebuggerObjectModel; +import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.schema.TargetObjectSchema; +import ghidra.dbg.util.PathPredicates; +import ghidra.program.model.address.AddressRange; +import ghidra.trace.model.Trace; + +/** + * A store of objects observed over time in a trace + */ +public interface TraceObjectManager { + + /** + * Get the trace to which the object manager belongs + * + * @return the trace + */ + Trace getTrace(); + + /** + * Creates the root object of the model, fixing its schema + * + *

+ * Note the schema cannot be changed once the root object is created. The only means to "change" + * the schema is to delete the root object (and thus the entire tree) then re-create the root + * object with the new schema. + * + * @param schema the schema + * @return the new object + */ + TraceObjectValue createRootObject(TargetObjectSchema schema); + + /** + * Create an object with the given canonical path having the given lifespan + * + * @param path the object's canonical path + * @param lifespan the initial lifespan + * @return the new object + */ + TraceObject createObject(TraceObjectKeyPath path, Range lifespan); + + /** + * Get the schema of the root object + * + *

+ * NOTE: The interface classes specified by the schema are those for {@link TargetObject}. The + * interfaces in the database are different, and not all may have an analog. + * + * @return the schema + */ + TargetObjectSchema getRootSchema(); + + /** + * Get the root object, if it has been created + * + * @return the root object, or null + */ + TraceObject getRootObject(); + + /** + * Get the object with the given database key, if it exists + * + * @param key the desired object's key + * @return the object, or null + */ + TraceObject getObjectById(long key); + + /** + * Get objects in the database having the given canonical path + * + * @param path the canonical path of the desired objects + * @return the collection of objects + */ + Collection getObjectsByCanonicalPath(TraceObjectKeyPath path); + + /** + * Get objects in the database having the given path intersecting the given span + * + * @param path the path of the desired objects + * @param span the span that desired objects' lifespans must intersect + * @return the iterable of objects + */ + Stream getObjectsByPath(Range span, + TraceObjectKeyPath path); + + /** + * Get value entries in the database matching the given predicates intersecting the given span + * + *

+ * While the manager does not maintain integrity wrt. child lifespans and that of their parents, + * nor even the connectivity of objects to their canonical parents, this search depends on that + * consistency. An object may not be discovered unless it is properly connected to the root + * object. Furthermore, it will not be discovered unless it and its ancestors' lifespans all + * intersect the given span. + * + * @param span the span that desired objects' lifespans must intersect + * @param predicates predicates to match the desired objects + * @return an iterator over the matching objects + */ + Stream getValuePaths(Range span, + PathPredicates predicates); + + /** + * Get all the objects in the database + * + * @return the collection of all objects + */ + Collection getAllObjects(); + + /** + * Get all address-ranged values intersecting the given span and address range + * + * @param span the span that desired values lifespans must intersect + * @param range the range that desired address-ranged values must intersect + * @return the collection of values + */ + Collection getValuesIntersecting(Range span, + AddressRange range); + + /** + * Get all interfaces of the given type in the database + * + * @param the type of the desired interface + * @param span the span that desired objects must intersect + * @param ifClass the class of the desired interface + * @return the collection of all instances of the given interface + */ + Stream queryAllInterface(Range span, + Class ifClass); + + /** + * Delete the entire object model, including the schema + * + *

+ * This is the only mechanism to modify the schema. This should almost never be necessary, + * because a {@link DebuggerObjectModel} should provide its immutable schema immediately. + * Nevertheless, the database permits schema modification, but requires that the entire model be + * replaced. + */ + void clear(); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java new file mode 100644 index 0000000000..cc9acaa65a --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValPath.java @@ -0,0 +1,36 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target; + +import java.util.List; + +public interface TraceObjectValPath extends Comparable { + List getEntryList(); + + List getKeyList(); + + boolean contains(TraceObjectValue entry); + + TraceObjectValue getFirstEntry(); + + TraceObject getFirstParent(TraceObject ifEmpty); + + TraceObjectValue getLastEntry(); + + Object getLastValue(Object ifEmpty); + + TraceObject getLastChild(TraceObject ifEmpty); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValue.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValue.java new file mode 100644 index 0000000000..0a01634496 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/TraceObjectValue.java @@ -0,0 +1,167 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target; + +import com.google.common.collect.Range; + +import ghidra.trace.model.Trace; +import ghidra.trace.model.target.TraceObject.ConflictResolution; + +public interface TraceObjectValue { + + /** + * Get the trace containing this value entry + * + * @return the trace + */ + Trace getTrace(); + + /** + * Get the parent object of this entry + * + * @return the parent + */ + TraceObject getParent(); + + /** + * Get the key identifying this child to its parent + * + * @return the key + */ + String getEntryKey(); + + /** + * Get the value + * + * @return the value + */ + Object getValue(); + + /** + * Get the value as an object + * + * @return the child + * @throws ClassCastException if the value is not an object + */ + TraceObject getChild(); + + /** + * Check if this value represents its child's canonical location + * + *

+ * The value is canonical if the parent's canonical path extended by this value's key gives the + * child's canonical path. If the value is not a child object, the value cannot be canonical. + * + * @return true if canonical + */ + boolean isCanonical(); + + /** + * Set the lifespan of this entry, truncating duplicates + * + * @param lifespan the new lifespan + */ + void setLifespan(Range lifespan); + + /** + * Set the lifespan of this entry + * + *

+ * NOTE: For storage efficiency, when expanding the lifespan, the manager may coalesce + * this value with intersecting values having equal keys and values. Thus, the resulting + * lifespan may be larger than specified. + * + *

+ * Values cannot intersect and have the same key, otherwise the value of that key could not be + * uniquely determined at a given snap. Thus, when lifespans are being adjusted, such conflicts + * must be resolved. + * + * @param lifespan the new lifespan + * @param resolution specifies how to resolve duplicate keys with intersecting lifespans + */ + void setLifespan(Range span, ConflictResolution resolution); + + /** + * Get the lifespan of this entry + * + * @return the lifespan + */ + Range getLifespan(); + + /** + * Set the minimum snap of this entry + * + * @see #setLifespan(Range) + * @param minSnap the minimum snap, or {@link Long#MIN_VALUE} for "since the beginning of time" + */ + void setMinSnap(long minSnap); + + /** + * Get the minimum snap of this entry + * + * @return the minimum snap, or {@link Long#MIN_VALUE} for "since the beginning of time" + */ + long getMinSnap(); + + /** + * Set the maximum snap of this entry + * + * @see #setLifespan(Range) + * @param maxSnap the maximum snap, or {@link Long#MAX_VALUE} for "to the end of time" + */ + void setMaxSnap(long maxSnap); + + /** + * Get the maximum snap of this entry + * + * @return the maximum snap, or {@link Long#MAX_VALUE} for "to the end of time" + */ + long getMaxSnap(); + + /** + * Delete this entry + * + *

+ * If this entry is part of the child object's canonical path, then the child is also deleted. + */ + void delete(); + + /** + * Delete this entry and, if it is canonical, its successors + */ + void deleteTree(); + + /** + * Check if this value entry has been deleted + * + * @return true if the entry has been deleted + */ + boolean isDeleted(); + + /** + * Modify the lifespan or delete this entry, such that it no longer intersects the given span. + * + *

+ * If the given span and the current lifespan are already disjoint, this does nothing. If the + * given span splits the current lifespan in two, then a new entry is created for the later + * lifespan. + * + * @param span the span to clear + * @return this if the one entry remains, null if the entry is deleted, or the generated entry + * if a second is created. + */ + TraceObjectValue truncateOrDelete(Range span); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInfo.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInfo.java new file mode 100644 index 0000000000..1b80ad43ad --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInfo.java @@ -0,0 +1,49 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target.annot; + +import java.lang.annotation.*; + +import ghidra.dbg.target.TargetObject; +import ghidra.trace.model.target.TraceObjectManager; + +/** + * Information about a trace target interface + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface TraceObjectInfo { + /** + * The target interface corresponding to this trace interface + * + *

+ * TODO: I really don't like this here. I would rather the schema interface names were mappable + * to a chosen domain, rather than being fixed on {@link TargetObject}.... In any case, this is + * used to ensure that {@link TraceObjectManager#queryAllInterface(Class)} and related have a + * means of translating from the trace domain into the target domain. + */ + Class targetIf(); + + /** + * A short name for this interface type + */ + String shortName(); + + /** + * Keys intrinsic to this interface, whose values are fixed during the object's lifespan + */ + String[] fixedKeys(); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java new file mode 100644 index 0000000000..7de8cbd8b7 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/target/annot/TraceObjectInterfaceUtils.java @@ -0,0 +1,86 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.target.annot; + +import java.util.Collection; +import java.util.List; + +import com.google.common.collect.Range; + +import ghidra.dbg.target.TargetObject; +import ghidra.program.model.address.*; +import ghidra.trace.model.target.*; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.util.LockHold; +import ghidra.util.exception.DuplicateNameException; + +public enum TraceObjectInterfaceUtils { + ; + + public static TraceObjectInfo requireAnnotation(Class traceIf) { + TraceObjectInfo annot = traceIf.getAnnotation(TraceObjectInfo.class); + if (annot == null) { + throw new IllegalArgumentException( + traceIf + " is missing @" + TraceObjectInfo.class + " annotation"); + } + return annot; + } + + public static Class toTargetIf( + Class traceIf) { + return requireAnnotation(traceIf).targetIf(); + } + + public static String getShortName(Class traceIf) { + return requireAnnotation(traceIf).shortName(); + } + + public static Collection getFixedKeys( + Class traceIf) { + return List.of(requireAnnotation(traceIf).fixedKeys()); + } + + public static void setLifespan(Class traceIf, + TraceObject object, Range lifespan) throws DuplicateNameException { + try (LockHold hold = object.getTrace().lockWrite()) { + for (TraceObjectValue val : object.getParents()) { + if (val.isCanonical() && !val.isDeleted()) { + val.setLifespan(lifespan, ConflictResolution.DENY); + } + } + } + catch (DuplicateKeyException e) { + throw new DuplicateNameException( + "Duplicate " + getShortName(traceIf) + ": " + e.getMessage()); + } + object.setLifespan(lifespan); + long lower = object.getMinSnap(); + for (String key : getFixedKeys(traceIf)) { + TraceObjectValue val = object.getValue(lower, key); + if (val != null) { + val.setLifespan(lifespan, ConflictResolution.TRUNCATE); + } + } + } + + public static T getValue(TraceObject object, long snap, String key, Class cls, T def) { + TraceObjectValue value = object.getValue(snap, key); + if (value == null) { + return def; + } + return cls.cast(value.getValue()); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceObjectThread.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceObjectThread.java new file mode 100644 index 0000000000..4b52e3a393 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceObjectThread.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.model.thread; + +import ghidra.dbg.target.TargetObject; +import ghidra.dbg.target.TargetThread; +import ghidra.trace.model.target.TraceObjectInterface; +import ghidra.trace.model.target.annot.TraceObjectInfo; + +@TraceObjectInfo( + targetIf = TargetThread.class, + shortName = "thread", + fixedKeys = { + TargetObject.DISPLAY_ATTRIBUTE_NAME, + TraceObjectThread.KEY_COMMENT + }) +public interface TraceObjectThread extends TraceThread, TraceObjectInterface { + String KEY_COMMENT = "_comment"; +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThread.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThread.java index 2f351a2039..c3c14599e5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThread.java @@ -21,13 +21,13 @@ import com.google.common.collect.Range; import ghidra.program.model.lang.Register; import ghidra.trace.model.Trace; -import ghidra.trace.model.TraceObject; +import ghidra.trace.model.TraceUniqueObject; import ghidra.util.exception.DuplicateNameException; /** * A thread in a trace */ -public interface TraceThread extends TraceObject { +public interface TraceThread extends TraceUniqueObject { /** * Get the trace containing this thread diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThreadManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThreadManager.java index edab6981fa..4e26ec0dc8 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThreadManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/thread/TraceThreadManager.java @@ -45,7 +45,7 @@ public interface TraceThreadManager { * Add a thread with the given lifespan * * @param path the "full name" of the thread - * @param name "short name" of the thread + * @param display "short name" of the thread * @param lifespan the lifespan of the thread * @return the new thread * @throws DuplicateNameException if a thread with the given full name already exists within an @@ -108,6 +108,10 @@ public interface TraceThreadManager { /** * Get live threads at the given snap, ordered eldest first * + *

+ * Note that thread whose destruction was observed at the given snap are not considered alive, + * i.e, the upper end of the lifespan is treated as open. + * * @param snap the snap * @return the collection */ diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceTimeViewport.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceTimeViewport.java index d008157155..4c18052092 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceTimeViewport.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/DefaultTraceTimeViewport.java @@ -148,6 +148,9 @@ public class DefaultTraceTimeViewport implements TraceTimeViewport { @Override public boolean isCompletelyVisible(AddressRange range, Range lifespan, T object, Occlusion occlusion) { + if (range == null) { + return false; + } try (LockHold hold = trace.lockRead()) { synchronized (ordered) { for (Range rng : ordered) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceAddressSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceAddressSpace.java index e282f6e327..523db0df7d 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceAddressSpace.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/TraceAddressSpace.java @@ -18,6 +18,15 @@ package ghidra.trace.util; import ghidra.program.model.address.AddressSpace; import ghidra.trace.model.thread.TraceThread; +/** + * Identify the "full" address space in a trace. + * + *

+ * Whenever the address space is {@code register}, then the thread and frame level become + * necessarily to uniquely identify it. This will be deprecated when either, 1) unique register + * overlay spaces are created for each thread/frame, or 2) register values are fully transitioned to + * object model storage. + */ public interface TraceAddressSpace { AddressSpace getAddressSpace(); 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 7ca4ceda1b..378722387c 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 @@ -49,7 +49,6 @@ import ghidra.trace.database.language.DBTraceGuestLanguage; import ghidra.trace.database.listing.*; import ghidra.trace.database.memory.DBTraceMemoryManager; import ghidra.trace.database.symbol.DBTraceReference; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.*; import ghidra.trace.model.language.TraceGuestLanguage; @@ -216,7 +215,7 @@ public class ToyDBTraceBuilder implements AutoCloseable { String typeName, String category, String comment) throws DuplicateNameException { Register register = language.getRegister(registerName); assertNotNull(register); - DBTraceThread thread = getOrAddThread(threadName, snap); + TraceThread thread = getOrAddThread(threadName, snap); DBTraceBookmarkType type = getOrAddBookmarkType(typeName); DBTraceBookmarkManager manager = trace.getBookmarkManager(); DBTraceBookmarkRegisterSpace space = manager.getBookmarkRegisterSpace(thread, true); @@ -282,14 +281,14 @@ public class ToyDBTraceBuilder implements AutoCloseable { return instruction; } - public DBTraceThread getOrAddThread(String name, long creationSnap) + public TraceThread getOrAddThread(String name, long creationSnap) throws DuplicateNameException { DBTraceThreadManager manager = trace.getThreadManager(); - Collection threads = manager.getThreadsByPath(name); + Collection threads = manager.getThreadsByPath(name); if (threads != null && !threads.isEmpty()) { return threads.iterator().next(); } - return (DBTraceThread) manager.createThread(name, creationSnap); + return manager.createThread(name, creationSnap); } public DBTraceReference addMemoryReference(long creationSnap, Address from, Address to) { @@ -338,7 +337,9 @@ public class ToyDBTraceBuilder implements AutoCloseable { @Override public void close() { - trace.release(this); + if (trace.getConsumerList().contains(this)) { + trace.release(this); + } } public Language getLanguage(String id) throws LanguageNotFoundException { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerObjectTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerObjectTest.java new file mode 100644 index 0000000000..ca03586491 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerObjectTest.java @@ -0,0 +1,64 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.breakpoint; + +import org.junit.Before; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.util.database.UndoableTransaction; + +public class DBTraceBreakpointManagerObjectTest extends DBTraceBreakpointManagerTest { + + protected SchemaContext ctx; + + @Before + public void setUpObjectsMode() throws Exception { + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = b.startTransaction()) { + b.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } + + @Override + public void testSplitAndSet() throws Exception { + // This method is not supported in objects mode. + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java index fc1ef72606..4de23d7418 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/breakpoint/DBTraceBreakpointManagerTest.java @@ -55,31 +55,34 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat @Test public void testAddBreakpoint() throws Exception { try (UndoableTransaction tid = b.startTransaction()) { - breakpointManager.addBreakpoint("Breaks[0]", Range.closed(0L, 10L), b.addr(0x00400000), + breakpointManager.addBreakpoint("Breakpoints[0]", Range.closed(0L, 10L), + b.addr(0x00400000), Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE), true, "main"); } try (UndoableTransaction tid = b.startTransaction()) { - breakpointManager.addBreakpoint("Breaks[0]", Range.closed(0L, 10L), + breakpointManager.addBreakpoint("Breakpoints[0]", Range.closed(0L, 10L), b.range(0x00400000, 0x00400003), Set.of(), Set.of(), false, "duplicate"); } catch (DuplicateNameException e) { // pass } - assertEquals(1, breakpointManager.getBreakpointsByPath("Breaks[0]").size()); + assertEquals(1, breakpointManager.getBreakpointsByPath("Breakpoints[0]").size()); } protected void addBreakpoints() throws Exception { try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread1", 0); - breakMain = breakpointManager.addBreakpoint("Breaks[0]", Range.closed(0L, 10L), + thread = b.getOrAddThread("Threads[1]", 0); + // NB. threads parameter is deprecated by object mode. + // For table mode, ensure the answer is the same as object mode + breakMain = breakpointManager.addBreakpoint("Breakpoints[0]", Range.closed(0L, 10L), b.addr(0x00400000), - Set.of(), Set.of(TraceBreakpointKind.SW_EXECUTE), true, "main"); - breakVarA = breakpointManager.addBreakpoint("Breaks[1]", Range.closed(0L, 10L), + Set.of(thread), Set.of(TraceBreakpointKind.SW_EXECUTE), true, "main"); + breakVarA = breakpointManager.addBreakpoint("Breakpoints[1]", Range.closed(0L, 10L), b.range(0x00600010, 0x00600013), - Set.of(), Set.of(TraceBreakpointKind.WRITE), false, "varA"); - breakVarB = breakpointManager.addBreakpoint("Breaks[1]", Range.closed(11L, 20L), + Set.of(thread), Set.of(TraceBreakpointKind.WRITE), false, "varA"); + breakVarB = breakpointManager.addBreakpoint("Breakpoints[1]", Range.closed(11L, 20L), b.range(0x00600020, 0x00600023), Set.of(thread), Set.of(TraceBreakpointKind.WRITE), false, "varB"); } @@ -96,16 +99,16 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat public void testBreakpointsByPath() throws Exception { addBreakpoints(); assertEquals(Set.of(breakMain), - Set.copyOf(breakpointManager.getBreakpointsByPath("Breaks[0]"))); + Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]"))); assertEquals(Set.of(breakVarA, breakVarB), - Set.copyOf(breakpointManager.getBreakpointsByPath("Breaks[1]"))); + Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[1]"))); } @Test public void testBreakpointPlacedByPath() throws Exception { addBreakpoints(); - assertEquals(breakVarA, breakpointManager.getPlacedBreakpointByPath(0, "Breaks[1]")); - assertEquals(breakVarB, breakpointManager.getPlacedBreakpointByPath(11, "Breaks[1]")); + assertEquals(breakVarA, breakpointManager.getPlacedBreakpointByPath(0, "Breakpoints[1]")); + assertEquals(breakVarB, breakpointManager.getPlacedBreakpointByPath(11, "Breakpoints[1]")); } @Test @@ -140,13 +143,13 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat @Test public void testGetPath() throws Exception { addBreakpoints(); - assertEquals("Breaks[0]", breakMain.getPath()); + assertEquals("Breakpoints[0]", breakMain.getPath()); } @Test public void testSetGetName() throws Exception { addBreakpoints(); - assertEquals("Breaks[0]", breakMain.getName()); + assertEquals("Breakpoints[0]", breakMain.getName()); try (UndoableTransaction tid = b.startTransaction()) { breakMain.setName("bpt 0"); assertEquals("bpt 0", breakMain.getName()); @@ -157,7 +160,8 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat @Test public void testGetThreads() throws Exception { addBreakpoints(); - assertEquals(Set.of(), Set.copyOf(breakMain.getThreads())); + assertEquals(Set.of(thread), Set.copyOf(breakMain.getThreads())); + assertEquals(Set.of(thread), Set.copyOf(breakVarA.getThreads())); assertEquals(Set.of(thread), Set.copyOf(breakVarB.getThreads())); } @@ -207,27 +211,27 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat assertSame(disMain, sameDis); } - assertTrue(breakMain.isEnabled()); + assertTrue(breakMain.isEnabled(0)); assertEquals(Set.of(TraceBreakpointKind.HW_EXECUTE), Set.copyOf(breakMain.getKinds())); - assertFalse(disMain.isEnabled()); + assertFalse(disMain.isEnabled(6)); assertEquals(Set.of(TraceBreakpointKind.HW_EXECUTE), Set.copyOf(disMain.getKinds())); } @Test public void testSetIsEnabled() throws Exception { addBreakpoints(); - assertTrue(breakMain.isEnabled()); + assertTrue(breakMain.isEnabled(0)); try (UndoableTransaction tid = b.startTransaction()) { breakMain.setEnabled(false); - assertFalse(breakMain.isEnabled()); + assertFalse(breakMain.isEnabled(0)); } - assertFalse(breakMain.isEnabled()); + assertFalse(breakMain.isEnabled(0)); try (UndoableTransaction tid = b.startTransaction()) { breakMain.setEnabled(true); - assertTrue(breakMain.isEnabled()); + assertTrue(breakMain.isEnabled(0)); } - assertTrue(breakMain.isEnabled()); + assertTrue(breakMain.isEnabled(0)); } @Test @@ -256,11 +260,13 @@ public class DBTraceBreakpointManagerTest extends AbstractGhidraHeadlessIntegrat public void testDelete() throws Exception { addBreakpoints(); assertEquals(Set.of(breakMain), - Set.copyOf(breakpointManager.getBreakpointsByPath("Breaks[0]"))); + Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]"))); try (UndoableTransaction tid = b.startTransaction()) { breakMain.delete(); - assertEquals(Set.of(), Set.copyOf(breakpointManager.getBreakpointsByPath("Breaks[0]"))); + assertEquals(Set.of(), + Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]"))); } - assertEquals(Set.of(), Set.copyOf(breakpointManager.getBreakpointsByPath("Breaks[0]"))); + assertEquals(Set.of(), + Set.copyOf(breakpointManager.getBreakpointsByPath("Breakpoints[0]"))); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeManagerTest.java index abcf8e6aac..18b5a85ba3 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeManagerTest.java @@ -41,10 +41,10 @@ import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.context.DBTraceRegisterContextManager; import ghidra.trace.database.language.*; -import ghidra.trace.database.stack.DBTraceStack; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.ImmutableTraceAddressSnapRange; import ghidra.trace.model.listing.*; +import ghidra.trace.model.stack.TraceStack; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.IntersectionAddressSetView; import ghidra.util.database.UndoableTransaction; import ghidra.util.exception.CancelledException; @@ -1253,7 +1253,7 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes @Test public void testRegisterSpace() throws Exception { - DBTraceThread thread; + TraceThread thread; DBTraceCodeRegisterSpace regCode; TraceData dR4; @@ -1277,7 +1277,7 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes TraceData dR5; try (UndoableTransaction tid = b.startTransaction()) { - DBTraceStack stack = b.trace.getStackManager().getStack(thread, 0, true); + TraceStack stack = b.trace.getStackManager().getStack(thread, 0, true); stack.setDepth(2, true); assertEquals(regCode, manager.getCodeRegisterSpace(stack.getFrame(0, false), false)); frameCode = manager.getCodeRegisterSpace(stack.getFrame(1, false), true); @@ -1733,7 +1733,7 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes try (UndoableTransaction tid = b.startTransaction()) { b.addInstruction(0, b.addr(0x4004), b.language, b.buf(0xf4, 0)); - DBTraceThread thread = b.getOrAddThread("Thread 1", 0); + TraceThread thread = b.getOrAddThread("Thread 1", 0); DBTraceCodeRegisterSpace regCode = manager.getCodeRegisterSpace(thread, true); regCode.definedData() .create(Range.atLeast(0L), b.language.getRegister("r4"), @@ -1747,7 +1747,7 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes DBTraceCodeManager manager = b.trace.getCodeManager(); // No transaction, so it had better exist - DBTraceThread thread = b.getOrAddThread("Thread 1", 0); + TraceThread thread = b.getOrAddThread("Thread 1", 0); List units = new ArrayList<>(); for (TraceCodeUnit u : manager.definedUnits().get(0, true)) { units.add(u); @@ -1781,7 +1781,7 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes try (UndoableTransaction tid = b.startTransaction()) { b.addInstruction(0, b.addr(0x4004), b.language, b.buf(0xf4, 0)); - DBTraceThread thread = b.getOrAddThread("Thread 1", 0); + TraceThread thread = b.getOrAddThread("Thread 1", 0); DBTraceCodeRegisterSpace regCode = manager.getCodeRegisterSpace(thread, true); regCode.definedData() .create(Range.atLeast(0L), b.language.getRegister("r4"), @@ -1796,7 +1796,7 @@ public class DBTraceCodeManagerTest extends AbstractGhidraHeadlessIntegrationTes b.trace.redo(); // No transaction, so it had better exist - DBTraceThread thread = b.getOrAddThread("Thread 1", 0); + TraceThread thread = b.getOrAddThread("Thread 1", 0); List units = new ArrayList<>(); for (TraceCodeUnit u : manager.definedUnits().get(0, true)) { units.add(u); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeUnitTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeUnitTest.java index fda9c111bf..a0e9d06fc0 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeUnitTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/listing/DBTraceCodeUnitTest.java @@ -50,7 +50,6 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAdd import ghidra.trace.database.memory.DBTraceMemoryRegisterSpace; import ghidra.trace.database.memory.DBTraceMemorySpace; import ghidra.trace.database.symbol.DBTraceReference; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.language.TraceGuestLanguage; import ghidra.trace.model.listing.TraceData; import ghidra.trace.model.listing.TraceInstruction; @@ -796,7 +795,7 @@ public class DBTraceCodeUnitTest extends AbstractGhidraHeadlessIntegrationTest // In space without memory, yet. und = manager.undefinedData().getAt(0, b.data(0x7fff)); - DBTraceThread thread = b.getOrAddThread("Thread1", 0); + TraceThread thread = b.getOrAddThread("Thread1", 0); DBTraceMemoryRegisterSpace regMem = b.trace.getMemoryManager().getMemoryRegisterSpace(thread, true); Register r4 = b.language.getRegister("r4"); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegionsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegionsTest.java new file mode 100644 index 0000000000..3f03c8e9a1 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerRegionsTest.java @@ -0,0 +1,173 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.io.IOException; +import java.util.Set; + +import org.junit.*; + +import com.google.common.collect.Range; + +import ghidra.program.model.lang.LanguageID; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.memory.TraceMemoryRegion; +import ghidra.trace.util.LanguageTestWatcher; +import ghidra.util.database.UndoableTransaction; + +public abstract class AbstractDBTraceMemoryManagerRegionsTest + extends AbstractGhidraHeadlessIntegrationTest { + protected ToyDBTraceBuilder b; + protected DBTraceMemoryManager memory; + + @Rule + public LanguageTestWatcher testLanguage = + new LanguageTestWatcher(getLanguageID().getIdAsString()); + + protected abstract LanguageID getLanguageID(); + + @Before + public void setUp() throws IOException { + b = new ToyDBTraceBuilder("Testing", testLanguage.getLanguage()); + try (UndoableTransaction tid = b.startTransaction()) { + b.trace.getTimeManager().createSnapshot("Initialize"); + } + memory = b.trace.getMemoryManager(); + } + + @After + public void tearDown() { + b.close(); + } + + @Test + public void testAddRegion() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + memory.addRegion("Regions[0x1000]", Range.atLeast(0L), b.range(0x1000, 0x1fff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + } + } + + @Test + public void testGetAllRegions() throws Exception { + assertEquals(Set.of(), Set.copyOf(memory.getAllRegions())); + + TraceMemoryRegion region; + try (UndoableTransaction tid = b.startTransaction()) { + region = memory.addRegion("Regions[0x1000]", Range.atLeast(0L), b.range(0x1000, 0x1fff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + } + + assertEquals(Set.of(region), Set.copyOf(memory.getAllRegions())); + } + + @Test + public void testGetLiveRegionByPath() throws Exception { + assertNull(memory.getLiveRegionByPath(0L, "Regions[0x1000]")); + + TraceMemoryRegion region; + try (UndoableTransaction tid = b.startTransaction()) { + region = memory.addRegion("Regions[0x1000]", Range.atLeast(0L), b.range(0x1000, 0x1fff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + } + + assertEquals(region, memory.getLiveRegionByPath(0L, "Regions[0x1000]")); + assertNull(memory.getLiveRegionByPath(0L, "Regions[0x1001]")); + assertNull(memory.getLiveRegionByPath(-1L, "Regions[0x1000]")); + } + + @Test + public void testGetRegionContaining() throws Exception { + assertNull(memory.getRegionContaining(0, b.addr(0x1000))); + + TraceMemoryRegion region; + try (UndoableTransaction tid = b.startTransaction()) { + region = memory.addRegion("Regions[0x1000]", Range.atLeast(0L), b.range(0x1000, 0x1fff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + } + + assertEquals(region, memory.getRegionContaining(0, b.addr(0x1000))); + assertEquals(region, memory.getRegionContaining(0, b.addr(0x1fff))); + assertNull(memory.getRegionContaining(-1, b.addr(0x1000))); + assertNull(memory.getRegionContaining(0, b.addr(0x0fff))); + assertNull(memory.getRegionContaining(0, b.addr(0x2000))); + } + + @Test + public void testRegionsIntersecting() throws Exception { + assertEquals(Set.of(), Set.copyOf( + memory.getRegionsIntersecting(Range.closed(0L, 10L), b.range(0x1800, 0x27ff)))); + + TraceMemoryRegion region; + try (UndoableTransaction tid = b.startTransaction()) { + region = memory.addRegion("Regions[0x1000]", Range.atLeast(0L), b.range(0x1000, 0x1fff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + } + + assertEquals(Set.of(region), Set.copyOf( + memory.getRegionsIntersecting(Range.closed(0L, 10L), b.range(0x1800, 0x27ff)))); + assertEquals(Set.of(), Set.copyOf( + memory.getRegionsIntersecting(Range.closed(-10L, -1L), b.range(0x1800, 0x27ff)))); + assertEquals(Set.of(), Set.copyOf( + memory.getRegionsIntersecting(Range.closed(0L, 10L), b.range(0x2000, 0x27ff)))); + } + + @Test + public void testGetRegionsAtSnap() throws Exception { + assertEquals(Set.of(), Set.copyOf(memory.getRegionsAtSnap(0))); + + TraceMemoryRegion region; + try (UndoableTransaction tid = b.startTransaction()) { + region = memory.addRegion("Regions[0x1000]", Range.atLeast(0L), b.range(0x1000, 0x1fff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + } + + assertEquals(Set.of(region), Set.copyOf(memory.getRegionsAtSnap(0))); + assertEquals(Set.of(), Set.copyOf(memory.getRegionsAtSnap(-1))); + } + + @Test + public void testGetRegionsAddressSet() throws Exception { + assertEquals(b.set(), memory.getRegionsAddressSet(0)); + + try (UndoableTransaction tid = b.startTransaction()) { + memory.addRegion("Regions[0x1000]", Range.atLeast(0L), b.range(0x1000, 0x1fff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + } + + assertEquals(b.set(b.range(0x1000, 0x1fff)), memory.getRegionsAddressSet(0)); + assertEquals(b.set(), memory.getRegionsAddressSet(-1)); + } + + @Test + public void testGetRegionsAddressSetWith() throws Exception { + assertEquals(b.set(), memory.getRegionsAddressSetWith(0, r -> true)); + + try (UndoableTransaction tid = b.startTransaction()) { + memory.addRegion("Regions[0x1000]", Range.atLeast(0L), b.range(0x1000, 0x1fff), + Set.of(TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE)); + } + + assertEquals(b.set(b.range(0x1000, 0x1fff)), memory.getRegionsAddressSetWith(0, r -> true)); + assertEquals(b.set(), memory.getRegionsAddressSetWith(-1, r -> true)); + assertEquals(b.set(), memory.getRegionsAddressSetWith(0, r -> false)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java index 70c9d4d766..1c2f657010 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/AbstractDBTraceMemoryManagerTest.java @@ -33,10 +33,10 @@ import ghidra.program.model.lang.*; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.trace.database.DBTrace; import ghidra.trace.database.ToyDBTraceBuilder; -import ghidra.trace.database.stack.DBTraceStack; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.TraceAddressSnapRange; import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.stack.TraceStack; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.LanguageTestWatcher; import ghidra.util.database.*; import ghidra.util.task.ConsoleTaskMonitor; @@ -1027,7 +1027,7 @@ public abstract class AbstractDBTraceMemoryManagerTest Register r0h = b.language.getRegister("r0h"); Register r0l = b.language.getRegister("r0l"); - DBTraceThread thread; + TraceThread thread; try (UndoableTransaction tid = b.startTransaction()) { thread = b.getOrAddThread("Thread1", 0); DBTraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(thread, true); @@ -1048,7 +1048,7 @@ public abstract class AbstractDBTraceMemoryManagerTest assertEquals(new BigInteger("76543210", 16), regs.getValue(0, r0h).getUnsignedValue()); assertEquals(new BigInteger("FEDCBA98", 16), regs.getValue(0, r0l).getUnsignedValue()); - DBTraceStack stack = b.trace.getStackManager().getStack(thread, 0, true); + TraceStack stack = b.trace.getStackManager().getStack(thread, 0, true); stack.setDepth(2, true); assertEquals(regs, memory.getMemoryRegisterSpace(stack.getFrame(0, false), false)); DBTraceMemoryRegisterSpace frame = @@ -1072,7 +1072,7 @@ public abstract class AbstractDBTraceMemoryManagerTest Register phase = b.language.getRegister("phase"); Register counter = b.language.getRegister("counter"); - DBTraceThread thread; + TraceThread thread; try (UndoableTransaction tid = b.startTransaction()) { thread = b.getOrAddThread("Thread1", 0); DBTraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(thread, true); @@ -1106,7 +1106,7 @@ public abstract class AbstractDBTraceMemoryManagerTest @Test public void testManyStateEntries() throws Exception { Register pc = b.language.getRegister("pc"); - DBTraceThread thread; + TraceThread thread; try (UndoableTransaction tid = b.startTransaction()) { thread = b.getOrAddThread("Thread1", 0); DBTraceMemoryRegisterSpace regs = memory.getMemoryRegisterSpace(thread, true); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegionsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegionsTest.java new file mode 100644 index 0000000000..44d196e40b --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerObjectRegionsTest.java @@ -0,0 +1,49 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import org.junit.Before; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.util.database.UndoableTransaction; + +public class DBTraceMemoryManagerObjectRegionsTest extends DBTraceMemoryManagerRegionsTest { + + protected SchemaContext ctx; + + @Before + public void setUpObjectsMode() throws Exception { + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = b.startTransaction()) { + b.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegionsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegionsTest.java new file mode 100644 index 0000000000..ab15f1dd58 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/memory/DBTraceMemoryManagerRegionsTest.java @@ -0,0 +1,26 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.memory; + +import ghidra.program.model.lang.LanguageID; +import ghidra.test.ToyProgramBuilder; + +public class DBTraceMemoryManagerRegionsTest extends AbstractDBTraceMemoryManagerRegionsTest { + @Override + protected LanguageID getLanguageID() { + return new LanguageID(ToyProgramBuilder._TOY64_BE); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerObjectTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerObjectTest.java new file mode 100644 index 0000000000..f0aa194104 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerObjectTest.java @@ -0,0 +1,57 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.module; + +import org.junit.Before; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.util.database.UndoableTransaction; + +public class DBTraceModuleManagerObjectTest extends DBTraceModuleManagerTest { + + protected SchemaContext ctx; + + @Before + public void setUpObjectsMode() throws Exception { + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = b.startTransaction()) { + b.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerTest.java index 618eba748e..e18e5ea752 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/module/DBTraceModuleManagerTest.java @@ -51,9 +51,9 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT @Test public void testAddModule() throws Exception { try (UndoableTransaction tid = b.startTransaction()) { - moduleManager.addModule("first", "first", + moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - moduleManager.addLoadedModule("second", "second", + moduleManager.addLoadedModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), 0); } } @@ -61,12 +61,12 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT @Test public void testAddSections() throws Exception { try (UndoableTransaction tid = b.startTransaction()) { - TraceModule mod1 = moduleManager.addModule("first", "first", + TraceModule mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); - mod1.addSection(".data", b.range(0x00600000, 0x0060002f)); + mod1.addSection("Modules[first].Sections[.text]", b.range(0x00401000, 0x00401f9f)); + mod1.addSection("Modules[first].Sections[.data]", b.range(0x00600000, 0x0060002f)); - moduleManager.addModule("second", "second", + moduleManager.addModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), Range.closed(0L, 10L)); } } @@ -78,12 +78,12 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceModule mod1; TraceModule mod2; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); - mod1.addSection(".data", b.range(0x00600000, 0x0060002f)); + mod1.addSection("Modules[first].Sections[.text]", b.range(0x00401000, 0x00401f9f)); + mod1.addSection("Modules[first].Sections[.data]", b.range(0x00600000, 0x0060002f)); - mod2 = moduleManager.addModule("second", "second", + mod2 = moduleManager.addModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), Range.closed(0L, 10L)); } assertEquals(Set.of(mod1, mod2), new HashSet<>(moduleManager.getAllModules())); @@ -97,25 +97,26 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceModule mod2; TraceModule mod3; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); - mod1.addSection(".data", b.range(0x00600000, 0x0060002f)); + mod1.addSection("Modules[first].Sections[.text]", b.range(0x00401000, 0x00401f9f)); + mod1.addSection("Modules[first].Sections[.data]", b.range(0x00600000, 0x0060002f)); - mod2 = moduleManager.addModule("second", "second", + mod2 = moduleManager.addModule("Modules[second]", "second", b.range(0x7e400000, 0x7e60002f), Range.closed(0L, 10L)); - mod3 = moduleManager.addModule("second", "second", + mod3 = moduleManager.addModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), Range.closed(11L, 20L)); } - assertEquals(Set.of(mod1), new HashSet<>(moduleManager.getModulesByPath("first"))); - assertEquals(Set.of(mod2, mod3), new HashSet<>(moduleManager.getModulesByPath("second"))); + assertEquals(Set.of(mod1), new HashSet<>(moduleManager.getModulesByPath("Modules[first]"))); + assertEquals(Set.of(mod2, mod3), + new HashSet<>(moduleManager.getModulesByPath("Modules[second]"))); } @Test public void testModuleGetTrace() throws Exception { TraceModule mod1; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); } assertEquals(b.trace, mod1.getTrace()); @@ -125,7 +126,7 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT public void testModuleSetGetName() throws Exception { TraceModule mod1; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); assertEquals("first", mod1.getName()); @@ -139,7 +140,7 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT // TODO: Should adjusting the base shift the sections? TraceModule mod1; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); assertEquals(b.addr(0x00400000), mod1.getBase()); @@ -152,7 +153,7 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT public void testModuleSetGetLifespan() throws Exception { TraceModule mod1; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); assertEquals(Range.closed(0L, 10L), mod1.getLifespan()); assertEquals(0, mod1.getLoadedSnap()); @@ -179,15 +180,19 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceSection s2text; TraceSection s2data; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - s1text = mod1.addSection("first[.text]", b.range(0x00401000, 0x00401f9f)); - s1data = mod1.addSection("first[.data]", b.range(0x00600000, 0x0060002f)); + s1text = + mod1.addSection("Modules[first].Sections[.text]", b.range(0x00401000, 0x00401f9f)); + s1data = + mod1.addSection("Modules[first].Sections[.data]", b.range(0x00600000, 0x0060002f)); - mod2 = moduleManager.addModule("second", "second", + mod2 = moduleManager.addModule("Modules[second]", "second", b.range(0x7e400000, 0x7e60002f), Range.closed(0L, 10L)); - s2text = mod2.addSection("second[.text]", b.range(0x7f401000, 0x7f401fa0)); - s2data = mod2.addSection("second[.data]", b.range(0x7f600000, 0x7f60002f)); + s2text = + mod2.addSection("Modules[second].Sections[.text]", b.range(0x7f401000, 0x7f401fa0)); + s2data = + mod2.addSection("Modules[second].Sections[.data]", b.range(0x7f600000, 0x7f60002f)); } assertEquals(Set.of(s1text, s1data), new HashSet<>(mod1.getSections())); assertEquals(Set.of(s2text, s2data), new HashSet<>(mod2.getSections())); @@ -202,15 +207,19 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceSection s2text; TraceSection s2data; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - s1text = mod1.addSection("first[.text]", ".text", b.range(0x00401000, 0x00401f9f)); - s1data = mod1.addSection("first[.data]", ".data", b.range(0x00600000, 0x0060002f)); + s1text = mod1.addSection("Modules[first].Sections[.text]", ".text", + b.range(0x00401000, 0x00401f9f)); + s1data = mod1.addSection("Modules[first].Sections[.data]", ".data", + b.range(0x00600000, 0x0060002f)); - mod2 = moduleManager.addModule("second", "second", + mod2 = moduleManager.addModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), Range.closed(0L, 10L)); - s2text = mod2.addSection("second[.text]", ".text", b.range(0x7f401000, 0x7f401f9f)); - s2data = mod2.addSection("second[.data]", ".data", b.range(0x7f600000, 0x7f60002f)); + s2text = mod2.addSection("Modules[second].Sections[.text]", ".text", + b.range(0x7f401000, 0x7f401f9f)); + s2data = mod2.addSection("Modules[second].Sections[.data]", ".data", + b.range(0x7f600000, 0x7f60002f)); } assertEquals(s1text, mod1.getSectionByName(".text")); assertEquals(s1data, mod1.getSectionByName(".data")); @@ -223,12 +232,12 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceModule mod1; TraceModule mod2; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); - mod1.addSection(".data", b.range(0x00600000, 0x0060002f)); + mod1.addSection("Modules[first].Sections[.text]", b.range(0x00401000, 0x00401f9f)); + mod1.addSection("Modules[first].Sections[.data]", b.range(0x00600000, 0x0060002f)); - mod2 = moduleManager.addModule("second", "second", + mod2 = moduleManager.addModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), Range.closed(0L, 10L)); } @@ -244,9 +253,10 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceModule mod1; TraceSection s1text; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - s1text = mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); + s1text = + mod1.addSection("Modules[first].Sections[.text]", b.range(0x00401000, 0x00401f9f)); } assertEquals(mod1, s1text.getModule()); @@ -257,9 +267,10 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceModule mod1; TraceSection s1text; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - s1text = mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); + s1text = mod1.addSection("Modules[first].Sections[.text]", ".text", + b.range(0x00401000, 0x00401f9f)); assertEquals(".text", s1text.getName()); s1text.setName("_TEXT"); @@ -272,9 +283,10 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceModule mod1; TraceSection s1text; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - s1text = mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); + s1text = mod1.addSection("Modules[first].Sections[.text]", ".text", + b.range(0x00401000, 0x00401f9f)); } assertEquals(b.range(0x00401000, 0x00401f9f), s1text.getRange()); @@ -293,12 +305,14 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceModule mod1; TraceModule mod2; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); - mod1.addSection(".data", b.range(0x00600000, 0x0060002f)); + mod1.addSection("Modules[first].Sections[.text]", ".text", + b.range(0x00401000, 0x00401f9f)); + mod1.addSection("Modules[first].Sections[.data]", ".data", + b.range(0x00600000, 0x0060002f)); - mod2 = moduleManager.addModule("second", "second", + mod2 = moduleManager.addModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), Range.closed(1L, 11L)); } @@ -306,8 +320,8 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT try (ToyDBTraceBuilder b = new ToyDBTraceBuilder(tmp)) { DBTraceModuleManager moduleManager = b.trace.getModuleManager(); - mod1 = assertOne(moduleManager.getModulesByPath("first")); - mod2 = assertOne(moduleManager.getModulesByPath("second")); + mod1 = assertOne(moduleManager.getModulesByPath("Modules[first]")); + mod2 = assertOne(moduleManager.getModulesByPath("Modules[second]")); TraceSection s1text = mod1.getSectionByName(".text"); TraceSection s1data = mod1.getSectionByName(".data"); @@ -325,22 +339,22 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT public void testUndoIdentitiesPreserved() throws Exception { TraceModule mod1; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); - mod1.addSection(".data", b.range(0x00600000, 0x0060002f)); + mod1.addSection("Modules[first].Sections[.text]", b.range(0x00401000, 0x00401f9f)); + mod1.addSection("Modules[first].Sections[.data]", b.range(0x00600000, 0x0060002f)); } try (UndoableTransaction tid = b.startTransaction()) { - moduleManager.addModule("second", "second", + moduleManager.addModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), Range.closed(1L, 11L)); } b.trace.undo(); - assertEquals(mod1, assertOne(moduleManager.getModulesByPath("first"))); + assertEquals(mod1, assertOne(moduleManager.getModulesByPath("Modules[first]"))); TODO(); // TODO: mod1 should still be identical to that in database - assertTrue(moduleManager.getModulesByPath("second").isEmpty()); + assertTrue(moduleManager.getModulesByPath("Modules[second]").isEmpty()); } @Test @@ -348,12 +362,14 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT TraceModule mod1; TraceModule mod2; try (UndoableTransaction tid = b.startTransaction()) { - mod1 = moduleManager.addModule("first", "first", + mod1 = moduleManager.addModule("Modules[first]", "first", b.range(0x00400000, 0x0060002f), Range.closed(0L, 10L)); - mod1.addSection(".text", b.range(0x00401000, 0x00401f9f)); - mod1.addSection(".data", b.range(0x00600000, 0x0060002f)); + mod1.addSection("Modules[first].Sections[.text]", ".text", + b.range(0x00401000, 0x00401f9f)); + mod1.addSection("Modules[first].Sections[.data]", ".data", + b.range(0x00600000, 0x0060002f)); - mod2 = moduleManager.addModule("second", "second", + mod2 = moduleManager.addModule("Modules[second]", "second", b.range(0x7f400000, 0x7f60002f), Range.closed(1L, 11L)); } @@ -363,8 +379,8 @@ public class DBTraceModuleManagerTest extends AbstractGhidraHeadlessIntegrationT b.trace.redo(); // NOTE: Because undo actually removes them, module identity may not be preserved - mod1 = assertOne(moduleManager.getModulesByPath("first")); - mod2 = assertOne(moduleManager.getModulesByPath("second")); + mod1 = assertOne(moduleManager.getModulesByPath("Modules[first]")); + mod2 = assertOne(moduleManager.getModulesByPath("Modules[second]")); TraceSection s1text = mod1.getSectionByName(".text"); TraceSection s1data = mod1.getSectionByName(".data"); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerObjectTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerObjectTest.java new file mode 100644 index 0000000000..aa26f98f41 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerObjectTest.java @@ -0,0 +1,66 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.stack; + +import org.junit.Before; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.util.database.UndoableTransaction; + +public class DBTraceStackManagerObjectTest extends DBTraceStackManagerTest { + + protected SchemaContext ctx; + + @Before + public void setUpObjectsMode() throws Exception { + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = b.startTransaction()) { + b.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerTest.java index 75dfed2fa2..0181ac1442 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/stack/DBTraceStackManagerTest.java @@ -18,14 +18,17 @@ package ghidra.trace.database.stack; import static org.junit.Assert.*; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; -import org.apache.commons.collections4.IterableUtils; import org.junit.*; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.trace.database.ToyDBTraceBuilder; -import ghidra.trace.database.thread.DBTraceThread; +import ghidra.trace.model.stack.TraceStack; import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.database.UndoableTransaction; public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTest { @@ -47,16 +50,16 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testCreateStack() throws Exception { try (UndoableTransaction tid = b.startTransaction()) { - DBTraceThread thread = b.getOrAddThread("Thread 1", 0); + TraceThread thread = b.getOrAddThread("Threads[1]", 0); stackManager.getStack(thread, 0, true); } } @Test public void testSetDepth() throws Exception { - DBTraceStack stack; + TraceStack stack; try (UndoableTransaction tid = b.startTransaction()) { - DBTraceThread thread = b.getOrAddThread("Thread 1", 0); + TraceThread thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); stack.setDepth(1, true); stack.setDepth(3, false); @@ -91,15 +94,15 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testGetLatestStack() throws Exception { - DBTraceThread thread1; - DBTraceThread thread2; - DBTraceStack stack1a; - DBTraceStack stack1b; - DBTraceStack stack2a; - DBTraceStack stack2b; + TraceThread thread1; + TraceThread thread2; + TraceStack stack1a; + TraceStack stack1b; + TraceStack stack2a; + TraceStack stack2b; try (UndoableTransaction tid = b.startTransaction()) { - thread1 = b.getOrAddThread("Thread 1", 0); - thread2 = b.getOrAddThread("Thread 2", 0); + thread1 = b.getOrAddThread("Threads[1]", 0); + thread2 = b.getOrAddThread("Threads[2]", 0); stack1a = stackManager.getStack(thread1, 2, true); stack1b = stackManager.getStack(thread1, 10, true); stack2a = stackManager.getStack(thread2, 2, true); @@ -119,39 +122,43 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe assertEquals(stack2b, stackManager.getLatestStack(thread2, 100)); } + protected Set toSet(Iterable it) { + return StreamSupport.stream(it.spliterator(), false).collect(Collectors.toSet()); + } + @Test public void testGetFramesIn() throws Exception { - DBTraceStackFrame frame1a; - DBTraceStackFrame frame1b; - DBTraceStackFrame frame2a; - DBTraceStackFrame frame2b; + TraceStackFrame frame1a; + TraceStackFrame frame1b; + TraceStackFrame frame2a; + TraceStackFrame frame2b; try (UndoableTransaction tid = b.startTransaction()) { - DBTraceThread thread = b.getOrAddThread("Thread 1", 0); + TraceThread thread = b.getOrAddThread("Threads[1]", 0); - DBTraceStack stack1 = stackManager.getStack(thread, 0, true); + TraceStack stack1 = stackManager.getStack(thread, 0, true); stack1.setDepth(2, true); (frame1a = stack1.getFrame(0, false)).setProgramCounter(b.addr(0x0040100)); (frame1b = stack1.getFrame(1, false)).setProgramCounter(b.addr(0x0040300)); - DBTraceStack stack2 = stackManager.getStack(thread, 1, true); + TraceStack stack2 = stackManager.getStack(thread, 1, true); stack2.setDepth(2, true); (frame2a = stack2.getFrame(0, false)).setProgramCounter(b.addr(0x0040200)); (frame2b = stack2.getFrame(1, false)).setProgramCounter(b.addr(0x0040400)); } - assertEquals(List.of(frame1a, frame2a, frame1b, frame2b), - IterableUtils.toList(stackManager.getFramesIn(b.set(b.drng(0x0040000, 0x0050000))))); + assertEquals(Set.of(frame1a, frame2a, frame1b, frame2b), toSet(stackManager + .getFramesIn(b.set(b.drng(0x0040000, 0x0050000))))); - assertEquals(List.of(frame1a, frame1b), IterableUtils.toList(stackManager + assertEquals(Set.of(frame1a, frame1b), toSet(stackManager .getFramesIn(b.set(b.drng(0x0040000, 0x00401ff), b.drng(0x0040300, 0x0040300))))); } @Test public void testStackGetThread() throws Exception { - DBTraceThread thread; - DBTraceStack stack; + TraceThread thread; + TraceStack stack; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); } @@ -160,10 +167,10 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testStackGetSnap() throws Exception { - DBTraceThread thread; - DBTraceStack stack; + TraceThread thread; + TraceStack stack; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 2, true); } @@ -172,10 +179,10 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testStackGetDepth() throws Exception { - DBTraceThread thread; - DBTraceStack stack; + TraceThread thread; + TraceStack stack; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); stack.setDepth(2, true); } @@ -185,10 +192,10 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testStackGetFrames() throws Exception { - DBTraceThread thread; - DBTraceStack stack; + TraceThread thread; + TraceStack stack; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); stack.setDepth(2, true); } @@ -201,34 +208,33 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testStackDelete() throws Exception { - DBTraceThread thread; - DBTraceStack stack; + TraceThread thread; + TraceStack stack; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); stack.setDepth(2, true); } assertFalse(stack.isDeleted()); - assertEquals(1, stackManager.stackStore.getRecordCount()); - assertEquals(2, stackManager.frameStore.getRecordCount()); + assertEquals(stack, stackManager.getStack(thread, 0, false)); + assertEquals(2, stack.getFrames().size()); try (UndoableTransaction tid = b.startTransaction()) { stack.delete(); } assertTrue(stack.isDeleted()); - assertEquals(0, stackManager.stackStore.getRecordCount()); - assertEquals(0, stackManager.frameStore.getRecordCount()); + assertNull(stackManager.getStack(thread, 0, false)); } @Test public void testStackFrameGetStack() throws Exception { - DBTraceThread thread; - DBTraceStack stack; - DBTraceStackFrame frame; + TraceThread thread; + TraceStack stack; + TraceStackFrame frame; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); frame = stack.getFrame(0, true); } @@ -238,12 +244,12 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testStackFrameGetLevel() throws Exception { - DBTraceThread thread; - DBTraceStack stack; - DBTraceStackFrame frame0; - DBTraceStackFrame frame1; + TraceThread thread; + TraceStack stack; + TraceStackFrame frame0; + TraceStackFrame frame1; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); stack.setDepth(2, true); frame0 = stack.getFrame(0, false); @@ -256,11 +262,11 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testStackFrameSetGetProgramCounter() throws Exception { - DBTraceThread thread; - DBTraceStack stack; - DBTraceStackFrame frame; + TraceThread thread; + TraceStack stack; + TraceStackFrame frame; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); stack.setDepth(1, true); frame = stack.getFrame(0, false); @@ -274,14 +280,16 @@ public class DBTraceStackManagerTest extends AbstractGhidraHeadlessIntegrationTe @Test public void testStackFrameSetGetComment() throws Exception { - DBTraceThread thread; - DBTraceStack stack; - DBTraceStackFrame frame; + TraceThread thread; + TraceStack stack; + TraceStackFrame frame; try (UndoableTransaction tid = b.startTransaction()) { - thread = b.getOrAddThread("Thread 1", 0); + thread = b.getOrAddThread("Threads[1]", 0); stack = stackManager.getStack(thread, 0, true); stack.setDepth(1, true); frame = stack.getFrame(0, false); + // NB. Object-mode sets comment at pc in listing, not on frame itself + frame.setProgramCounter(b.addr(0x00400123)); assertNull(frame.getComment()); frame.setComment("Hello, World!"); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/symbol/DBTraceSymbolManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/symbol/DBTraceSymbolManagerTest.java index c4b5ef7293..45bc59f730 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/symbol/DBTraceSymbolManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/symbol/DBTraceSymbolManagerTest.java @@ -32,8 +32,8 @@ import ghidra.program.model.symbol.Namespace; import ghidra.program.model.symbol.SourceType; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.trace.database.ToyDBTraceBuilder; -import ghidra.trace.database.thread.DBTraceThread; import ghidra.trace.model.symbol.TraceLabelSymbol; +import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.TraceRegisterUtils; import ghidra.util.database.UndoableTransaction; import ghidra.util.exception.*; @@ -110,7 +110,7 @@ public class DBTraceSymbolManagerTest extends AbstractGhidraHeadlessIntegrationT public void testAddLabels() throws Exception { DBTraceNamespaceSymbol global = manager.getGlobalNamespace(); try (UndoableTransaction tid = b.startTransaction()) { - DBTraceThread thread = b.getOrAddThread("Thread1", 0); + TraceThread thread = b.getOrAddThread("Thread1", 0); manager.labels() .create(0, null, b.addr(0x4000), "myLabel", global, @@ -415,7 +415,7 @@ public class DBTraceSymbolManagerTest extends AbstractGhidraHeadlessIntegrationT public void testGetChildWithNameAt() throws DuplicateNameException, InvalidInputException, IllegalArgumentException { DBTraceNamespaceSymbol global = manager.getGlobalNamespace(); - DBTraceThread thread; + TraceThread thread; DBTraceNamespaceSymbol nsA; DBTraceClassSymbol clsA; TraceLabelSymbol lab1; @@ -478,7 +478,7 @@ public class DBTraceSymbolManagerTest extends AbstractGhidraHeadlessIntegrationT public void testGetIntersecting() throws DuplicateNameException, InvalidInputException, IllegalArgumentException { DBTraceNamespaceSymbol global = manager.getGlobalNamespace(); - DBTraceThread thread; + TraceThread thread; DBTraceNamespaceSymbol nsA; DBTraceClassSymbol clsA; TraceLabelSymbol lab1; @@ -561,7 +561,7 @@ public class DBTraceSymbolManagerTest extends AbstractGhidraHeadlessIntegrationT public void testDelete() throws DuplicateNameException, InvalidInputException, IllegalArgumentException { DBTraceNamespaceSymbol global = manager.getGlobalNamespace(); - DBTraceThread thread; + TraceThread thread; DBTraceNamespaceSymbol nsA; DBTraceClassSymbol clsA; TraceLabelSymbol lab2; @@ -595,7 +595,7 @@ public class DBTraceSymbolManagerTest extends AbstractGhidraHeadlessIntegrationT public void testSaveAndLoad() throws DuplicateNameException, InvalidInputException, IllegalArgumentException, CancelledException, IOException, VersionException { DBTraceNamespaceSymbol global = manager.getGlobalNamespace(); - DBTraceThread thread; + TraceThread thread; DBTraceNamespaceSymbol nsA; DBTraceClassSymbol clsA; TraceLabelSymbol lab1; @@ -640,7 +640,7 @@ public class DBTraceSymbolManagerTest extends AbstractGhidraHeadlessIntegrationT public void testUndoThenRedo() throws DuplicateNameException, InvalidInputException, IllegalArgumentException, IOException { DBTraceNamespaceSymbol global = manager.getGlobalNamespace(); - DBTraceThread thread; + TraceThread thread; DBTraceNamespaceSymbol nsA; DBTraceClassSymbol clsA; TraceLabelSymbol lab1; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java new file mode 100644 index 0000000000..6ddc63c266 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/target/DBTraceObjectManagerTest.java @@ -0,0 +1,1047 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.target; + +import static org.junit.Assert.*; + +import java.io.File; +import java.util.*; +import java.util.stream.Collectors; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Range; + +import generic.Unique; +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.dbg.util.PathPredicates; +import ghidra.dbg.util.PathUtils; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.model.target.*; +import ghidra.trace.model.target.TraceObject.ConflictResolution; +import ghidra.trace.model.thread.TraceObjectThread; +import ghidra.util.database.*; + +public class DBTraceObjectManagerTest extends AbstractGhidraHeadlessIntegrationTest { + protected ToyDBTraceBuilder b; + protected DBTraceObjectManager manager; + + protected SchemaContext ctx; + + protected TraceObject root; + protected TraceObject targetContainer; + protected List targets = new ArrayList<>(); + + @Before + public void setUpObjectManagerTest() throws Exception { + b = new ToyDBTraceBuilder("Testing", "Toy:BE:64:default"); + manager = b.trace.getObjectManager(); + + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + } + + protected void populateModel(int targetCount) { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectKeyPath pathTargets = TraceObjectKeyPath.of("Targets"); + targetContainer = manager.createObject(pathTargets, Range.atLeast(0L)); + root.setAttribute(Range.atLeast(0L), "Targets", targetContainer); + dumpStore(manager.valueStore); + + for (int i = 0; i < targetCount; i++) { + TraceObject target = + manager.createObject(pathTargets.index(i), Range.atLeast((long) i)); + target.setAttribute(target.getLifespan(), "self", target); + dumpStore(manager.valueStore); + targetContainer.setElement(target.getLifespan(), i, target); + dumpStore(manager.valueStore); + targets.add(target); + root.setAttribute(target.getLifespan(), "curTarget", target); + dumpStore(manager.valueStore); + } + + root.setValue(Range.all(), "anAttribute", "A primitive string"); + dumpStore(manager.valueStore); + } + } + + @Test + public void testGetTrace() { + assertEquals(b.trace, manager.getTrace()); + } + + @Test(expected = IllegalStateException.class) + public void testCreateObjectWithRootErr() { + try (UndoableTransaction tid = b.startTransaction()) { + manager.createObject(TraceObjectKeyPath.of("Test"), Range.all()); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateObjectAsRootErr() { + try (UndoableTransaction tid = b.startTransaction()) { + manager.createObject(TraceObjectKeyPath.of(), Range.all()); + } + } + + @Test(expected = IllegalArgumentException.class) + public void testCreateObjectAsRootErrRootExists() { + try (UndoableTransaction tid = b.startTransaction()) { + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + manager.createObject(TraceObjectKeyPath.of(), Range.all()); + } + } + + @Test + public void testCreateRoot() { + try (UndoableTransaction tid = b.startTransaction()) { + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } + + @Test(expected = IllegalStateException.class) + public void testCreate2ndRootErr() { + try (UndoableTransaction tid = b.startTransaction()) { + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } + + @Test + public void testGetRoot() { + assertNull(manager.getRootObject()); + assertNull(manager.getRootSchema()); + TraceObjectValue value; + try (UndoableTransaction tid = b.startTransaction()) { + value = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + assertEquals(value.getValue(), manager.getRootObject()); + assertNotNull(manager.getRootSchema()); + } + + @Test + public void testCreateObject() { + TraceObject obj; + try (UndoableTransaction tid = b.startTransaction()) { + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + obj = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all()); + } + assertEquals(TraceObjectKeyPath.of("Targets"), obj.getCanonicalPath()); + assertEquals(Range.all(), obj.getLifespan()); + } + + @Test + public void testGetObjectsByCanonicalPath() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all()); + } + + assertTrue(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of("Nothing")).isEmpty()); + assertEquals(root, + Unique.assertOne(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of()))); + assertEquals(targetContainer, + Unique.assertOne(manager.getObjectsByCanonicalPath(TraceObjectKeyPath.of("Targets")))); + } + + @Test + public void testGetValuesByPathRootOnly() { + assertEquals(0, manager.getValuePaths(Range.all(), PathPredicates.pattern()).count()); + + try (UndoableTransaction tid = b.startTransaction()) { + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + assertEquals(1, manager.getValuePaths(Range.all(), PathPredicates.pattern()).count()); + } + + @Test + public void testGetObjectsByPath() { + populateModel(2); + + assertEquals(1, + manager.getObjectsByPath(Range.singleton(0L), TraceObjectKeyPath.parse("Targets[0]")) + .count()); + assertEquals(0, + manager.getObjectsByPath(Range.singleton(0L), TraceObjectKeyPath.parse("Targets[1]")) + .count()); + assertEquals(1, + manager.getObjectsByPath(Range.singleton(1L), TraceObjectKeyPath.parse("Targets[1]")) + .count()); + assertEquals(2, + manager.getObjectsByPath(Range.all(), TraceObjectKeyPath.parse("curTarget")).count()); + + TraceObject target1 = + manager.getObjectsByPath(Range.all(), TraceObjectKeyPath.parse("Targets[1]")) + .findAny() + .get(); + assertEquals(target1, + manager.getObjectsByPath(Range.singleton(1L), TraceObjectKeyPath.parse("curTarget")) + .findAny() + .get()); + } + + @Test + public void testGetRangeValues() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + targetContainer = + manager.createObject(TraceObjectKeyPath.parse("Targets"), Range.all()); + root.setAttribute(Range.all(), "Targets", targetContainer); + + TraceObjectValue rangeVal = + root.setValue(Range.atLeast(0L), "a", b.range(0x1000, 0x1fff)); + + assertTrue(root.getValues().contains(rangeVal)); + assertFalse(targetContainer.getValues().contains(rangeVal)); + assertEquals(rangeVal, root.getValue(0, "a")); + assertNull(root.getValue(0, "b")); + + assertEquals(Set.of(rangeVal), + root.getSuccessors(Range.all(), PathPredicates.parse("a")) + .map(p -> p.getLastEntry()) + .collect(Collectors.toSet())); + assertEquals(Set.of(), + root.getSuccessors(Range.atMost(-1L), PathPredicates.parse("a")) + .map(p -> p.getLastEntry()) + .collect(Collectors.toSet())); + assertEquals(Set.of(), + root.getSuccessors(Range.all(), PathPredicates.parse("b")) + .map(p -> p.getLastEntry()) + .collect(Collectors.toSet())); + assertEquals(Set.of(), + targetContainer.getSuccessors(Range.all(), PathPredicates.parse("a")) + .map(p -> p.getLastEntry()) + .collect(Collectors.toSet())); + + assertEquals(Set.of(rangeVal), + Set.copyOf(manager.getValuesIntersecting(Range.all(), b.range(0, -1)))); + assertEquals(Set.of(), + Set.copyOf(manager.getValuesIntersecting(Range.atMost(-1L), b.range(0, -1)))); + assertEquals(Set.of(), + Set.copyOf(manager.getValuesIntersecting(Range.all(), b.range(0, 0xfff)))); + } + } + + @Test + public void testQueryAllInterface() { + populateModel(3); + TraceObject thread; + try (UndoableTransaction tid = b.startTransaction()) { + thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"), + Range.atLeast(0L)); + thread.insert(ConflictResolution.DENY); + } + + assertEquals(Set.of(), + manager.queryAllInterface(Range.atMost(-1L), TraceObjectThread.class) + .collect(Collectors.toSet())); + assertEquals(Set.of(thread.queryInterface(TraceObjectThread.class)), + manager.queryAllInterface(Range.all(), TraceObjectThread.class) + .collect(Collectors.toSet())); + } + + @Test + public void testClear() { + populateModel(3); + assertEquals(5, manager.getAllObjects().size()); + + try (UndoableTransaction tid = b.startTransaction()) { + manager.clear(); + } + assertEquals(0, manager.getAllObjects().size()); + + populateModel(3); + assertEquals(5, manager.getAllObjects().size()); + } + + @Test + public void testUndoRedo() throws Exception { + populateModel(3); + assertEquals(5, manager.getAllObjects().size()); + + b.trace.undo(); + assertEquals(0, manager.getAllObjects().size()); + + b.trace.redo(); + assertEquals(5, manager.getAllObjects().size()); + } + + @Test + public void testAbort() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + populateModel(3); + assertEquals(5, manager.getAllObjects().size()); + + tid.abort(); + } + + assertEquals(0, manager.getAllObjects().size()); + + populateModel(3); + assertEquals(5, manager.getAllObjects().size()); + } + + @Test + public void testObjectGetTrace() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + } + assertEquals(b.trace, root.getTrace()); + } + + @Test + public void testIsRoot() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + targetContainer = manager.createObject(TraceObjectKeyPath.of("Targets"), Range.all()); + } + + assertTrue(root.isRoot()); + assertFalse(targetContainer.isRoot()); + } + + @Test + public void testGetAllPaths() { + populateModel(3); + + TraceObject object = Unique.assertOne( + manager.getObjectsByPath(Range.singleton(0L), TraceObjectKeyPath.parse("curTarget"))); + + List paths = + object.getAllPaths(Range.singleton(0L)).collect(Collectors.toList()); + assertEquals(4, paths.size()); + + paths.sort(Comparator.naturalOrder()); + TraceObjectValPath path; + + path = paths.get(0); + assertEquals(object, path.getLastChild(root)); + assertEquals(PathUtils.parse("Targets[0]"), path.getKeyList()); + + path = paths.get(1); + assertEquals(object, path.getLastChild(root)); + assertEquals(PathUtils.parse("Targets[0].self"), path.getKeyList()); + + path = paths.get(2); + assertEquals(object, path.getLastChild(root)); + assertEquals(List.of("curTarget"), path.getKeyList()); + + path = paths.get(3); + assertEquals(object, path.getLastChild(root)); + assertEquals(PathUtils.parse("curTarget.self"), path.getKeyList()); + + paths = root.getAllPaths(Range.all()).collect(Collectors.toList()); + assertEquals(1, paths.size()); + path = paths.get(0); + assertEquals(root, path.getLastChild(root)); + } + + @Test + public void testObjectSetLifespan() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + assertEquals(Range.all(), root.getLifespan()); + + root.setLifespan(Range.singleton(0L)); + assertEquals(Range.singleton(0L), root.getLifespan()); + + try { + root.setMinSnap(10); + fail(); + } + catch (IllegalArgumentException e) { + // pass + } + try { + root.setMaxSnap(-10); + fail(); + } + catch (IllegalArgumentException e) { + // pass + } + + root.setMinSnap(-10); + assertEquals(Range.closed(-10L, 0L), root.getLifespan()); + assertEquals(-10, root.getMinSnap()); + + root.setMaxSnap(10); + assertEquals(Range.closed(-10L, 10L), root.getLifespan()); + assertEquals(10, root.getMaxSnap()); + } + } + + @Test + public void testGetInterfaces() { + TraceObject thread; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"), + Range.atLeast(0L)); + thread.insert(ConflictResolution.DENY); + } + assertEquals(Set.of(), root.getInterfaces()); + assertEquals(Set.of(TraceObjectThread.class), thread.getInterfaces()); + } + + @Test + public void testQueryInterface() { + TraceObject thread; + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + thread = manager.createObject(TraceObjectKeyPath.parse("Targets[0].Threads[0]"), + Range.atLeast(0L)); + thread.insert(ConflictResolution.DENY); + } + assertNull(root.queryInterface(TraceObjectThread.class)); + TraceObjectThread threadIf = thread.queryInterface(TraceObjectThread.class); + assertNotNull(threadIf); + assertSame(threadIf, thread.queryInterface(TraceObjectThread.class)); + } + + @Test + public void testGetParents() { + populateModel(3); + + assertEquals(1, root.getParents().size()); + assertEquals(root, Unique.assertOne(targetContainer.getParents()).getParent()); + assertEquals(3, targets.get(0).getParents().size()); // curTarget, targetContainer, self + } + + @Test + public void testGetValues() { + populateModel(3); + + assertEquals(3, targetContainer.getValues().size()); + } + + @Test + public void testGetElements() { + populateModel(3); + + assertEquals(0, root.getElements().size()); + assertEquals(3, targetContainer.getElements().size()); + } + + @Test + public void testGetAttributes() { + populateModel(3); + + assertEquals(5, root.getAttributes().size()); // Targets, curTarget(x3), string + assertEquals(0, targetContainer.getAttributes().size()); + } + + @Test + public void testGetValue() { + populateModel(3); + + assertEquals(targetContainer, root.getValue(0, "Targets").getChild()); + assertEquals(targets.get(0), targetContainer.getValue(0, "[0]").getChild()); + } + + @Test + public void testGetElement() { + populateModel(3); + + assertEquals(targets.get(0), targetContainer.getElement(0, 0).getChild()); + } + + @Test + public void testGetAttribute() { + populateModel(3); + + assertEquals(targets.get(0), root.getAttribute(0, "curTarget").getChild()); + assertEquals(targets.get(1), root.getAttribute(1, "curTarget").getChild()); + assertEquals(targets.get(2), root.getAttribute(2, "curTarget").getChild()); + + try { + targetContainer.getAttribute(0, "[0]"); + fail(); + } + catch (IllegalArgumentException e) { + // pass + } + } + + @Test + public void testGetSuccessors() { + populateModel(3); + + assertEquals(1, root.getSuccessors(Range.all(), PathPredicates.parse("Targets")).count()); + + assertEquals(1, + root.getSuccessors(Range.singleton(0L), PathPredicates.parse("Targets[]")).count()); + assertEquals(1, + targetContainer.getSuccessors(Range.singleton(0L), PathPredicates.parse("[]")).count()); + assertEquals(3, + targetContainer.getSuccessors(Range.all(), PathPredicates.parse("[]")).count()); + + assertEquals(3, + root.getSuccessors(Range.all(), PathPredicates.parse("curTarget")).count()); + assertEquals(2, + root.getSuccessors(Range.closed(0L, 1L), PathPredicates.parse("curTarget")).count()); + assertEquals(1, + root.getSuccessors(Range.singleton(1L), PathPredicates.parse("curTarget")).count()); + assertEquals(0, + root.getSuccessors(Range.atMost(-1L), PathPredicates.parse("curTarget")).count()); + + assertEquals(1, + root.getSuccessors(Range.all(), PathPredicates.parse("anAttribute")).count()); + assertEquals(0, + root.getSuccessors(Range.all(), PathPredicates.parse("anAttribute.nope")).count()); + } + + @Test + public void testSetValue_TruncatesOrDeletes() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.all(), "a", 1); + TraceObjectValue valB = root.setValue(Range.singleton(0L), "b", 2); + + assertEquals(Range.all(), valA.getLifespan()); + assertEquals("a", valA.getEntryKey()); + assertEquals(1, valA.getValue()); + + assertEquals(Range.singleton(0L), valB.getLifespan()); + assertEquals("b", valB.getEntryKey()); + assertEquals(2, valB.getValue()); + + TraceObjectValue valA2 = root.setValue(Range.closed(0L, 10L), "a", 2); + assertEquals(Range.closed(0L, 10L), valA2.getLifespan()); + + assertEquals(Range.atMost(-1L), valA.getLifespan()); + assertFalse(valB.isDeleted()); + assertEquals(Range.singleton(0L), valB.getLifespan()); + + TraceObjectValue valA3 = root.getValue(11, "a"); + assertNotNull(valA3); + assertEquals(Range.atLeast(11L), valA3.getLifespan()); + assertEquals("a", valA3.getEntryKey()); + assertEquals(1, valA3.getValue()); + + TraceObjectValue valA4 = root.setValue(Range.closed(5L, 15L), "a", 4); + assertEquals(Range.atMost(-1L), valA.getLifespan()); + assertEquals(Range.closed(0L, 4L), valA2.getLifespan()); + assertEquals(Range.closed(5L, 15L), valA4.getLifespan()); + + TraceObjectValue valA5 = root.getValue(16, "a"); + assertEquals(Range.atLeast(16L), valA5.getLifespan()); + + root.setValue(Range.all(), "a", "Clobber"); + assertTrue(valA.isDeleted()); + assertTrue(valA2.isDeleted()); + assertTrue(valA3.isDeleted()); + assertTrue(valA4.isDeleted()); + assertTrue(valA5.isDeleted()); + } + } + + @Test + public void testSetValue_AbutLeftCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + + assertEquals(valA, root.setValue(Range.closed(-10L, -1L), "a", 1)); + assertEquals(Range.closed(-10L, 9L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetRangeValue_AbutLeftCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = + root.setValue(Range.closed(0L, 9L), "a", b.range(0x1000, 0x1fff)); + + assertEquals(valA, + root.setValue(Range.closed(-10L, -1L), "a", b.range(0x1000, 0x1fff))); + assertEquals(Range.closed(-10L, 9L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValue_AbutRightCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + + assertEquals(valA, root.setValue(Range.closed(10L, 19L), "a", 1)); + assertEquals(Range.closed(0L, 19L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValue_IntersectLeftCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + + assertEquals(valA, root.setValue(Range.closed(-5L, 4L), "a", 1)); + assertEquals(Range.closed(-5L, 9L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValue_IntersectRightCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + + assertEquals(valA, root.setValue(Range.closed(5L, 14L), "a", 1)); + assertEquals(Range.closed(0L, 14L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValue_EqualSpansCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + + assertEquals(valA, root.setValue(Range.closed(0L, 9L), "a", 1)); + assertEquals(Range.closed(0L, 9L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValue_ContainsCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + + assertEquals(valA, root.setValue(Range.singleton(5L), "a", 1)); + assertEquals(Range.closed(0L, 9L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + + assertEquals(valA, root.setValue(Range.closed(-5L, 14L), "a", 1)); + assertEquals(Range.closed(-5L, 14L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValue_SameLeftCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + + assertEquals(valA, root.setValue(Range.closed(0L, 5L), "a", 1)); + assertEquals(Range.closed(0L, 9L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + + assertEquals(valA, root.setValue(Range.closed(0L, 14L), "a", 1)); + assertEquals(Range.closed(0L, 14L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValue_SameRightCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + + assertEquals(valA, root.setValue(Range.closed(5L, 9L), "a", 1)); + assertEquals(Range.closed(0L, 9L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + + assertEquals(valA, root.setValue(Range.closed(-5L, 9L), "a", 1)); + assertEquals(Range.closed(-5L, 9L), valA.getLifespan()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValue_ConnectDisjointCoalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + TraceObjectValue valB = root.setValue(Range.closed(20L, 29L), "a", 1); + assertNotSame(valA, valB); + assertEquals(2, root.getValues().size()); + + assertEquals(valA, root.setValue(Range.closed(10L, 19L), "a", 1)); + assertEquals(Range.closed(0L, 29L), valA.getLifespan()); + assertTrue(valB.isDeleted()); + assertEquals(1, root.getValues().size()); + } + } + + @Test + public void testSetValuePrimitives() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + root.setValue(Range.all(), "aBool", true); + root.setValue(Range.all(), "aByte", (byte) 2); + root.setValue(Range.all(), "aChar", 'c'); + root.setValue(Range.all(), "aShort", (short) 4); + root.setValue(Range.all(), "anInt", 5); + root.setValue(Range.all(), "aLong", 6L); + root.setValue(Range.all(), "aString", "Hello, 7"); + root.setValue(Range.all(), "aRange", b.range(0x1000, 0x1fff)); + root.setValue(Range.all(), "anAddress", b.addr(0x2000)); + + root.setValue(Range.all(), "anEmptyIntArr", new int[] {}); + root.setValue(Range.all(), "aBoolArr", new boolean[] { false, true }); + root.setValue(Range.all(), "aByteArr", new byte[] { 2, 3, 4 }); + root.setValue(Range.all(), "aCharArr", new char[] { 'e', 'f', 'g', 'h' }); + root.setValue(Range.all(), "aShortArr", new short[] { 9, 10, 11, 12, 13 }); + root.setValue(Range.all(), "anIntArr", new int[] { 14, 15, 16, 17 }); + root.setValue(Range.all(), "aLongArr", new long[] { 18, 19 }); + root.setValue(Range.all(), "aStringArr", new String[] { "Hello,", "20" }); + } + + assertEquals(true, root.getValue(0, "aBool").getValue()); + assertEquals((byte) 2, root.getValue(0, "aByte").getValue()); + assertEquals('c', root.getValue(0, "aChar").getValue()); + assertEquals((short) 4, root.getValue(0, "aShort").getValue()); + assertEquals(5, root.getValue(0, "anInt").getValue()); + assertEquals(6L, root.getValue(0, "aLong").getValue()); + assertEquals("Hello, 7", root.getValue(0, "aString").getValue()); + assertEquals(b.range(0x1000, 0x1fff), root.getValue(0, "aRange").getValue()); + assertEquals(b.addr(0x2000), root.getValue(0, "anAddress").getValue()); + + assertArrayEquals(new int[] {}, (int[]) root.getValue(0, "anEmptyIntArr").getValue()); + assertArrayEquals(new boolean[] { false, true }, + (boolean[]) root.getValue(0, "aBoolArr").getValue()); + assertArrayEquals(new byte[] { 2, 3, 4 }, (byte[]) root.getValue(0, "aByteArr").getValue()); + assertArrayEquals(new char[] { 'e', 'f', 'g', 'h' }, + (char[]) root.getValue(0, "aCharArr").getValue()); + assertArrayEquals(new short[] { 9, 10, 11, 12, 13 }, + (short[]) root.getValue(0, "aShortArr").getValue()); + assertArrayEquals(new int[] { 14, 15, 16, 17 }, + (int[]) root.getValue(0, "anIntArr").getValue()); + assertArrayEquals(new long[] { 18, 19 }, (long[]) root.getValue(0, "aLongArr").getValue()); + assertArrayEquals(new String[] { "Hello,", "20" }, + (String[]) root.getValue(0, "aStringArr").getValue()); + + File saved = b.save(); + + try (ToyDBTraceBuilder loaded = new ToyDBTraceBuilder(saved)) { + TraceObject root2 = loaded.trace.getObjectManager().getRootObject(); + + assertEquals(true, root2.getValue(0, "aBool").getValue()); + assertEquals((byte) 2, root2.getValue(0, "aByte").getValue()); + assertEquals('c', root2.getValue(0, "aChar").getValue()); + assertEquals((short) 4, root2.getValue(0, "aShort").getValue()); + assertEquals(5, root2.getValue(0, "anInt").getValue()); + assertEquals(6L, root2.getValue(0, "aLong").getValue()); + assertEquals("Hello, 7", root2.getValue(0, "aString").getValue()); + + assertArrayEquals(new int[] {}, (int[]) root2.getValue(0, "anEmptyIntArr").getValue()); + assertArrayEquals(new boolean[] { false, true }, + (boolean[]) root2.getValue(0, "aBoolArr").getValue()); + assertArrayEquals(new byte[] { 2, 3, 4 }, + (byte[]) root2.getValue(0, "aByteArr").getValue()); + assertArrayEquals(new char[] { 'e', 'f', 'g', 'h' }, + (char[]) root2.getValue(0, "aCharArr").getValue()); + assertArrayEquals(new short[] { 9, 10, 11, 12, 13 }, + (short[]) root2.getValue(0, "aShortArr").getValue()); + assertArrayEquals(new int[] { 14, 15, 16, 17 }, + (int[]) root2.getValue(0, "anIntArr").getValue()); + assertArrayEquals(new long[] { 18, 19 }, + (long[]) root2.getValue(0, "aLongArr").getValue()); + assertArrayEquals(new String[] { "Hello,", "20" }, + (String[]) root2.getValue(0, "aStringArr").getValue()); + } + } + + @Test + public void testSetAttribute() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + root.setAttribute(root.getLifespan(), "myAttribute", 1234); + + try { + root.setAttribute(root.getLifespan(), "[0]", 1234); + fail(); + } + catch (IllegalArgumentException e) { + // pass + } + } + + assertEquals(1234, root.getAttribute(0, "myAttribute").getValue()); + assertNull(root.getValue(0, "[0]")); + } + + @Test + public void testObjectDelete() { + populateModel(3); + + // Delete a leaf + TraceObject t1 = targets.get(1); + assertFalse(t1.isDeleted()); + assertEquals(3, targetContainer.getValues().size()); + assertEquals(t1, Unique.assertOne( + manager.getObjectsByPath(Range.all(), TraceObjectKeyPath.parse("Targets[1]")))); + assertEquals(t1, t1.getAttribute(1, "self").getValue()); + assertEquals(t1, root.getValue(1, "curTarget").getValue()); + + try (UndoableTransaction tid = b.startTransaction()) { + t1.delete(); + } + + assertTrue(t1.isDeleted()); + assertTrue(t1.getParents().isEmpty()); + assertEquals(2, targetContainer.getValues().size()); + assertEquals(0, + manager.getObjectsByPath(Range.all(), TraceObjectKeyPath.parse("Targets[1]")).count()); + assertNull(t1.getAttribute(2, "self")); + assertNull(root.getValue(1, "curTarget")); + + // Delete a branch (leaves stay, but detached) + TraceObject t0 = targets.get(0); + assertEquals(2, + manager.getObjectsByPath(Range.all(), TraceObjectKeyPath.parse("Targets[]")).count()); + assertTrue(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer)); + + try (UndoableTransaction tid = b.startTransaction()) { + targetContainer.delete(); + } + + assertEquals(0, + manager.getObjectsByPath(Range.all(), TraceObjectKeyPath.parse("Targets[]")).count()); + assertFalse(t0.isDeleted()); + assertFalse(t0.getParents().stream().anyMatch(v -> v.getParent() == targetContainer)); + // A little odd, but allows branch to be replaced and successors restored later + assertEquals(t0, root.getValue(0, "curTarget").getValue()); + } + + @Test + public void testDeleteTree() { + populateModel(3); + + // This deletes everything except the root, since curTarget refers to successors + try (UndoableTransaction tid = b.startTransaction()) { + targetContainer.deleteTree(); + } + + assertTrue(targetContainer.isDeleted()); + for (TraceObject t : targets) { + assertTrue(t.isDeleted()); + } + + assertEquals(0, root.getSuccessors(Range.all(), PathPredicates.parse("curTarget")).count()); + assertEquals(root, Unique.assertOne(manager.getAllObjects())); + } + + @Test + public void testObjectTruncateOrDelete() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + try { + root.truncateOrDelete(Range.singleton(0L)); + fail(); + } + catch (IllegalArgumentException e) { + // pass + } + assertEquals(Range.all(), root.getLifespan()); + + assertEquals(root, root.truncateOrDelete(Range.atLeast(10L))); + assertEquals(Range.atMost(9L), root.getLifespan()); + + assertEquals(root, root.truncateOrDelete(Range.atMost(-1L))); + assertEquals(Range.closed(0L, 9L), root.getLifespan()); + + assertNull(root.truncateOrDelete(Range.all())); + assertTrue(root.isDeleted()); + } + } + + @Test + public void testValueSetLifespan_TruncatesOrDeletes() { + try (UndoableTransaction tid = b.startTransaction()) { + TraceObjectValue rootVal = + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + root = rootVal.getChild(); + + try { + rootVal.setLifespan(Range.singleton(0L)); + fail(); + } + catch (IllegalArgumentException e) { + // pass + } + assertEquals(Range.all(), rootVal.getLifespan()); + + TraceObjectValue val1234 = root.setValue(Range.all(), "myValue", 1234); + TraceObjectValue val2345 = root.setValue(Range.atLeast(10L), "myValue", 2345); + + assertEquals(Range.atMost(9L), val1234.getLifespan()); + + val1234.setMaxSnap(19); + assertEquals(19, val1234.getMaxSnap()); + assertEquals(Range.atMost(19L), val1234.getLifespan()); + assertEquals(Range.atLeast(20L), val2345.getLifespan()); + + val2345.setMinSnap(10); + assertEquals(10, val2345.getMinSnap()); + assertEquals(Range.atLeast(10L), val2345.getLifespan()); + assertEquals(Range.atMost(9L), val1234.getLifespan()); + + val1234.setLifespan(Range.closed(20L, 30L)); + assertEquals(Range.closed(20L, 30L), val1234.getLifespan()); + assertEquals(Range.closed(10L, 19L), val2345.getLifespan()); + assertEquals(Range.atLeast(31L), root.getValue(31, "myValue").getLifespan()); + + val1234.setLifespan(Range.all()); + assertEquals(Range.all(), val1234.getLifespan()); + assertTrue(val2345.isDeleted()); + } + } + + protected void dumpStore(DBCachedObjectStore store) { + /*System.err.println("Contents of " + store); + for (T t : store.asMap().values()) { + System.err.println( + " " + t.getClass().getSimpleName() + "(key=" + t.getKey() + ",obj=" + t + ")"); + }*/ + } + + @Test + public void testValueSetLifespan_Coalesces() { + try (UndoableTransaction tid = b.startTransaction()) { + root = manager.createRootObject(ctx.getSchema(new SchemaName("Session"))).getChild(); + + TraceObjectValue valA = root.setValue(Range.closed(0L, 9L), "a", 1); + TraceObjectValue valB = root.setValue(Range.closed(20L, 29L), "a", 1); + assertNotSame(valA, valB); + + valA.setLifespan(Range.closed(5L, 20L)); + assertEquals(Range.closed(5L, 29L), valA.getLifespan()); + assertTrue(valB.isDeleted()); + } + } + + @Test + public void testIsCanonical() { + try (UndoableTransaction tid = b.startTransaction()) { + TraceObjectValue rootVal = + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + root = rootVal.getChild(); + + assertTrue(rootVal.isCanonical()); + + TraceObjectValue primVal = root.setValue(Range.all(), "primitive", "A string"); + assertFalse(primVal.isCanonical()); + + TraceObject child = + manager.createObject(TraceObjectKeyPath.parse("child"), Range.all()); + + TraceObjectValue objVal = root.setValue(Range.all(), "child", child); + assertTrue(objVal.isCanonical()); + + TraceObjectValue linkVal = root.setValue(Range.all(), "link", child); + assertFalse(linkVal.isCanonical()); + } + } + + @Test + public void testValueDelete() { + try (UndoableTransaction tid = b.startTransaction()) { + TraceObjectValue rootVal = + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + root = rootVal.getChild(); + + try { + rootVal.delete(); + fail(); + } + catch (IllegalArgumentException e) { + // pass + } + + TraceObjectValue val = root.setValue(Range.all(), "myValue", 1234); + assertFalse(val.isDeleted()); + assertEquals(val, root.getValue(0, "myValue")); + + val.delete(); + assertTrue(val.isDeleted()); + assertNull(root.getValue(0, "myValue")); + } + } + + @Test + public void testValueTruncateOrDelete() { + try (UndoableTransaction tid = b.startTransaction()) { + TraceObjectValue rootVal = + manager.createRootObject(ctx.getSchema(new SchemaName("Session"))); + root = rootVal.getChild(); + + try { + rootVal.truncateOrDelete(Range.atLeast(0L)); + fail(); + } + catch (IllegalArgumentException e) { + // pass + } + + TraceObjectValue stringVal = root.setValue(Range.all(), "myValue", "A string"); + + assertEquals(stringVal, stringVal.truncateOrDelete(Range.atLeast(11L))); + assertEquals(Range.atMost(10L), stringVal.getLifespan()); + + TraceObjectValue split = stringVal.truncateOrDelete(Range.singleton(0L)); + assertNotSame(stringVal, split); + assertEquals(Range.atMost(-1L), stringVal.getLifespan()); + assertEquals(Range.closed(1L, 10L), split.getLifespan()); + assertEquals("A string", split.getValue()); + + assertNull(stringVal.truncateOrDelete(Range.all())); + assertTrue(stringVal.isDeleted()); + assertFalse(split.isDeleted()); // Other values not affected + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerObjectTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerObjectTest.java new file mode 100644 index 0000000000..b4430f7e3e --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerObjectTest.java @@ -0,0 +1,49 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.trace.database.thread; + +import org.junit.Before; + +import ghidra.dbg.target.schema.SchemaContext; +import ghidra.dbg.target.schema.TargetObjectSchema.SchemaName; +import ghidra.dbg.target.schema.XmlSchemaContext; +import ghidra.util.database.UndoableTransaction; + +public class DBTraceThreadManagerObjectTest extends DBTraceThreadManagerTest { + + protected SchemaContext ctx; + + @Before + public void setUpObjectsMode() throws Exception { + ctx = XmlSchemaContext.deserialize("" + // + "" + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + " " + // + ""); + + try (UndoableTransaction tid = b.startTransaction()) { + b.trace.getObjectManager().createRootObject(ctx.getSchema(new SchemaName("Session"))); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java index 298263ce61..27d02e103f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/thread/DBTraceThreadManagerTest.java @@ -15,9 +15,9 @@ */ package ghidra.trace.database.thread; -import static ghidra.lifecycle.Unfinished.TODO; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; + +import java.util.Set; import org.junit.*; @@ -25,6 +25,7 @@ import com.google.common.collect.Range; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.database.UndoableTransaction; import ghidra.util.exception.DuplicateNameException; @@ -33,6 +34,16 @@ public class DBTraceThreadManagerTest extends AbstractGhidraHeadlessIntegrationT ToyDBTraceBuilder b; DBTraceThreadManager threadManager; + TraceThread thread1; + TraceThread thread2; + + protected void addThreads() throws Exception { + try (UndoableTransaction tid = b.startTransaction()) { + thread1 = threadManager.createThread("Threads[1]", 0); + thread2 = threadManager.addThread("Threads[2]", Range.closed(0L, 10L)); + } + } + @Before public void setUpThreadManagerTest() throws Exception { b = new ToyDBTraceBuilder("Testing", "Toy:BE:64:default"); @@ -46,25 +57,66 @@ public class DBTraceThreadManagerTest extends AbstractGhidraHeadlessIntegrationT @Test public void testAddThread() throws Exception { + addThreads(); try (UndoableTransaction tid = b.startTransaction()) { - threadManager.createThread("Thread 1", 0); - threadManager.addThread("Thread 2", Range.closed(0L, 10L)); - } - - try (UndoableTransaction tid = b.startTransaction()) { - threadManager.createThread("Thread 1", 1); + threadManager.createThread("Threads[1]", 1); fail(); } catch (DuplicateNameException e) { // pass } - assertEquals(1, threadManager.getThreadsByPath("Thread 1").size()); + assertEquals(Set.of(thread1), Set.copyOf(threadManager.getThreadsByPath("Threads[1]"))); } @Test - @Ignore("TODO") - public void testMore() throws Exception { - TODO(); + public void testGetAllThreads() throws Exception { + assertEquals(Set.of(), Set.copyOf(threadManager.getAllThreads())); + + addThreads(); + assertEquals(Set.of(thread1, thread2), Set.copyOf(threadManager.getAllThreads())); + } + + @Test + public void testGetThreadsByPath() throws Exception { + assertEquals(Set.of(), Set.copyOf(threadManager.getThreadsByPath("Threads[1]"))); + + addThreads(); + assertEquals(Set.of(thread1), Set.copyOf(threadManager.getThreadsByPath("Threads[1]"))); + assertEquals(Set.of(thread2), Set.copyOf(threadManager.getThreadsByPath("Threads[2]"))); + } + + @Test + public void testLiveThreadByPath() throws Exception { + assertNull(threadManager.getLiveThreadByPath(0, "Threads[1]")); + + addThreads(); + assertEquals(thread1, threadManager.getLiveThreadByPath(0, "Threads[1]")); + assertEquals(thread2, threadManager.getLiveThreadByPath(0, "Threads[2]")); + assertEquals(thread2, threadManager.getLiveThreadByPath(10, "Threads[2]")); + assertNull(threadManager.getLiveThreadByPath(0, "Threads[3]")); + assertNull(threadManager.getLiveThreadByPath(-1, "Threads[2]")); + assertNull(threadManager.getLiveThreadByPath(11, "Threads[2]")); + } + + @Test + public void testGetThread() throws Exception { + assertNull(threadManager.getThread(0)); + + addThreads(); + assertEquals(thread1, threadManager.getThread(thread1.getKey())); + assertEquals(thread2, threadManager.getThread(thread2.getKey())); + } + + @Test + public void testGetLiveThreads() throws Exception { + assertEquals(Set.of(), threadManager.getLiveThreads(0)); + + addThreads(); + assertEquals(Set.of(), threadManager.getLiveThreads(-1)); + assertEquals(Set.of(thread1, thread2), threadManager.getLiveThreads(0)); + assertEquals(Set.of(thread1, thread2), threadManager.getLiveThreads(9)); + // NB. Destruction is excluded + assertEquals(Set.of(thread1), threadManager.getLiveThreads(10)); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/generic/Unique.java b/Ghidra/Debug/ProposedUtils/src/main/java/generic/Unique.java index af0af6ee72..d87c78aeea 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/generic/Unique.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/generic/Unique.java @@ -16,6 +16,8 @@ package generic; import java.util.Iterator; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * Some utilities for when singleton collections are expected @@ -42,6 +44,18 @@ public interface Unique { return result; } + /** + * Assert that exactly one element is in a stream and get that element + * + * @param the type of element + * @param st the stream + * @return the element + * @throws AssertionError if no element or many elements exist in the stream + */ + static T assertOne(Stream st) { + return assertOne(st.collect(Collectors.toList())); + } + /** * Assert that at most one element is in an iterable and get that element or {@code null} * diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java index e131f0ecce..dab9b4f9b2 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/util/database/DBCachedObjectStoreFactory.java @@ -18,8 +18,13 @@ package ghidra.util.database; import java.io.IOException; import java.lang.reflect.*; import java.lang.reflect.Field; -import java.nio.ByteBuffer; +import java.nio.*; +import java.nio.charset.*; import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; import db.*; import ghidra.util.Msg; @@ -405,6 +410,283 @@ public class DBCachedObjectStoreFactory { } } + public interface PrimitiveCodec { + byte getSelector(); + + T decode(ByteBuffer buffer); + + void encode(ByteBuffer buffer, T value); + + Class getValueClass(); + + abstract class AbstractPrimitiveCodec implements PrimitiveCodec { + static byte nextSelector = 0; + protected final byte selector = nextSelector++; + protected final Class valueClass; + + public AbstractPrimitiveCodec(Class valueClass) { + this.valueClass = valueClass; + } + + @Override + public byte getSelector() { + return selector; + } + + @Override + public Class getValueClass() { + return valueClass; + } + } + + class SimplePrimitiveCodec extends AbstractPrimitiveCodec { + protected final Function decode; + protected final BiConsumer encode; + + public SimplePrimitiveCodec(Class valueClass, Function decode, + BiConsumer encode) { + super(valueClass); + this.decode = decode; + this.encode = encode; + } + + @Override + public T decode(ByteBuffer buffer) { + return decode.apply(buffer); + } + + @Override + public void encode(ByteBuffer buffer, T value) { + encode.accept(buffer, value); + } + } + + class ArrayPrimitiveCodec extends AbstractPrimitiveCodec { + protected final PrimitiveCodec elemCodec; + protected final Class elemClass; + + public

ArrayPrimitiveCodec(Class valueClass, PrimitiveCodec elemCodec) { + super(valueClass); + assert valueClass.isArray(); + this.elemCodec = elemCodec; + this.elemClass = elemCodec.getValueClass(); + } + + @Override + public T decode(ByteBuffer buffer) { + List result = new ArrayList<>(); + while (buffer.hasRemaining()) { + result.add(elemCodec.decode(buffer)); + } + int size = result.size(); + Object arr = Array.newInstance(valueClass.getComponentType(), size); + for (int i = 0; i < size; i++) { + Array.set(arr, i, result.get(i)); + } + return valueClass.cast(arr); + } + + @Override + public void encode(ByteBuffer buffer, T value) { + int len = Array.getLength(value); + for (int i = 0; i < len; i++) { + elemCodec.encode(buffer, elemClass.cast(Array.get(value, i))); + } + } + } + + class ArrayObjectCodec extends ArrayPrimitiveCodec { + @SuppressWarnings("unchecked") + public ArrayObjectCodec(PrimitiveCodec elemCodec) { + super((Class) Array.newInstance(elemCodec.getValueClass(), 0).getClass(), + elemCodec); + } + } + + class LengthBoundCodec extends AbstractPrimitiveCodec { + protected final PrimitiveCodec unbounded; + + public LengthBoundCodec(PrimitiveCodec unbounded) { + super(unbounded.getValueClass()); + this.unbounded = unbounded; + } + + @Override + public T decode(ByteBuffer buffer) { + int length = buffer.getInt(); + int oldLimit = buffer.limit(); + try { + buffer.limit(buffer.position() + length); + return unbounded.decode(buffer); + } + finally { + buffer.limit(oldLimit); + } + } + + @Override + public void encode(ByteBuffer buffer, T value) { + int lenPos = buffer.position(); + buffer.putInt(0); + int startPos = buffer.position(); + unbounded.encode(buffer, value); + int endPos = buffer.position(); + buffer.putInt(lenPos, endPos - startPos); + } + } + + PrimitiveCodec BOOL = new SimplePrimitiveCodec<>(Boolean.class, + buf -> buf.get() != 0, (buf, b) -> buf.put((byte) (b ? 1 : 0))); + PrimitiveCodec BYTE = + new SimplePrimitiveCodec<>(Byte.class, ByteBuffer::get, ByteBuffer::put); + PrimitiveCodec CHAR = + new SimplePrimitiveCodec<>(Character.class, ByteBuffer::getChar, ByteBuffer::putChar); + PrimitiveCodec SHORT = + new SimplePrimitiveCodec<>(Short.class, ByteBuffer::getShort, ByteBuffer::putShort); + PrimitiveCodec INT = + new SimplePrimitiveCodec<>(Integer.class, ByteBuffer::getInt, ByteBuffer::putInt); + PrimitiveCodec LONG = + new SimplePrimitiveCodec<>(Long.class, ByteBuffer::getLong, ByteBuffer::putLong); + PrimitiveCodec STRING = new AbstractPrimitiveCodec<>(String.class) { + final Charset cs = Charset.forName("UTF-8"); + + @Override + public String decode(ByteBuffer buffer) { + CharsetDecoder dec = cs.newDecoder(); + try { + CharBuffer cb = dec.decode(buffer); + return cb.toString(); + } + catch (CharacterCodingException e) { + throw new AssertionError(e); + } + } + + @Override + public void encode(ByteBuffer buffer, String value) { + CharsetEncoder enc = cs.newEncoder(); + enc.encode(CharBuffer.wrap(value), buffer, true); + } + }; + PrimitiveCodec BOOL_ARR = new ArrayPrimitiveCodec<>(boolean[].class, BOOL); + PrimitiveCodec BYTE_ARR = new AbstractPrimitiveCodec<>(byte[].class) { + @Override + public byte[] decode(ByteBuffer buffer) { + byte[] result = new byte[buffer.remaining()]; + buffer.get(result); + return result; + } + + @Override + public void encode(ByteBuffer buffer, byte[] value) { + buffer.put(value); + } + }; + PrimitiveCodec CHAR_ARR = new ArrayPrimitiveCodec<>(char[].class, CHAR); + PrimitiveCodec SHORT_ARR = new ArrayPrimitiveCodec<>(short[].class, SHORT); + PrimitiveCodec INT_ARR = new ArrayPrimitiveCodec<>(int[].class, INT); + PrimitiveCodec LONG_ARR = new ArrayPrimitiveCodec<>(long[].class, LONG); + PrimitiveCodec STRING_ARR = + new ArrayObjectCodec<>(new LengthBoundCodec<>(STRING)); + + Map> CODECS_BY_SELECTOR = Stream + .of(BOOL, BYTE, CHAR, SHORT, INT, LONG, STRING, BOOL_ARR, BYTE_ARR, CHAR_ARR, + SHORT_ARR, INT_ARR, LONG_ARR, STRING_ARR) + .collect(Collectors.toMap(c -> c.getSelector(), c -> c)); + Map, PrimitiveCodec> CODECS_BY_CLASS = CODECS_BY_SELECTOR.values() + .stream() + .collect(Collectors.toMap(c -> c.getValueClass(), c -> c)); + + @SuppressWarnings("unchecked") + static PrimitiveCodec getCodec(Class cls) { + return (PrimitiveCodec) Objects.requireNonNull(CODECS_BY_CLASS.get(cls), + "No variant codec for class " + cls); + } + + static PrimitiveCodec getCodec(byte sel) { + return Objects.requireNonNull(CODECS_BY_SELECTOR.get(sel), + "No variant codec with selector " + sel); + } + } + + public static abstract class AbstractVariantDBFieldCodec + extends AbstractDBFieldCodec { + public AbstractVariantDBFieldCodec(Class objectType, Field field, int column) { + super(Object.class, objectType, BinaryField.class, field, column); + } + + protected abstract PrimitiveCodec getPrimitiveCodec(Class cls); + + protected abstract PrimitiveCodec getPrimitiveCodec(OT obj, byte sel); + + protected byte[] encode(Object value) { + if (value == null) { + return null; + } + @SuppressWarnings("unchecked") + PrimitiveCodec codec = + (PrimitiveCodec) getPrimitiveCodec(value.getClass()); + ByteBuffer buf = ByteBuffer.allocate(1024); + while (true) { + try { + buf.clear(); + buf.put(codec.getSelector()); + codec.encode(buf, value); + buf.flip(); + byte[] result = new byte[buf.remaining()]; + buf.get(result); + return result; + } + catch (BufferOverflowException e) { + buf = ByteBuffer.allocate(buf.capacity() * 2); + } + } + } + + protected Object decode(OT obj, byte[] enc) { + if (enc == null) { + return null; + } + ByteBuffer buf = ByteBuffer.wrap(enc); + PrimitiveCodec codec = getPrimitiveCodec(obj, buf.get()); + return codec.decode(buf); + } + + @Override + public void store(Object value, BinaryField f) { + f.setBinaryData(encode(value)); + } + + @Override + protected void doStore(OT obj, DBRecord record) + throws IllegalArgumentException, IllegalAccessException { + record.setBinaryData(column, encode(getValue(obj))); + } + + @Override + protected void doLoad(OT obj, DBRecord record) + throws IllegalArgumentException, IllegalAccessException { + setValue(obj, decode(obj, record.getBinaryData(column))); + } + } + + public static class VariantDBFieldCodec + extends AbstractVariantDBFieldCodec { + public VariantDBFieldCodec(Class objectType, Field field, int column) { + super(objectType, field, column); + } + + @Override + protected PrimitiveCodec getPrimitiveCodec(Class cls) { + return PrimitiveCodec.getCodec(cls); + } + + @Override + protected PrimitiveCodec getPrimitiveCodec(OT obj, byte sel) { + return PrimitiveCodec.getCodec(sel); + } + } + private static class TableInfo { public final Schema schema; public final int[] indexColumns;