From 51a1933ab38ce9497bfd5e6a4b3d087094671083 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:15:14 -0400 Subject: [PATCH] GP-1230: Add Taint Analysis prototype and emulator framework support --- Ghidra/Debug/Debugger/build.gradle | 2 +- .../Debugger/data/ExtensionPoint.manifest | 2 + .../DebuggerEmuExampleScript.java | 6 +- .../DemoPcodeUseropLibrary.java | 14 +- .../ghidra_scripts/DemoSyscallLibrary.java | 35 +- .../StandAloneEmuExampleScript.java | 30 +- .../DebuggerEmulationServicePlugin.html | 6 + .../AbstractDebuggerMapProposalDialog.java | 7 +- .../debug/gui/DebuggerBlockChooserDialog.java | 7 +- .../core/debug/gui/DebuggerResources.java | 15 + .../debug/gui/action/DebuggerGoToTrait.java | 2 +- .../DebuggerBreakpointsProvider.java | 6 +- .../gui/console/DebuggerConsoleProvider.java | 11 +- .../copying/DebuggerCopyActionsPlugin.java | 3 +- .../DebuggerCopyIntoProgramDialog.java | 10 +- .../DebuggerRegionMapProposalDialog.java | 11 +- .../gui/memory/DebuggerRegionsProvider.java | 13 +- .../modules/DebuggerBlockChooserDialog.java | 243 ----- .../DebuggerModuleMapProposalDialog.java | 11 +- .../gui/modules/DebuggerModulesProvider.java | 21 +- .../DebuggerSectionMapProposalDialog.java | 11 +- .../DebuggerStaticMappingProvider.java | 13 +- .../gui/objects/DebuggerObjectsProvider.java | 2 +- .../components/DebuggerAttachDialog.java | 6 +- .../ObjectEnumeratedColumnTableModel.java | 61 -- .../pcode/DebuggerPcodeStepperProvider.java | 41 +- .../core/debug/gui/pcode/UniqueRow.java | 83 +- .../gui/platform/DebuggerPlatformPlugin.java | 4 +- .../DebuggerSelectPlatformOfferDialog.java | 28 +- .../DebuggerAvailableRegistersDialog.java | 9 +- .../DebuggerRegisterColumnFactory.java} | 21 +- .../register/DebuggerRegistersProvider.java | 24 +- .../core/debug/gui/register/RegisterRow.java | 97 +- .../gui/stack/DebuggerStackProvider.java | 11 +- .../gui/thread/DebuggerThreadsProvider.java | 4 +- .../gui/time/DebuggerSnapshotTablePanel.java | 8 +- .../debug/gui/time/DebuggerTimeProvider.java | 3 +- .../gui/time/DebuggerTimeSelectionDialog.java | 2 +- .../gui/watch/DebuggerWatchesProvider.java | 7 +- .../plugin/core/debug/gui/watch/WatchRow.java | 37 +- ...ctReadsTargetPcodeExecutorStatePiece.java} | 66 +- ...r.java => BytesDebuggerPcodeEmulator.java} | 59 +- .../BytesDebuggerPcodeEmulatorFactory.java | 39 + .../DebuggerEmulationServicePlugin.java | 108 ++- .../DebuggerPcodeEmulatorFactory.java | 51 ++ .../emulation/DebuggerPcodeMachine.java | 51 ++ .../ReadsTargetMemoryPcodeExecutorState.java | 128 +-- ...dsTargetMemoryPcodeExecutorStatePiece.java | 173 ++++ ...eadsTargetRegistersPcodeExecutorState.java | 68 +- ...argetRegistersPcodeExecutorStatePiece.java | 108 +++ .../model/DebuggerModelServicePlugin.java | 5 +- .../DebuggerSelectMappingOfferDialog.java | 26 +- ...dRowWrappedEnumeratedColumnTableModel.java | 6 +- .../services/DebuggerEmulationService.java | 46 +- .../ghidra/pcode/exec/AsyncPcodeExecutor.java | 12 +- .../exec/AsyncWrappedPcodeArithmetic.java | 77 +- .../exec/AsyncWrappedPcodeExecutorState.java | 13 +- .../AsyncWrappedPcodeExecutorStatePiece.java | 46 +- ...codeUtils.java => DebuggerPcodeUtils.java} | 16 +- .../TraceRecorderAsyncPcodeExecutorState.java | 128 +-- ...eRecorderAsyncPcodeExecutorStatePiece.java | 154 ++++ .../AuxDebuggerEmulatorPartsFactory.java | 100 +++ .../auxiliary/AuxDebuggerPcodeEmulator.java | 91 ++ .../AbstractGhidraHeadedDebuggerGUITest.java | 7 +- .../DebuggerPcodeStepperProviderTest.java | 8 +- .../DebuggerStateEditingServiceTest.java | 10 +- .../exec/TraceRecorderAsyncPcodeExecTest.java | 1 + ...hedWriteBytesPcodeExecutorStatePiece.java} | 34 +- .../exec/trace/BytesTracePcodeEmulator.java | 65 ++ .../trace/BytesTracePcodeExecutorState.java | 36 + ...=> BytesTracePcodeExecutorStatePiece.java} | 72 +- .../trace/DefaultTracePcodeExecutorState.java | 47 + .../DirectBytesTracePcodeExecutorState.java | 68 ++ ...ectBytesTracePcodeExecutorStatePiece.java} | 124 ++- .../trace/PairedTracePcodeExecutorState.java | 55 ++ .../PairedTracePcodeExecutorStatePiece.java | 70 ++ ...aceCachedWriteBytesPcodeExecutorState.java | 33 +- ...chedWriteBytesPcodeExecutorStatePiece.java | 53 ++ ...aceCachedWriteBytesPcodeExecutorState.java | 48 +- ...chedWriteBytesPcodeExecutorStatePiece.java | 67 ++ .../TraceMemoryStatePcodeArithmetic.java | 52 +- ...aceMemoryStatePcodeExecutorStatePiece.java | 34 +- .../pcode/exec/trace/TracePcodeEmulator.java | 101 --- .../exec/trace/TracePcodeExecutorState.java | 36 + .../trace/TracePcodeExecutorStatePiece.java | 43 + .../pcode/exec/trace/TracePcodeMachine.java | 147 +++ .../pcode/exec/trace/TraceSleighUtils.java | 145 ++- .../AuxTraceEmulatorPartsFactory.java | 69 ++ .../auxiliary/AuxTracePcodeEmulator.java | 89 ++ .../ghidra/trace/database/DBTraceUtils.java | 286 +++++- .../data/DBTraceDataSettingsAdapter.java | 9 +- .../listing/DBTraceCodeUnitAdapter.java | 42 +- .../map/AbstractDBTracePropertyMap.java | 229 ++++- .../AbstractDBTraceProgramViewListing.java | 8 +- .../DBTraceProgramViewPropertyMapManager.java | 389 +++++++- .../DBTraceAddressPropertyManager.java | 40 +- .../DBTraceAddressPropertyManagerApiView.java | 22 +- .../property/TraceAddressPropertyManager.java | 43 +- .../model/property/TracePropertyGetter.java | 47 - .../model/property/TracePropertyMap.java | 86 +- ...r.java => TracePropertyMapOperations.java} | 60 +- .../TracePropertyMapRegisterSpace.java | 84 ++ .../model/property/TracePropertyMapSpace.java | 49 + .../trace/AbstractTracePcodeEmulatorTest.java | 92 ++ ....java => BytesTracePcodeEmulatorTest.java} | 165 ++-- .../exec/trace/TraceSleighUtilsTest.java | 2 +- .../trace/database/ToyDBTraceBuilder.java | 425 ++++++++- .../DBTraceAddressPropertyManagerTest.java | 56 +- .../model/time/schedule/TestMachine.java | 18 +- .../trace/model/time/schedule/TestThread.java | 20 +- ...aultEnumeratedColumnProgramTableModel.java | 8 +- .../DefaultEnumeratedColumnTableModel.java | 268 +++--- .../table/EnumeratedColumnTableModel.java | 22 +- .../RowWrappedEnumeratedColumnTableModel.java | 5 +- .../widgets/timeline/TimelinePanel.java | 847 ------------------ .../pcode/emu/AbstractPcodeMachine.java | 44 +- .../ghidra/pcode/emu/BytesPcodeThread.java | 17 +- .../ghidra/pcode/emu/DefaultPcodeThread.java | 75 +- ...deThread.java => ModifiedPcodeThread.java} | 69 +- .../java/ghidra/pcode/emu/PcodeEmulator.java | 43 +- .../java/ghidra/pcode/emu/PcodeThread.java | 47 +- .../pcode/emu/SleighInstructionDecoder.java | 4 +- .../pcode/emu/ThreadPcodeExecutorState.java | 48 +- .../auxiliary/AuxEmulatorPartsFactory.java | 143 +++ .../pcode/emu/auxiliary/AuxPcodeEmulator.java | 90 ++ .../pcode/emu/auxiliary/AuxPcodeThread.java | 57 ++ .../EmuLinuxAmd64SyscallUseropLibrary.java | 3 +- .../EmuLinuxX86SyscallUseropLibrary.java | 28 +- .../pcode/emu/sys/BytesEmuFileContents.java | 65 ++ .../ghidra/pcode/emu/sys/EmuFileContents.java | 64 ++ .../emu/sys/EmuProcessExitedException.java | 15 +- .../pcode/emu/sys/EmuSyscallLibrary.java | 5 +- .../pcode/emu/sys/PairedEmuFileContents.java | 62 ++ .../pcode/emu/unix/AbstractEmuUnixFile.java | 41 +- .../AbstractEmuUnixSyscallUseropLibrary.java | 152 +++- .../emu/unix/BytesEmuUnixFileSystem.java | 50 +- .../emu/unix/DefaultEmuUnixFileHandle.java | 16 +- ...AbstractBytesPcodeExecutorStatePiece.java} | 73 +- ...ractLongOffsetPcodeExecutorStatePiece.java | 125 ++- ...ctOffsetTransformedPcodeExecutorState.java | 70 -- .../pcode/exec/AddressOfPcodeArithmetic.java | 50 +- ... => AddressOfPcodeExecutorStatePiece.java} | 43 +- .../exec/AnnotatedPcodeUseropLibrary.java | 159 ++-- .../pcode/exec/BigIntegerPcodeArithmetic.java | 69 -- .../pcode/exec/BytesPcodeArithmetic.java | 82 +- .../pcode/exec/BytesPcodeExecutorState.java | 19 +- .../exec/BytesPcodeExecutorStatePiece.java | 45 + .../exec/BytesPcodeExecutorStateSpace.java | 2 +- ...xecutorState.java => ConcretionError.java} | 32 +- .../pcode/exec/DefaultPcodeExecutorState.java | 67 ++ .../pcode/exec/PairedPcodeArithmetic.java | 140 ++- .../pcode/exec/PairedPcodeExecutorState.java | 73 +- .../exec/PairedPcodeExecutorStatePiece.java | 66 +- .../ghidra/pcode/exec/PcodeArithmetic.java | 236 ++++- .../java/ghidra/pcode/exec/PcodeExecutor.java | 283 +++++- .../ghidra/pcode/exec/PcodeExecutorState.java | 18 +- .../pcode/exec/PcodeExecutorStatePiece.java | 94 +- .../ghidra/pcode/exec/PcodeUseropLibrary.java | 32 +- .../table/DemoRangeCellRendererTest.java | 23 +- .../widgets/timeline/TimelinePanelTest.java | 209 ----- ...EmuLinuxAmd64SyscallUseropLibraryTest.java | 138 ++- .../EmuLinuxX86SyscallUseropLibraryTest.java | 132 ++- .../sys/EmuAmd64SyscallUseropLibraryTest.java | 5 +- .../pcode/emu/sys/SyscallTestHelper.java | 108 +++ .../exec/AnnotatedPcodeUseropLibraryTest.java | 30 +- Ghidra/Debug/TaintAnalysis/Module.manifest | 0 Ghidra/Debug/TaintAnalysis/README.md | 7 + Ghidra/Debug/TaintAnalysis/build.gradle | 33 + .../TaintAnalysis/certification.manifest | 4 + .../AbstractTaintPcodeExecutorStatePiece.java | 118 +++ .../pcode/emu/taint/TaintPartsFactory.java | 236 +++++ .../pcode/emu/taint/TaintPcodeArithmetic.java | 238 +++++ .../emu/taint/TaintPcodeThreadExecutor.java | 71 ++ .../emu/taint/TaintPcodeUseropLibrary.java | 84 ++ .../full/TaintDebuggerPcodeEmulator.java | 58 ++ .../TaintDebuggerPcodeEmulatorFactory.java | 43 + .../full/TaintDebuggerPcodeExecutorState.java | 53 ++ .../TaintDebuggerPcodeExecutorStatePiece.java | 87 ++ .../emu/taint/full/TaintDebuggerSpace.java | 90 ++ .../emu/taint/lib/TaintEmuUnixFileSystem.java | 116 +++ ...aintFileReadsLinuxAmd64SyscallLibrary.java | 72 ++ .../emu/taint/plain/TaintPcodeEmulator.java | 60 ++ .../taint/plain/TaintPcodeExecutorState.java | 55 ++ .../plain/TaintPcodeExecutorStatePiece.java | 76 ++ .../pcode/emu/taint/plain/TaintSpace.java | 111 +++ .../pcode/emu/taint/plain/package-info.java | 33 + ...ractTaintTracePcodeExecutorStatePiece.java | 102 +++ .../taint/trace/TaintTracePcodeEmulator.java | 53 ++ .../trace/TaintTracePcodeExecutorState.java | 56 ++ .../TaintTracePcodeExecutorStatePiece.java | 74 ++ .../emu/taint/trace/TaintTraceSpace.java | 134 +++ .../pcode/emu/taint/trace/package-info.java | 35 + .../TaintDebuggerRegisterColumnFactory.java | 65 ++ .../taint/gui/field/TaintFieldFactory.java | 139 +++ .../taint/gui/field/TaintFieldLocation.java | 37 + .../ghidra/taint/gui/field/package-info.java} | 21 +- .../java/ghidra/taint/model/TaintMark.java | 151 ++++ .../java/ghidra/taint/model/TaintSet.java | 152 ++++ .../java/ghidra/taint/model/TaintVec.java | 503 +++++++++++ .../java/ghidra/taint/model/package-info.java | 36 + .../main/java/ghidra/taint/package-info.java | 64 ++ .../full/TaintDebuggerPcodeEmulatorTest.java | 143 +++ .../taint/plain/TaintPcodeEmulatorTest.java | 301 +++++++ .../trace/TaintTracePcodeEmulatorTest.java | 213 +++++ .../app/plugin/assembler/AssemblyBuffer.java | 109 +++ 205 files changed, 11214 insertions(+), 3714 deletions(-) delete mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerBlockChooserDialog.java rename Ghidra/Debug/Debugger/src/main/java/ghidra/{pcode/exec/TracePcodeExecutorState.java => app/plugin/core/debug/gui/register/DebuggerRegisterColumnFactory.java} (52%) rename Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/{AbstractReadsTargetPcodeExecutorState.java => AbstractReadsTargetPcodeExecutorStatePiece.java} (63%) rename Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/{DebuggerTracePcodeEmulator.java => BytesDebuggerPcodeEmulator.java} (56%) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeEmulatorFactory.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeMachine.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorStatePiece.java rename Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/{TracePcodeUtils.java => DebuggerPcodeUtils.java} (78%) create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java create mode 100644 Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java rename Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/{AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java => AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java} (57%) create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java rename Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/{TraceCachedWriteBytesPcodeExecutorState.java => BytesTracePcodeExecutorStatePiece.java} (73%) create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java rename Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/{TraceBytesPcodeExecutorState.java => DirectBytesTracePcodeExecutorStatePiece.java} (57%) create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java delete mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeEmulator.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java delete mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyGetter.java rename Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/{TracePropertySetter.java => TracePropertyMapOperations.java} (57%) create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapRegisterSpace.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapSpace.java create mode 100644 Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java rename Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/{TracePcodeEmulatorTest.java => BytesTracePcodeEmulatorTest.java} (83%) delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/timeline/TimelinePanel.java rename Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/{AbstractModifiedPcodeThread.java => ModifiedPcodeThread.java} (81%) create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeThread.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/BytesEmuFileContents.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuFileContents.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/PairedEmuFileContents.java rename Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/{AbstractBytesPcodeExecutorState.java => AbstractBytesPcodeExecutorStatePiece.java} (57%) delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractOffsetTransformedPcodeExecutorState.java rename Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/{AddressOfPcodeExecutorState.java => AddressOfPcodeExecutorStatePiece.java} (61%) delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BigIntegerPcodeArithmetic.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java rename Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/{AbstractLongOffsetPcodeExecutorState.java => ConcretionError.java} (50%) create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/timeline/TimelinePanelTest.java create mode 100644 Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/SyscallTestHelper.java create mode 100644 Ghidra/Debug/TaintAnalysis/Module.manifest create mode 100644 Ghidra/Debug/TaintAnalysis/README.md create mode 100644 Ghidra/Debug/TaintAnalysis/build.gradle create mode 100644 Ghidra/Debug/TaintAnalysis/certification.manifest create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeArithmetic.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeThreadExecutor.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeUseropLibrary.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorFactory.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorState.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerSpace.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintEmuUnixFileSystem.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintFileReadsLinuxAmd64SyscallLibrary.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulator.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorState.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/package-info.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/AbstractTaintTracePcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/package-info.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldLocation.java rename Ghidra/Debug/{ProposedUtils/src/main/java/docking/widgets/timeline/TimelineListener.java => TaintAnalysis/src/main/java/ghidra/taint/gui/field/package-info.java} (57%) create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintMark.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintSet.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintVec.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/package-info.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/package-info.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulatorTest.java create mode 100644 Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblyBuffer.java diff --git a/Ghidra/Debug/Debugger/build.gradle b/Ghidra/Debug/Debugger/build.gradle index dd05c091c9..2f3a865dc5 100644 --- a/Ghidra/Debug/Debugger/build.gradle +++ b/Ghidra/Debug/Debugger/build.gradle @@ -33,7 +33,7 @@ dependencies { helpPath project(path: ':Base', configuration: 'helpPath') helpPath project(path: ':ProgramDiff', configuration: 'helpPath') - testImplementation project(path: ':Base', configuration: 'testArtifacts') + testImplementation project(path: ':Base', configuration: 'testArtifacts') testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts') testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts') testImplementation project(path: ':Framework-TraceModeling', configuration: 'testArtifacts') diff --git a/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest b/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest index 8a0ed8a9a0..b5ed39bc86 100644 --- a/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest +++ b/Ghidra/Debug/Debugger/data/ExtensionPoint.manifest @@ -2,7 +2,9 @@ AutoReadMemorySpec DebuggerBot DebuggerMappingOpinion DebuggerModelFactory +DebuggerPcodeEmulatorFactory DebuggerPlatformOpinion DebuggerProgramLaunchOpinion +DebuggerRegisterColumnFactory DisassemblyInject LocationTrackingSpec diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java index e8b432f71e..98d136fc60 100644 --- a/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java +++ b/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java @@ -28,7 +28,7 @@ import java.util.List; import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; -import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator; +import ghidra.app.plugin.core.debug.service.emulation.BytesDebuggerPcodeEmulator; import ghidra.app.plugin.core.debug.service.emulation.ProgramEmulationUtils; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.script.GhidraScript; @@ -136,7 +136,7 @@ public class DebuggerEmuExampleScript extends GhidraScript { * library. This emulator will still know how to integrate with the UI, reading through to * open programs and writing state back into the trace. */ - DebuggerTracePcodeEmulator emulator = new DebuggerTracePcodeEmulator(tool, trace, 0, null) { + BytesDebuggerPcodeEmulator emulator = new BytesDebuggerPcodeEmulator(tool, trace, 0, null) { @Override protected PcodeUseropLibrary createUseropLibrary() { return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this); @@ -169,7 +169,7 @@ public class DebuggerEmuExampleScript extends GhidraScript { thread.stepInstruction(); snapshot = time.createSnapshot("Stepped to " + thread.getCounter()); - emulator.writeDown(trace, snapshot.getKey(), 0, false); + emulator.writeDown(trace, snapshot.getKey(), 0); } printerr("We should not have completed 10 steps!"); } diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java b/Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java index fb83ec0de4..68c1732d8d 100644 --- a/Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java +++ b/Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java @@ -29,16 +29,17 @@ import ghidra.program.model.pcode.Varnode; * A userop library for the emulator * *

- * If you do not have need of a custom userop library, use {@link PcodeUseropLibrary#NIL}. These - * libraries allow you to implement userop, including those declared by the language. Without these, - * the emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also - * define new userops, which can be invoked from Sleigh code injected into the emulator. + * If you do not need a custom userop library, use {@link PcodeUseropLibrary#NIL}. These libraries + * allow you to implement userops, including those declared by the language. Without these, the + * emulator must interrupt whenever a userop ({@code CALLOTHER}) is encountered. You can also define + * new userops, which can be invoked from Sleigh code injected into the emulator. * *

* These libraries can have both Java-callback and p-code implementations of userops. If only using * p-code implementations, the library can be parameterized with type {@code } and just pass that * over to {@link AnnotatedPcodeUseropLibrary}. Because this will demo a Java callback that assumes - * concrete bytes, we will fix the library's type to {@code byte[]}. + * concrete bytes, we will fix the library's type to {@code byte[]}. With careful use of the + * {@link PcodeArithmetic}, you can keep the type an abstract {@code } with Java callbacks. * *

* Methods in this class (not including those in its nested classes) are implemented as Java @@ -74,8 +75,7 @@ public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary * @return the length of the string in bytes */ @PcodeUserop - public byte[] print_utf8(@OpState PcodeExecutorStatePiece state, - byte[] start) { + public byte[] print_utf8(@OpState PcodeExecutorState state, byte[] start) { long offset = Utils.bytesToLong(start, start.length, language.isBigEndian()); long end = offset; while (state.getVar(space, end, 1, true)[0] != 0) { diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java b/Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java index 9d2ef94f41..732a863d21 100644 --- a/Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java +++ b/Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java @@ -23,6 +23,7 @@ import ghidra.pcode.emu.linux.EmuLinuxX86SyscallUseropLibrary; import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary; import ghidra.pcode.emu.sys.EmuSyscallLibrary; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.struct.StructuredSleigh; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.AddressSpace; @@ -41,7 +42,8 @@ import ghidra.program.model.listing.Program; * call libraries typically implement that interface by annotating p-code userops with * {@link EmuSyscall}. This allows system calls to be implemented via Java callback or Structured * Sleigh. Conventionally, the Java method names of system calls should be - * platform_name. This is to prevent name-space pollution of userops. + * platform_name. This is to prevent name conflicts among userops when several + * libraries are composed. * *

* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in @@ -53,7 +55,7 @@ import ghidra.program.model.listing.Program; * *

* For demonstration, this will implement one from scratch for no particular operating system, but - * it will borrow many conventions from linux-amd64. + * it will borrow many conventions from Linux-amd64. */ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary { private final static Charset UTF8 = Charset.forName("utf8"); @@ -80,11 +82,11 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary /** * Because the system call numbering is derived from the "syscall" overlay on OTHER space, a - * program is required. The system call analyzer must be applied to it. The program and its - * compiler spec are also used to derive (what it can of) the system call ABI. Notably, it - * applies the calling convention of the functions placed in syscall overlay. Those parts which - * cannot (yet) be derived from the program are instead implemented as abstract methods of this - * class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and + * program is required. Use the system call analyzer on your program to populate this space. The + * program and its compiler spec are also used to derive (what it can of) the system call ABI. + * Notably, it applies the calling convention of the functions placed in syscall overlay. Those + * parts which cannot (yet) be derived from the program are instead implemented as abstract + * methods of this class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and * {@link #handleError(PcodeExecutor, PcodeExecutionException)}. * * @param machine the emulator @@ -151,7 +153,7 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary *

* The {@link EmuSyscall} annotation allows us to specify the system call name, because the * userop name should be prefixed with the platform name, to avoid naming collisions among - * userops. + * composed libraries. * *

* For demonstration, we will export this as a system call, though that is not required for @@ -173,8 +175,8 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary * copy of the arithmetic as a field at library construction time. */ PcodeArithmetic arithmetic = machine.getArithmetic(); - long strLong = arithmetic.toConcrete(str).longValue(); - long endLong = arithmetic.toConcrete(end).longValue(); + long strLong = arithmetic.toLong(str, Purpose.LOAD); + long endLong = arithmetic.toLong(end, Purpose.OTHER); byte[] stringBytes = machine.getSharedState().getVar(space, strLong, (int) (endLong - strLong), true); @@ -185,12 +187,17 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary // Second, a Structured Sleigh example /** - * The nested class for syscall implemented using StructuredSleigh. Note that no matter the - * implementation type, the Java method is annotated with {@link EmuSyscall}. We declare it - * public so that the annotation processor can access the methods. Alternatively, we could - * override {@link #getMethodLookup()}. + * The nested class for syscalls implemented using Structured Sleigh. Note that no matter the + * implementation type, the Java method is annotated with {@link EmuSyscall}. We declare the + * class public so that the annotation processor can access the methods. Alternatively, we could + * override {@link #getMethodLookup()} to provide the processor private access. */ public class DemoStructuredPart extends StructuredPart { + /** + * This creates a handle to the "demo_write" p-code userop for use in Structured Sleigh. + * Otherwise, there's no way to refer to the userop. Think of it like a "forward" or + * "external" declaration. + */ UseropDecl write = userop(type("void"), "demo_write", types("char *", "char *")); /** diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneEmuExampleScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneEmuExampleScript.java index 1c56e080eb..cbcecf9a34 100644 --- a/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneEmuExampleScript.java +++ b/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneEmuExampleScript.java @@ -21,8 +21,6 @@ //@menupath //@toolbar -import java.io.ByteArrayOutputStream; -import java.io.IOException; import java.nio.charset.Charset; import java.util.List; @@ -88,7 +86,7 @@ public class StandAloneEmuExampleScript extends GhidraScript { */ Address entry = dyn.getAddress(0x00400000); Assembler asm = Assemblers.getAssembler(language); - CodeBuffer buffer = new CodeBuffer(asm, entry); + AssemblyBuffer buffer = new AssemblyBuffer(asm, entry); buffer.assemble("MOV RCX, 0xdeadbeef"); Address injectHere = buffer.getNext(); buffer.assemble("MOV RAX, 1"); @@ -150,30 +148,4 @@ public class StandAloneEmuExampleScript extends GhidraScript { .evaluate(thread.getExecutor()), 8, language.isBigEndian())); } - - public static class CodeBuffer { - private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - private final Assembler asm; - private final Address entry; - - public CodeBuffer(Assembler asm, Address entry) { - this.asm = asm; - this.entry = entry; - } - - public Address getNext() { - return entry.add(baos.size()); - } - - public byte[] assemble(String line) - throws AssemblySyntaxException, AssemblySemanticException, IOException { - byte[] bytes = asm.assembleLine(getNext(), line); - baos.write(bytes); - return bytes; - } - - public byte[] getBytes() { - return baos.toByteArray(); - } - } } diff --git a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html index e3f948553a..cd9f81ad9b 100644 --- a/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html +++ b/Ghidra/Debug/Debugger/src/main/help/help/topics/DebuggerEmulationServicePlugin/DebuggerEmulationServicePlugin.html @@ -40,5 +40,11 @@ current address and whose registers are initialized to the register context at the current address. Optionally, other registers can be initialized via the UI or a script. The new thread is activated so that stepping actions will affect it by default.

+ +

Configure Emulator

+ +

This action is always available. It lists emulators available for configuration. Selecting + one will set it as the current emulator. The next time emulation is activated, it will use the + selected emulator.

diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerMapProposalDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerMapProposalDialog.java index 81203e50d4..4eadc04aba 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerMapProposalDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/AbstractDebuggerMapProposalDialog.java @@ -28,18 +28,19 @@ import ghidra.util.table.GhidraTableFilterPanel; public abstract class AbstractDebuggerMapProposalDialog extends DialogComponentProvider { - protected final EnumeratedColumnTableModel tableModel = createTableModel(); + protected final EnumeratedColumnTableModel tableModel; protected GTable table; protected GhidraTableFilterPanel filterPanel; private Collection adjusted; - protected AbstractDebuggerMapProposalDialog(String title) { + protected AbstractDebuggerMapProposalDialog(PluginTool tool, String title) { super(title, true, true, true, false); + tableModel = createTableModel(tool); populateComponents(); } - protected abstract EnumeratedColumnTableModel createTableModel(); + protected abstract EnumeratedColumnTableModel createTableModel(PluginTool tool); protected void populateComponents() { JPanel panel = new JPanel(new BorderLayout()); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerBlockChooserDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerBlockChooserDialog.java index 3f6b8b5e3b..b1b5923df9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerBlockChooserDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerBlockChooserDialog.java @@ -143,16 +143,17 @@ public class DebuggerBlockChooserDialog extends DialogComponentProvider { } } - final EnumeratedColumnTableModel tableModel = - new DefaultEnumeratedColumnTableModel<>("Blocks", MemoryBlockTableColumns.class); + final EnumeratedColumnTableModel tableModel; GTable table; GhidraTableFilterPanel filterPanel; private Entry chosen; - public DebuggerBlockChooserDialog() { + public DebuggerBlockChooserDialog(PluginTool tool) { super("Memory Blocks", true, true, true, false); + tableModel = + new DefaultEnumeratedColumnTableModel<>(tool, "Blocks", MemoryBlockTableColumns.class); populateComponents(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java index 1897444151..6982e7dde9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/DebuggerResources.java @@ -572,6 +572,21 @@ public interface DebuggerResources { } } + interface ConfigureEmulatorAction { + String NAME = "Configure Emulator"; + String DESCRIPTION = "Choose and configure the current emulator"; + String GROUP = GROUP_MAINTENANCE; + String HELP_ANCHOR = "configure_emulator"; + + static ToggleActionBuilder builder(Plugin owner) { + String ownerName = owner.getName(); + return new ToggleActionBuilder(NAME, ownerName) + .description(DESCRIPTION) + .menuGroup(GROUP) + .helpLocation(new HelpLocation(ownerName, HELP_ANCHOR)); + } + } + abstract class AbstractQuickLaunchAction extends DockingAction { public static final String NAME = "Quick Launch"; public static final Icon ICON = ICON_DEBUGGER; // TODO: A different icon? diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java index f77fa44de7..e9687151a1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/action/DebuggerGoToTrait.java @@ -93,7 +93,7 @@ public abstract class DebuggerGoToTrait { } public CompletableFuture goToSleigh(AddressSpace space, PcodeExpression expression) { - AsyncPcodeExecutor executor = TracePcodeUtils.executorForCoordinates(current); + AsyncPcodeExecutor executor = DebuggerPcodeUtils.executorForCoordinates(current); CompletableFuture result = expression.evaluate(executor); return result.thenApply(offset -> { Address address = space.getAddress( diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java index a5f2bfc67f..e085a8f41d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/breakpoint/DebuggerBreakpointsProvider.java @@ -134,7 +134,7 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter LogicalBreakpointTableColumns, LogicalBreakpoint, LogicalBreakpointRow, LogicalBreakpoint> { public LogicalBreakpointTableModel(DebuggerBreakpointsProvider provider) { - super("Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb, + super(provider.getTool(), "Breakpoints", LogicalBreakpointTableColumns.class, lb -> lb, lb -> new LogicalBreakpointRow(provider, lb)); } @@ -212,8 +212,8 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter BreakpointLocationTableColumns, ObjectKey, BreakpointLocationRow, TraceBreakpoint> { public BreakpointLocationTableModel(DebuggerBreakpointsProvider provider) { - super("Locations", BreakpointLocationTableColumns.class, TraceBreakpoint::getObjectKey, - loc -> new BreakpointLocationRow(provider, loc)); + super(provider.getTool(), "Locations", BreakpointLocationTableColumns.class, + TraceBreakpoint::getObjectKey, loc -> new BreakpointLocationRow(provider, loc)); } @Override 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 5e57edb0a6..b3d68bac88 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 @@ -45,8 +45,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.SelectNoneAction; import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel; import ghidra.framework.options.AutoOptions; import ghidra.framework.options.annotation.*; -import ghidra.framework.plugintool.AutoService; -import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.*; import ghidra.util.*; import ghidra.util.table.GhidraTable; import ghidra.util.table.GhidraTableFilterPanel; @@ -205,8 +204,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter protected static class LogTableModel extends DebouncedRowWrappedEnumeratedColumnTableModel< // LogTableColumns, ActionContext, LogRow, LogRow> { - public LogTableModel() { - super("Log", LogTableColumns.class, r -> r.getActionContext(), r -> r); + public LogTableModel(PluginTool tool) { + super(tool, "Log", LogTableColumns.class, r -> r.getActionContext(), r -> r); } @Override @@ -286,7 +285,7 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter protected final Map> actionsByOwnerThenName = new LinkedHashMap<>(); - protected final LogTableModel logTableModel = new LogTableModel(); + protected final LogTableModel logTableModel; protected GhidraTable logTable; private GhidraTableFilterPanel logFilterPanel; @@ -301,6 +300,8 @@ public class DebuggerConsoleProvider extends ComponentProviderAdapter super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_CONSOLE, plugin.getName()); this.plugin = plugin; + logTableModel = new LogTableModel(tool); + tool.addPopupActionProvider(this); setIcon(DebuggerResources.ICON_PROVIDER_CONSOLE); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java index 97053afc18..13b408c549 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyActionsPlugin.java @@ -55,7 +55,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin { return ctx.hasSelection() ? ctx.getSelection() : null; } - protected DebuggerCopyIntoProgramDialog copyDialog = new DebuggerCopyIntoProgramDialog(); + protected DebuggerCopyIntoProgramDialog copyDialog; protected DockingAction actionExportView; protected DockingAction actionCopyIntoCurrentProgram; @@ -70,6 +70,7 @@ public class DebuggerCopyActionsPlugin extends AbstractDebuggerPlugin { public DebuggerCopyActionsPlugin(PluginTool tool) { super(tool); + copyDialog = new DebuggerCopyIntoProgramDialog(tool); createActions(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java index 032d83bf09..ea870a230d 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/copying/DebuggerCopyIntoProgramDialog.java @@ -37,6 +37,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.copying.DebuggerCopyPlan.Copier; import ghidra.app.services.*; import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; @@ -200,8 +201,8 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider { protected static class RangeTableModel extends DefaultEnumeratedColumnTableModel { - public RangeTableModel() { - super("Ranges", RangeTableColumns.class); + public RangeTableModel(PluginTool tool) { + super(tool, "Ranges", RangeTableColumns.class); } @Override @@ -302,15 +303,16 @@ public class DebuggerCopyIntoProgramDialog extends DialogComponentProvider { protected JCheckBox cbUseOverlays; protected DebuggerCopyPlan plan = new DebuggerCopyPlan(); - protected final RangeTableModel tableModel = new RangeTableModel(); + protected final RangeTableModel tableModel; protected GTable table; protected GhidraTableFilterPanel filterPanel; protected JButton resetButton; - public DebuggerCopyIntoProgramDialog() { + public DebuggerCopyIntoProgramDialog(PluginTool tool) { super("Copy Into Program", true, true, true, true); + tableModel = new RangeTableModel(tool); populateComponents(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionMapProposalDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionMapProposalDialog.java index 7f6f3891fb..a5cc8766ae 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionMapProposalDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionMapProposalDialog.java @@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapRegionsAction; import ghidra.app.services.RegionMapProposal.RegionMapEntry; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; @@ -101,8 +102,8 @@ public class DebuggerRegionMapProposalDialog protected static class RegionMapPropsalTableModel extends DefaultEnumeratedColumnTableModel { - public RegionMapPropsalTableModel() { - super("Region Map", RegionMapTableColumns.class); + public RegionMapPropsalTableModel(PluginTool tool) { + super(tool, "Region Map", RegionMapTableColumns.class); } @Override @@ -114,13 +115,13 @@ public class DebuggerRegionMapProposalDialog private final DebuggerRegionsProvider provider; public DebuggerRegionMapProposalDialog(DebuggerRegionsProvider provider) { - super(MapRegionsAction.NAME); + super(provider.getTool(), MapRegionsAction.NAME); this.provider = provider; } @Override - protected RegionMapPropsalTableModel createTableModel() { - return new RegionMapPropsalTableModel(); + protected RegionMapPropsalTableModel createTableModel(PluginTool tool) { + return new RegionMapPropsalTableModel(tool); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java index c03fe58b3e..f8a318a683 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/memory/DebuggerRegionsProvider.java @@ -44,8 +44,7 @@ import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTab import ghidra.app.services.*; import ghidra.app.services.RegionMapProposal.RegionMapEntry; import ghidra.framework.model.DomainObject; -import ghidra.framework.plugintool.AutoService; -import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; @@ -124,8 +123,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { extends DebouncedRowWrappedEnumeratedColumnTableModel< // RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> { - public RegionTableModel() { - super("Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey, + public RegionTableModel(PluginTool tool) { + super(tool, "Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey, RegionRow::new); } } @@ -233,7 +232,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { private final RegionsListener regionsListener = new RegionsListener(); - protected final RegionTableModel regionTableModel = new RegionTableModel(); + protected final RegionTableModel regionTableModel; protected GhidraTable regionTable; private GhidraTableFilterPanel regionFilterPanel; @@ -260,6 +259,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { DebuggerRegionActionContext.class); this.plugin = plugin; + regionTableModel = new RegionTableModel(tool); + setIcon(DebuggerResources.ICON_PROVIDER_REGIONS); setHelpLocation(DebuggerResources.HELP_PROVIDER_REGIONS); setWindowMenuGroup(DebuggerPluginPackage.NAME); @@ -268,7 +269,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter { this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); - blockChooserDialog = new DebuggerBlockChooserDialog(); + blockChooserDialog = new DebuggerBlockChooserDialog(tool); regionProposalDialog = new DebuggerRegionMapProposalDialog(this); setDefaultWindowPosition(WindowPosition.BOTTOM); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerBlockChooserDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerBlockChooserDialog.java deleted file mode 100644 index 20ad3f191c..0000000000 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerBlockChooserDialog.java +++ /dev/null @@ -1,243 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.app.plugin.core.debug.gui.modules; - -import java.awt.BorderLayout; -import java.util.*; -import java.util.Map.Entry; -import java.util.function.Function; - -import javax.swing.*; -import javax.swing.table.TableColumn; -import javax.swing.table.TableColumnModel; - -import docking.DialogComponentProvider; -import docking.widgets.table.*; -import docking.widgets.table.ColumnSortState.SortDirection; -import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; -import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.framework.model.DomainFile; -import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.Address; -import ghidra.program.model.listing.Program; -import ghidra.program.model.mem.MemoryBlock; -import ghidra.program.util.ProgramLocation; -import ghidra.trace.model.modules.TraceSection; -import ghidra.util.table.GhidraTableFilterPanel; - -public class DebuggerBlockChooserDialog extends DialogComponentProvider { - static class MemoryBlockRow { - private final Program program; - private final MemoryBlock block; - private double score; - - public MemoryBlockRow(Program program, MemoryBlock block) { - this.program = program; - this.block = block; - } - - public Program getProgram() { - return program; - } - - public MemoryBlock getBlock() { - return block; - } - - public String getProgramName() { - DomainFile df = program.getDomainFile(); - if (df != null) { - return df.getName(); - } - return program.getName(); - } - - public String getBlockName() { - return block.getName(); - } - - public Address getMinAddress() { - return block.getStart(); - } - - public Address getMaxAddress() { - return block.getEnd(); - } - - public long getLength() { - return block.getSize(); - } - - public double getScore() { - return score; - } - - public double score(TraceSection section, DebuggerStaticMappingService service) { - if (section == null) { - return score = 0; - } - return score = service.proposeSectionMap(section, program, block).computeScore(); - } - - public ProgramLocation getProgramLocation() { - return new ProgramLocation(program, block.getStart()); - } - } - - enum MemoryBlockTableColumns - implements EnumeratedTableColumn { - SCORE("Score", Double.class, MemoryBlockRow::getScore, SortDirection.DESCENDING), - PROGRAM("Program", String.class, MemoryBlockRow::getProgramName, SortDirection.ASCENDING), - BLOCK("Block", String.class, MemoryBlockRow::getBlockName, SortDirection.ASCENDING), - START("Start Address", Address.class, MemoryBlockRow::getMinAddress, SortDirection.ASCENDING), - END("End Address", Address.class, MemoryBlockRow::getMaxAddress, SortDirection.ASCENDING), - LENGTH("Length", Long.class, MemoryBlockRow::getLength, SortDirection.ASCENDING); - - MemoryBlockTableColumns(String header, Class cls, Function getter, - SortDirection dir) { - this.header = header; - this.cls = cls; - this.getter = getter; - this.dir = dir; - } - - private final String header; - private final Function getter; - private final Class cls; - private final SortDirection dir; - - @Override - public String getHeader() { - return header; - } - - @Override - public Class getValueClass() { - return cls; - } - - @Override - public Object getValueOf(MemoryBlockRow row) { - return getter.apply(row); - } - - @Override - public SortDirection defaultSortDirection() { - return dir; - } - } - - final EnumeratedColumnTableModel tableModel = - new DefaultEnumeratedColumnTableModel<>("Blocks", MemoryBlockTableColumns.class); - - GTable table; - GhidraTableFilterPanel filterPanel; - - private Entry chosen; - - protected DebuggerBlockChooserDialog() { - super("Memory Blocks", true, true, true, false); - populateComponents(); - } - - protected void populateComponents() { - JPanel panel = new JPanel(new BorderLayout()); - - table = new GTable(tableModel); - table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - panel.add(new JScrollPane(table)); - - filterPanel = new GhidraTableFilterPanel<>(table, tableModel); - panel.add(filterPanel, BorderLayout.SOUTH); - - addWorkPanel(panel); - - addOKButton(); - addCancelButton(); - - table.getSelectionModel().addListSelectionListener(evt -> { - okButton.setEnabled(filterPanel.getSelectedItems().size() == 1); - // Prevent empty selection - }); - - // TODO: Adjust column widths? - TableColumnModel columnModel = table.getColumnModel(); - - TableColumn startCol = columnModel.getColumn(MemoryBlockTableColumns.START.ordinal()); - startCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); - - TableColumn endCol = columnModel.getColumn(MemoryBlockTableColumns.END.ordinal()); - endCol.setCellRenderer(CustomToStringCellRenderer.MONO_OBJECT); - - TableColumn lenCol = columnModel.getColumn(MemoryBlockTableColumns.LENGTH.ordinal()); - lenCol.setCellRenderer(CustomToStringCellRenderer.MONO_ULONG_HEX); - } - - public Map.Entry chooseBlock(PluginTool tool, TraceSection section, - Collection programs) { - setBlocksFromPrograms(programs); - computeScores(section, tool.getService(DebuggerStaticMappingService.class)); - selectHighestScoringBlock(); - tool.showDialog(this); - return getChosen(); - } - - protected void computeScores(TraceSection section, DebuggerStaticMappingService service) { - for (MemoryBlockRow rec : tableModel.getModelData()) { - rec.score(section, service); - } - } - - protected void setBlocksFromPrograms(Collection programs) { - this.tableModel.clear(); - List rows = new ArrayList<>(); - for (Program program : programs) { - for (MemoryBlock block : program.getMemory().getBlocks()) { - rows.add(new MemoryBlockRow(program, block)); - } - } - this.tableModel.addAll(rows); - } - - protected void selectHighestScoringBlock() { - MemoryBlockRow best = null; - for (MemoryBlockRow rec : tableModel.getModelData()) { - if (best == null || rec.getScore() > best.getScore()) { - best = rec; - } - } - if (best != null) { - filterPanel.setSelectedItem(best); - } - } - - @Override - protected void okCallback() { - MemoryBlockRow sel = filterPanel.getSelectedItem(); - this.chosen = sel == null ? null : Map.entry(sel.program, sel.block); - close(); - } - - @Override - protected void cancelCallback() { - this.chosen = null; - close(); - } - - public Entry getChosen() { - return chosen; - } -} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java index 1d839b4f54..5cfd63df60 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerModuleMapProposalDialog.java @@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapModulesAction; import ghidra.app.services.ModuleMapProposal.ModuleMapEntry; import ghidra.framework.model.DomainFile; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.util.Swing; @@ -100,8 +101,8 @@ public class DebuggerModuleMapProposalDialog protected static class ModuleMapPropsalTableModel extends DefaultEnumeratedColumnTableModel { - public ModuleMapPropsalTableModel() { - super("Module Map", ModuleMapTableColumns.class); + public ModuleMapPropsalTableModel(PluginTool tool) { + super(tool, "Module Map", ModuleMapTableColumns.class); } @Override @@ -113,13 +114,13 @@ public class DebuggerModuleMapProposalDialog private final DebuggerModulesProvider provider; protected DebuggerModuleMapProposalDialog(DebuggerModulesProvider provider) { - super(MapModulesAction.NAME); + super(provider.getTool(), MapModulesAction.NAME); this.provider = provider; } @Override - protected ModuleMapPropsalTableModel createTableModel() { - return new ModuleMapPropsalTableModel(); + protected ModuleMapPropsalTableModel createTableModel(PluginTool tool) { + return new ModuleMapPropsalTableModel(tool); } @Override 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 4107480c19..38549b0791 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 @@ -53,8 +53,7 @@ import ghidra.async.TypeSpec; import ghidra.framework.main.AppInfo; import ghidra.framework.main.DataTreeDialog; import ghidra.framework.model.*; -import ghidra.framework.plugintool.AutoService; -import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; @@ -207,8 +206,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { extends DebouncedRowWrappedEnumeratedColumnTableModel< // ModuleTableColumns, ObjectKey, ModuleRow, TraceModule> { - public ModuleTableModel() { - super("Modules", ModuleTableColumns.class, TraceModule::getObjectKey, ModuleRow::new); + public ModuleTableModel(PluginTool tool) { + super(tool, "Modules", ModuleTableColumns.class, TraceModule::getObjectKey, + ModuleRow::new); } @Override @@ -221,8 +221,8 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { extends DebouncedRowWrappedEnumeratedColumnTableModel< // SectionTableColumns, ObjectKey, SectionRow, TraceSection> { - public SectionTableModel() { - super("Sections", SectionTableColumns.class, TraceSection::getObjectKey, + public SectionTableModel(PluginTool tool) { + super(tool, "Sections", SectionTableColumns.class, TraceSection::getObjectKey, SectionRow::new); } @@ -555,11 +555,11 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { private final RecordersChangedListener recordersChangedListener = new RecordersChangedListener(); - protected final ModuleTableModel moduleTableModel = new ModuleTableModel(); + protected final ModuleTableModel moduleTableModel; protected GhidraTable moduleTable; private GhidraTableFilterPanel moduleFilterPanel; - protected final SectionTableModel sectionTableModel = new SectionTableModel(); + protected final SectionTableModel sectionTableModel; protected GhidraTable sectionTable; protected GhidraTableFilterPanel sectionFilterPanel; private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules = @@ -599,6 +599,9 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MODULES, plugin.getName(), null); this.plugin = plugin; + moduleTableModel = new ModuleTableModel(tool); + sectionTableModel = new SectionTableModel(tool); + setIcon(DebuggerResources.ICON_PROVIDER_MODULES); setHelpLocation(DebuggerResources.HELP_PROVIDER_MODULES); setWindowMenuGroup(DebuggerPluginPackage.NAME); @@ -607,7 +610,7 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter { this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); - blockChooserDialog = new DebuggerBlockChooserDialog(); + blockChooserDialog = new DebuggerBlockChooserDialog(tool); moduleProposalDialog = new DebuggerModuleMapProposalDialog(this); sectionProposalDialog = new DebuggerSectionMapProposalDialog(this); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionMapProposalDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionMapProposalDialog.java index b01faaeb40..d890f177a0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionMapProposalDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerSectionMapProposalDialog.java @@ -29,6 +29,7 @@ import ghidra.app.plugin.core.debug.gui.AbstractDebuggerMapProposalDialog; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.DebuggerResources.MapSectionsAction; import ghidra.app.services.SectionMapProposal.SectionMapEntry; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; @@ -102,8 +103,8 @@ public class DebuggerSectionMapProposalDialog protected static class SectionMapPropsalTableModel extends DefaultEnumeratedColumnTableModel { - public SectionMapPropsalTableModel() { - super("Section Map", SectionMapTableColumns.class); + public SectionMapPropsalTableModel(PluginTool tool) { + super(tool, "Section Map", SectionMapTableColumns.class); } @Override @@ -115,13 +116,13 @@ public class DebuggerSectionMapProposalDialog private final DebuggerModulesProvider provider; public DebuggerSectionMapProposalDialog(DebuggerModulesProvider provider) { - super(MapSectionsAction.NAME); + super(provider.getTool(), MapSectionsAction.NAME); this.provider = provider; } @Override - protected SectionMapPropsalTableModel createTableModel() { - return new SectionMapPropsalTableModel(); + protected SectionMapPropsalTableModel createTableModel(PluginTool tool) { + return new SectionMapPropsalTableModel(tool); } @Override diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java index ce3ab2cc88..74b5a05ca8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/modules/DebuggerStaticMappingProvider.java @@ -42,8 +42,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.plugin.core.debug.utils.DebouncedRowWrappedEnumeratedColumnTableModel; import ghidra.app.services.*; import ghidra.framework.model.DomainObject; -import ghidra.framework.plugintool.AutoService; -import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; @@ -103,9 +102,9 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter extends DebouncedRowWrappedEnumeratedColumnTableModel< // StaticMappingTableColumns, ObjectKey, StaticMappingRow, TraceStaticMapping> { - public MappingTableModel() { - super("Mappings", StaticMappingTableColumns.class, TraceStaticMapping::getObjectKey, - StaticMappingRow::new); + public MappingTableModel(PluginTool tool) { + super(tool, "Mappings", StaticMappingTableColumns.class, + TraceStaticMapping::getObjectKey, StaticMappingRow::new); } } @@ -149,7 +148,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter private ListenerForStaticMappingDisplay listener = new ListenerForStaticMappingDisplay(); - protected final MappingTableModel mappingTableModel = new MappingTableModel(); + protected final MappingTableModel mappingTableModel; private JPanel mainPanel = new JPanel(new BorderLayout()); protected GTable mappingTable; @@ -165,6 +164,8 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_MAPPINGS, plugin.getName(), null); this.plugin = plugin; + mappingTableModel = new MappingTableModel(tool); + this.addMappingDialog = new DebuggerAddMappingDialog(); this.autoWiring = AutoService.wireServicesConsumed(plugin, this); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java index 30731308cc..3d879552da 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/DebuggerObjectsProvider.java @@ -647,7 +647,7 @@ public class DebuggerObjectsProvider extends ComponentProviderAdapter TargetObject targetObject = container.getTargetObject(); String name = targetObject.getName(); DefaultEnumeratedColumnTableModel model = - new DefaultEnumeratedColumnTableModel<>(name, ObjectAttributeColumn.class); + new DefaultEnumeratedColumnTableModel<>(tool, name, ObjectAttributeColumn.class); Map map = container.getAttributeMap(); List list = new ArrayList<>(); for (Object val : map.values()) { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerAttachDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerAttachDialog.java index 2f97e46eda..13642638c5 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerAttachDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/DebuggerAttachDialog.java @@ -64,9 +64,7 @@ public class DebuggerAttachDialog extends DialogComponentProvider { protected RefreshAction actionRefresh; protected JButton attachButton; - private final RowObjectTableModel processes = - new DefaultEnumeratedColumnTableModel<>("Attachables", - AttachableProcessesTableColumns.class); + private final RowObjectTableModel processes; protected TargetAttacher attacher; private GTable processTable; @@ -74,6 +72,8 @@ public class DebuggerAttachDialog extends DialogComponentProvider { super(AbstractAttachAction.NAME, true, true, true, false); this.provider = provider; this.plugin = provider.getPlugin(); + processes = new DefaultEnumeratedColumnTableModel<>(plugin.getTool(), "Attachables", + AttachableProcessesTableColumns.class); populateComponents(); createActions(); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectEnumeratedColumnTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectEnumeratedColumnTableModel.java index 9362337185..4868e1bc3b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectEnumeratedColumnTableModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/objects/components/ObjectEnumeratedColumnTableModel.java @@ -52,67 +52,6 @@ public class ObjectEnumeratedColumnTableModel { - protected final ListIterator it = modelData.listIterator(); - protected int index; - - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public R next() { - index = it.nextIndex(); - return it.next(); - } - - @Override - public boolean hasPrevious() { - return it.hasPrevious(); - } - - @Override - public R previous() { - index = it.previousIndex(); - return it.previous(); - } - - @Override - public int nextIndex() { - return it.nextIndex(); - } - - @Override - public int previousIndex() { - return it.previousIndex(); - } - - @Override - public void remove() { - it.remove(); - fireTableRowsDeleted(index, index); - } - - @Override - public void set(R e) { - it.set(e); - fireTableRowsUpdated(index, index); - } - - @Override - public void notifyUpdated() { - fireTableRowsUpdated(index, index); - } - - @Override - public void add(R e) { - it.add(e); - int nextIndex = it.nextIndex(); - fireTableRowsInserted(nextIndex, nextIndex); - } - } - private final List modelData = new ArrayList<>(); private final String name; private C[] cols; diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java index ac863b1348..5aeb57b458 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProvider.java @@ -38,7 +38,7 @@ import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.gui.pcode.UniqueRow.RefType; -import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine; import ghidra.app.plugin.processors.sleigh.template.OpTpl; import ghidra.app.services.DebuggerEmulationService; import ghidra.app.services.DebuggerTraceManagerService; @@ -49,12 +49,10 @@ import ghidra.base.widgets.table.DataTypeTableCellEditor; import ghidra.docking.settings.Settings; import ghidra.framework.options.AutoOptions; import ghidra.framework.options.annotation.*; -import ghidra.framework.plugintool.AutoService; -import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.pcode.exec.PcodeFrame; +import ghidra.pcode.exec.*; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.DataType; import ghidra.program.model.lang.Language; @@ -143,8 +141,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { protected static class PcodeTableModel extends DefaultEnumeratedColumnTableModel { - public PcodeTableModel() { - super("p-code", PcodeTableColumns.class); + public PcodeTableModel(PluginTool tool) { + super(tool, "p-code", PcodeTableColumns.class); } @Override @@ -208,8 +206,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { protected static class UniqueTableModel extends DefaultEnumeratedColumnTableModel { - public UniqueTableModel() { - super("Unique", UniqueTableColumns.class); + public UniqueTableModel(PluginTool tool) { + super(tool, "Unique", UniqueTableColumns.class); } @Override @@ -575,12 +573,12 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT); + final UniqueTableModel uniqueTableModel; GhidraTable uniqueTable; - UniqueTableModel uniqueTableModel = new UniqueTableModel(); GhidraTableFilterPanel uniqueFilterPanel; + final PcodeTableModel pcodeTableModel; GhidraTable pcodeTable; - PcodeTableModel pcodeTableModel = new PcodeTableModel(); JLabel instructionLabel; // No filter panel on p-code PcodeCellRenderer codeColRenderer; @@ -592,6 +590,9 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_PCODE, plugin.getName(), null); this.plugin = plugin; + uniqueTableModel = new UniqueTableModel(tool); + pcodeTableModel = new PcodeTableModel(tool); + this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); this.autoOptionsWiring = AutoOptions.wireOptions(plugin, this); @@ -877,9 +878,10 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { pcodeTableModel.add(row); } - protected void populateFromFrame(PcodeFrame frame, PcodeExecutorState state) { + protected void populateFromFrame(PcodeFrame frame, PcodeExecutorState state, + PcodeArithmetic arithmetic) { populatePcode(frame); - populateUnique(frame, state); + populateUnique(frame, state, arithmetic); } protected int computeCodeColWidth(List rows) { @@ -916,7 +918,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { pcodeTable.scrollToSelectedRow(); } - protected void populateUnique(PcodeFrame frame, PcodeExecutorState state) { + protected void populateUnique(PcodeFrame frame, PcodeExecutorState state, + PcodeArithmetic arithmetic) { Language language = current.getTrace().getBaseLanguage(); // NOTE: They may overlap. I don't think I care. Set uniques = new TreeSet<>(UNIQUE_COMPARATOR); @@ -936,7 +939,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { // TODO: Permit modification of unique variables List toAdd = uniques.stream() - .map(u -> new UniqueRow(this, language, state, u)) + .map(u -> new UniqueRow(this, language, state, arithmetic, u)) .collect(Collectors.toList()); uniqueTableModel.addAll(toAdd); } @@ -971,7 +974,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { populateSingleton(EnumPcodeRow.DECODE); return; } - DebuggerTracePcodeEmulator emu = emulationService.getCachedEmulator(trace, time); + DebuggerPcodeMachine emu = emulationService.getCachedEmulator(trace, time); if (emu != null) { clear(); doLoadPcodeFrameFromEmulator(emu); @@ -986,8 +989,8 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { }, SwingExecutorService.LATER); } - protected void doLoadPcodeFrameFromEmulator(DebuggerTracePcodeEmulator emu) { - PcodeThread thread = emu.getThread(current.getThread().getPath(), false); + protected void doLoadPcodeFrameFromEmulator(DebuggerPcodeMachine emu) { + PcodeThread thread = emu.getThread(current.getThread().getPath(), false); if (thread == null) { /** * Happens when focus is on a thread not stepped in the schedule. Stepping it would @@ -1012,7 +1015,7 @@ public class DebuggerPcodeStepperProvider extends ComponentProviderAdapter { populateSingleton(EnumPcodeRow.DECODE); return; } - populateFromFrame(frame, thread.getState()); + populateFromFrame(frame, thread.getState(), thread.getArithmetic()); } @AutoServiceConsumed diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/UniqueRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/UniqueRow.java index df8a9ca1b1..4537a3686b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/UniqueRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/pcode/UniqueRow.java @@ -19,8 +19,9 @@ import java.math.BigInteger; import java.util.stream.Stream; import ghidra.docking.settings.SettingsImpl; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.pcode.utils.Utils; import ghidra.program.model.address.*; import ghidra.program.model.data.DataType; import ghidra.program.model.lang.Language; @@ -39,36 +40,54 @@ public class UniqueRow { if (isWrite) { return READ_WRITE; } - else { - return READ; - } + return READ; } - else { - if (isWrite) { - return WRITE; - } - else { - return NONE; - } + if (isWrite) { + return WRITE; } + return NONE; + } + } + + /** + * Putting these related methods, all using a common type, into a nested class allows us to + * introduce {@code }, essentially a "universal type." + * + * @param the type of state from which concrete parts are extracted. + */ + public static class ConcretizedState { + private final PcodeExecutorState state; + private final PcodeArithmetic arithmetic; + + public ConcretizedState(PcodeExecutorState state, PcodeArithmetic arithmetic) { + this.state = state; + this.arithmetic = arithmetic; + } + + public byte[] getBytes(Varnode vn) { + return arithmetic.toConcrete(state.getVar(vn), Purpose.INSPECT); + } + + public BigInteger getValue(Varnode vn) { + return arithmetic.toBigInteger(state.getVar(vn), Purpose.INSPECT); } } protected final DebuggerPcodeStepperProvider provider; protected final Language language; - protected final PcodeExecutorState state; + protected final ConcretizedState state; protected final Varnode vn; protected DataType dataType; - public UniqueRow(DebuggerPcodeStepperProvider provider, Language language, - PcodeExecutorState state, Varnode vn) { + public UniqueRow(DebuggerPcodeStepperProvider provider, Language language, + PcodeExecutorState state, PcodeArithmetic arithmetic, Varnode vn) { if (!vn.isUnique()) { throw new AssertionError("Only uniques allowed in unique table"); } this.provider = provider; this.language = language; - this.state = state; + this.state = new ConcretizedState<>(state, arithmetic); this.vn = vn; } @@ -105,9 +124,26 @@ public class UniqueRow { return String.format("$U%x:%d", vn.getOffset(), vn.getSize()); } + // TODO: Pluggable columns to display abstract pieces + + /** + * Renders the raw bytes as space-separated hexadecimal-digit pairs, if concrete + * + *

+ * If the state's concrete piece cannot be extracted by the machine's arithmetic, this simply + * returns {@code "(not concrete)"}. + * + * @return the byte string + */ public String getBytes() { // TODO: Could keep value cached? - byte[] bytes = state.getVar(vn); + byte[] bytes; + try { + bytes = state.getBytes(vn); + } + catch (UnsupportedOperationException e) { + return "(not concrete)"; + } if (bytes == null) { return "??"; } @@ -117,9 +153,18 @@ public class UniqueRow { return NumericUtilities.convertBytesToString(bytes, " "); } + /** + * Extract the concrete part of the variable as an unsigned big integer + * + * @return the value, or null if the value cannot be made concrete + */ public BigInteger getValue() { - byte[] bytes = state.getVar(vn); - return Utils.bytesToBigInteger(bytes, bytes.length, language.isBigEndian(), false); + try { + return state.getValue(vn); + } + catch (UnsupportedOperationException e) { + return null; + } } public DataType getDataType() { @@ -135,7 +180,7 @@ public class UniqueRow { if (dataType == null) { return ""; } - byte[] bytes = state.getVar(vn); + byte[] bytes = state.getBytes(vn); if (bytes == null) { return "??"; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPlugin.java index 4ab731a435..874c0f17f8 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerPlatformPlugin.java @@ -203,8 +203,7 @@ public class DebuggerPlatformPlugin extends Plugin { private final ChangeListener classChangeListener = evt -> this.classesChanged(); - protected final DebuggerSelectPlatformOfferDialog offerDialog = - new DebuggerSelectPlatformOfferDialog(); + protected final DebuggerSelectPlatformOfferDialog offerDialog; final Map actionsChoosePlatform = new WeakHashMap<>(); DockingAction actionMore; @@ -212,6 +211,7 @@ public class DebuggerPlatformPlugin extends Plugin { public DebuggerPlatformPlugin(PluginTool tool) { super(tool); autoServiceWiring = AutoService.wireServicesProvidedAndConsumed(this); + offerDialog = new DebuggerSelectPlatformOfferDialog(tool); ClassSearcher.addChangeListener(classChangeListener); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java index 129c5d3528..f305b25a9a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/platform/DebuggerSelectPlatformOfferDialog.java @@ -29,6 +29,7 @@ import docking.widgets.table.ColumnSortState.SortDirection; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.mapping.DebuggerPlatformOffer; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.lang.*; import ghidra.program.util.DefaultLanguageService; import ghidra.util.table.GhidraTable; @@ -134,8 +135,8 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider { public static class OfferTableModel extends DefaultEnumeratedColumnTableModel { - public OfferTableModel() { - super("Offers", OfferTableColumns.class); + public OfferTableModel(PluginTool tool) { + super(tool, "Offers", OfferTableColumns.class); } @Override @@ -146,14 +147,13 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider { } public static class OfferPanel extends JPanel { - private final OfferTableModel offerTableModel = new OfferTableModel(); - private final GhidraTable offerTable = new GhidraTable(offerTableModel); - private final GhidraTableFilterPanel offerTableFilterPanel = - new GhidraTableFilterPanel<>(offerTable, offerTableModel); + private final OfferTableModel offerTableModel; + private final GhidraTable offerTable; + private final GhidraTableFilterPanel offerTableFilterPanel; private final JLabel descLabel = new JLabel(); private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers"); - private final JScrollPane scrollPane = new JScrollPane(offerTable) { + private final JScrollPane scrollPane = new JScrollPane() { @Override public Dimension getPreferredSize() { Dimension pref = super.getPreferredSize(); @@ -178,7 +178,12 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider { private LanguageID preferredLangID; private CompilerSpecID preferredCsID; - { + protected OfferPanel(PluginTool tool) { + offerTableModel = new OfferTableModel(tool); + offerTable = new GhidraTable(offerTableModel); + offerTableFilterPanel = new GhidraTableFilterPanel<>(offerTable, offerTableModel); + scrollPane.setViewportView(offerTable); + JPanel descPanel = new JPanel(new BorderLayout()); descPanel.setBorder(BorderFactory.createTitledBorder("Description")); descPanel.add(descLabel, BorderLayout.CENTER); @@ -263,13 +268,14 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider { } } - private final OfferPanel offerPanel = new OfferPanel(); + private final OfferPanel offerPanel; private boolean isCancelled = false; - protected DebuggerSelectPlatformOfferDialog() { + protected DebuggerSelectPlatformOfferDialog(PluginTool tool) { super(DebuggerResources.NAME_CHOOSE_PLATFORM, true, false, true, false); + offerPanel = new OfferPanel(tool); populateComponents(); } @@ -340,4 +346,4 @@ public class DebuggerSelectPlatformOfferDialog extends DialogComponentProvider { } // Do nothing. Should be disabled anyway } -} +} \ No newline at end of file diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerAvailableRegistersDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerAvailableRegistersDialog.java index d5907abc3d..fea51a28e9 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerAvailableRegistersDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerAvailableRegistersDialog.java @@ -32,6 +32,7 @@ import docking.widgets.table.DefaultEnumeratedColumnTableModel; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import docking.widgets.table.GTable; import ghidra.app.plugin.core.debug.gui.DebuggerResources; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; import ghidra.util.table.GhidraTableFilterPanel; @@ -105,8 +106,8 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider { protected static class AvailableRegistersTableModel extends DefaultEnumeratedColumnTableModel { - public AvailableRegistersTableModel() { - super("Available Registers", AvailableRegisterTableColumns.class); + public AvailableRegistersTableModel(PluginTool tool) { + super(tool, "Available Registers", AvailableRegisterTableColumns.class); } @Override @@ -119,8 +120,7 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider { private Language language; - /* testing */ final AvailableRegistersTableModel availableTableModel = - new AvailableRegistersTableModel(); + /* testing */ final AvailableRegistersTableModel availableTableModel; private final Map regMap = new HashMap<>(); private GTable availableTable; @@ -135,6 +135,7 @@ public class DebuggerAvailableRegistersDialog extends DialogComponentProvider { super(DebuggerResources.SelectRegistersAction.NAME, true, true, true, false); this.provider = provider; + availableTableModel = new AvailableRegistersTableModel(provider.getTool()); populateComponents(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TracePcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegisterColumnFactory.java similarity index 52% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TracePcodeExecutorState.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegisterColumnFactory.java index c51b5f5b49..6ca5c1cc03 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TracePcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegisterColumnFactory.java @@ -13,4 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.exec; +package ghidra.app.plugin.core.debug.gui.register; + +import docking.widgets.table.DynamicTableColumn; +import ghidra.util.classfinder.ExtensionPoint; + +/** + * A factory for adding a custom column to the Registers table + * + *

+ * All discovered factories' columns are automatically added as hidden columns to the Registers + * table. + */ +public interface DebuggerRegisterColumnFactory extends ExtensionPoint { + /** + * Create the column + * + * @return the column + */ + DynamicTableColumn create(); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java index 87e3645e7f..31ae275044 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/DebuggerRegistersProvider.java @@ -77,6 +77,7 @@ import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.*; import ghidra.util.*; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.data.DataTypeParser.AllowedDataTypes; import ghidra.util.database.UndoableTransaction; import ghidra.util.exception.CancelledException; @@ -201,14 +202,24 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter protected static class RegistersTableModel extends DefaultEnumeratedColumnTableModel { - public RegistersTableModel() { - super("Registers", RegisterTableColumns.class); + public RegistersTableModel(PluginTool tool) { + super(tool, "Registers", RegisterTableColumns.class); } @Override public List defaultSortOrder() { return List.of(RegisterTableColumns.FAV, RegisterTableColumns.NUMBER); } + + @Override + protected TableColumnDescriptor createTableColumnDescriptor() { + TableColumnDescriptor descriptor = super.createTableColumnDescriptor(); + for (DebuggerRegisterColumnFactory factory : ClassSearcher + .getInstances(DebuggerRegisterColumnFactory.class)) { + descriptor.addHiddenColumn(factory.create()); + } + return descriptor; + } } protected static boolean sameCoordinates(DebuggerCoordinates a, DebuggerCoordinates b) { @@ -472,8 +483,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter private JPanel mainPanel = new JPanel(new BorderLayout()); + final RegistersTableModel regsTableModel; GhidraTable regsTable; - RegistersTableModel regsTableModel = new RegistersTableModel(); GhidraTableFilterPanel regsFilterPanel; Map regMap = new HashMap<>(); @@ -495,6 +506,9 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter boolean isClone) { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_REGISTERS, plugin.getName()); this.plugin = plugin; + + regsTableModel = new RegistersTableModel(tool); + this.selectionByCSpec = selectionByCSpec; this.favoritesByCSpec = favoritesByCSpec; this.isClone = isClone; @@ -1387,4 +1401,8 @@ public class DebuggerRegistersProvider extends ComponentProviderAdapter DebuggerCoordinates.readDataState(tool, saveState, KEY_DEBUGGER_COORDINATES)); } } + + public DebuggerCoordinates getCurrent() { + return current; + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java index 407290fdb4..12fb6f1417 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/register/RegisterRow.java @@ -18,30 +18,49 @@ package ghidra.app.plugin.core.debug.gui.register; import java.math.BigInteger; import java.util.Objects; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.services.DebuggerStateEditingService; import ghidra.program.model.data.DataType; import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; import ghidra.program.model.listing.Data; +import ghidra.trace.model.Trace; import ghidra.util.Msg; +/** + * A row displayed in the registers table of the Debugger + */ public class RegisterRow { private final DebuggerRegistersProvider provider; private boolean favorite; private final int number; private final Register register; - public RegisterRow(DebuggerRegistersProvider provider, int number, Register register) { + protected RegisterRow(DebuggerRegistersProvider provider, int number, Register register) { this.provider = provider; this.number = number; this.register = Objects.requireNonNull(register); this.favorite = provider.isFavorite(register); } + /** + * Set whether this register is one of the user's favorites + * + *

+ * Note: Favorites are memorized on a per-compiler-spec (ABI, almost) basis. + * + * @param favorite true if favorite + */ public void setFavorite(boolean favorite) { this.favorite = favorite; provider.setFavorite(register, favorite); } + /** + * Check if this register is one of the user's favorites + * + * @return true if favorite + */ public boolean isFavorite() { return favorite; } @@ -55,18 +74,42 @@ public class RegisterRow { return number; } + /** + * Get the register + * + * @return the register + */ public Register getRegister() { return register; } + /** + * Get the register's name + * + * @return the name + */ public String getName() { return register.getName(); } + /** + * Check if the register can be edited + * + * @return true if editable + */ public boolean isValueEditable() { return provider.canWriteRegister(register); } + /** + * Attempt to set the register's value + * + *

+ * The edit will be directed according to the tool's current edit mode. See + * {@link DebuggerStateEditingService#getCurrentMode(Trace)} + * + * @param value the value + */ public void setValue(BigInteger value) { try { provider.writeRegisterValue(register, value); @@ -78,8 +121,13 @@ public class RegisterRow { } /** + * Get the value of the register + * + *

* TODO: Perhaps some caching for all these getters which rely on the DB, since they could be * invoked on every repaint. + * + * @return the value */ public BigInteger getValue() { return provider.getRegisterValue(register); @@ -89,31 +137,78 @@ public class RegisterRow { return provider.getRegisterData(register); } + /** + * Assign a data type to the register + * + *

+ * This is memorized in the trace for the current and future snaps + * + * @param dataType the data type + */ public void setDataType(DataType dataType) { provider.writeRegisterDataType(register, dataType); } + /** + * Get the data type of the register + * + * @return the data type + */ public DataType getDataType() { return provider.getRegisterDataType(register); } + /** + * Set the value of the register as represented by its data type + * + * @param representation the value to set + */ public void setRepresentation(String representation) { provider.writeRegisterValueRepresentation(register, representation); } + /** + * Check if the register's value can be set via its data type's representation + * + * @return + */ public boolean isRepresentationEditable() { return provider.canWriteRegisterRepresentation(register); } + /** + * Get the value of the register as represented by its data type + * + * @return the value + */ public String getRepresentation() { return provider.getRegisterValueRepresentation(register); } + /** + * Check if the register's value is (completely) known + * + * @return true if known + */ public boolean isKnown() { return provider.isRegisterKnown(register); } + /** + * Check if the register's value changed since last navigation or command + * + * @return true if changed + */ public boolean isChanged() { return provider.isRegisterChanged(register); } + + /** + * Get the table's current coordinates (usually also the tool's) + * + * @return the coordinates + */ + public DebuggerCoordinates getCurrent() { + return provider.getCurrent(); + } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java index 0de60c933d..aeb5043152 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/stack/DebuggerStackProvider.java @@ -36,8 +36,7 @@ import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.services.*; import ghidra.dbg.DebugModelConventions; import ghidra.dbg.target.TargetStackFrame; -import ghidra.framework.plugintool.AutoService; -import ghidra.framework.plugintool.ComponentProviderAdapter; +import ghidra.framework.plugintool.*; import ghidra.framework.plugintool.annotation.AutoServiceConsumed; import ghidra.program.model.address.Address; import ghidra.program.model.lang.Register; @@ -116,8 +115,8 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { protected static class StackTableModel extends DefaultEnumeratedColumnTableModel { - public StackTableModel() { - super("Stack", StackTableColumns.class); + public StackTableModel(PluginTool tool) { + super(tool, "Stack", StackTableColumns.class); } @Override @@ -243,7 +242,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { private final SuppressableCallback cbFrameSelected = new SuppressableCallback<>(); - protected final StackTableModel stackTableModel = new StackTableModel(); + protected final StackTableModel stackTableModel; protected GhidraTable stackTable; protected GhidraTableFilterPanel stackFilterPanel; @@ -253,7 +252,7 @@ public class DebuggerStackProvider extends ComponentProviderAdapter { public DebuggerStackProvider(DebuggerStackPlugin plugin) { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_STACK, plugin.getName()); - //this.plugin = plugin; + stackTableModel = new StackTableModel(tool); this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java index bc81d21a9d..9c771f93ac 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/thread/DebuggerThreadsProvider.java @@ -92,8 +92,8 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter { ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> { public ThreadTableModel(DebuggerThreadsProvider provider) { - super("Threads", ThreadTableColumns.class, TraceThread::getObjectKey, - t -> new ThreadRow(provider.modelService, t)); + super(provider.getTool(), "Threads", ThreadTableColumns.class, + TraceThread::getObjectKey, t -> new ThreadRow(provider.modelService, t)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java index 38d4346201..cae6b229eb 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerSnapshotTablePanel.java @@ -30,6 +30,7 @@ import com.google.common.collect.Collections2; import docking.widgets.table.*; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import ghidra.framework.model.DomainObject; +import ghidra.framework.plugintool.PluginTool; import ghidra.trace.model.Trace; import ghidra.trace.model.Trace.TraceSnapshotChangeType; import ghidra.trace.model.TraceDomainObjectListener; @@ -130,8 +131,7 @@ public class DebuggerSnapshotTablePanel extends JPanel { } } - protected final EnumeratedColumnTableModel snapshotTableModel = - new DefaultEnumeratedColumnTableModel<>("Snapshots", SnapshotTableColumns.class); + protected final EnumeratedColumnTableModel snapshotTableModel; protected final GTable snapshotTable; protected final GhidraTableFilterPanel snapshotFilterPanel; protected boolean hideScratch = true; @@ -141,8 +141,10 @@ public class DebuggerSnapshotTablePanel extends JPanel { protected final SnapshotListener listener = new SnapshotListener(); - public DebuggerSnapshotTablePanel() { + public DebuggerSnapshotTablePanel(PluginTool tool) { super(new BorderLayout()); + snapshotTableModel = + new DefaultEnumeratedColumnTableModel<>(tool, "Snapshots", SnapshotTableColumns.class); snapshotTable = new GTable(snapshotTableModel); snapshotTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); add(new JScrollPane(snapshotTable)); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java index 2c3b4c8090..6f49bc3dec 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeProvider.java @@ -60,7 +60,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { @SuppressWarnings("unused") private final Wiring autoServiceWiring; - /*testing*/ final DebuggerSnapshotTablePanel mainPanel = new DebuggerSnapshotTablePanel(); + /*testing*/ final DebuggerSnapshotTablePanel mainPanel; private DebuggerSnapActionContext myActionContext; @@ -80,6 +80,7 @@ public class DebuggerTimeProvider extends ComponentProviderAdapter { setHelpLocation(HELP_PROVIDER_TIME); setWindowMenuGroup(DebuggerPluginPackage.NAME); + mainPanel = new DebuggerSnapshotTablePanel(tool); buildMainPanel(); myActionContext = new DebuggerSnapActionContext(current.getTrace(), current.getSnap()); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java index aa82540213..76a8d94238 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/time/DebuggerTimeSelectionDialog.java @@ -86,7 +86,7 @@ public class DebuggerTimeSelectionDialog extends DialogComponentProvider { opStep.addActionListener(evt -> doStep(s -> s.steppedPcodeForward(null, 1))); { - snapshotPanel = new DebuggerSnapshotTablePanel(); + snapshotPanel = new DebuggerSnapshotTablePanel(tool); workPanel.add(snapshotPanel, BorderLayout.CENTER); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java index 199c378996..27ec9e9297 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/DebuggerWatchesProvider.java @@ -197,8 +197,8 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter { protected static class WatchTableModel extends DefaultEnumeratedColumnTableModel { - public WatchTableModel() { - super("Watches", WatchTableColumns.class); + public WatchTableModel(PluginTool tool) { + super(tool, "Watches", WatchTableColumns.class); } } @@ -346,7 +346,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter { private JPanel mainPanel = new JPanel(new BorderLayout()); - protected final WatchTableModel watchTableModel = new WatchTableModel(); + protected final WatchTableModel watchTableModel; protected GhidraTable watchTable; protected GhidraTableFilterPanel watchFilterPanel; @@ -366,6 +366,7 @@ public class DebuggerWatchesProvider extends ComponentProviderAdapter { public DebuggerWatchesProvider(DebuggerWatchesPlugin plugin) { super(plugin.getTool(), DebuggerResources.TITLE_PROVIDER_WATCHES, plugin.getName()); this.plugin = plugin; + watchTableModel = new WatchTableModel(tool); this.autoServiceWiring = AutoService.wireServicesConsumed(plugin, this); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java index 3ad1c468a4..17f4b566b4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/gui/watch/WatchRow.java @@ -31,7 +31,7 @@ import ghidra.docking.settings.Settings; import ghidra.docking.settings.SettingsImpl; import ghidra.framework.options.SaveState; import ghidra.pcode.exec.*; -import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState; +import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece; import ghidra.pcode.exec.trace.TraceSleighUtils; import ghidra.pcode.utils.Utils; import ghidra.program.model.address.*; @@ -170,21 +170,20 @@ public class WatchRow { return dataType.getValue(buffer, SettingsImpl.NO_SETTINGS, value.length); } - public static class ReadDepsTraceBytesPcodeExecutorState - extends TraceBytesPcodeExecutorState { + public static class ReadDepsTraceBytesPcodeExecutorStatePiece + extends DirectBytesTracePcodeExecutorStatePiece { private AddressSet reads = new AddressSet(); - public ReadDepsTraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, + public ReadDepsTraceBytesPcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, int frame) { super(trace, snap, thread, frame); } @Override - public byte[] getVar(AddressSpace space, long offset, int size, - boolean truncateAddressableUnit) { - byte[] data = super.getVar(space, offset, size, truncateAddressableUnit); + public byte[] getVar(AddressSpace space, long offset, int size, boolean quantize) { + byte[] data = super.getVar(space, offset, size, quantize); if (space.isMemorySpace()) { - offset = truncateOffset(space, offset); + offset = quantizeOffset(space, offset); } if (space.isMemorySpace() || space.isRegisterSpace()) { try { @@ -213,42 +212,42 @@ public class WatchRow { public static class ReadDepsPcodeExecutor extends PcodeExecutor> { - private ReadDepsTraceBytesPcodeExecutorState depsState; + private ReadDepsTraceBytesPcodeExecutorStatePiece depsPiece; - public ReadDepsPcodeExecutor(ReadDepsTraceBytesPcodeExecutorState depsState, + public ReadDepsPcodeExecutor(ReadDepsTraceBytesPcodeExecutorStatePiece depsState, SleighLanguage language, PairedPcodeArithmetic arithmetic, PcodeExecutorState> state) { super(language, arithmetic, state); - this.depsState = depsState; + this.depsPiece = depsState; } @Override public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary> library) { - depsState.reset(); + depsPiece.reset(); return super.execute(program, library); } public AddressSet getReads() { - return depsState.getReads(); + return depsPiece.getReads(); } } protected static ReadDepsPcodeExecutor buildAddressDepsExecutor( DebuggerCoordinates coordinates) { Trace trace = coordinates.getTrace(); - ReadDepsTraceBytesPcodeExecutorState state = - new ReadDepsTraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(), + ReadDepsTraceBytesPcodeExecutorStatePiece piece = + new ReadDepsTraceBytesPcodeExecutorStatePiece(trace, coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame()); Language language = trace.getBaseLanguage(); if (!(language instanceof SleighLanguage)) { throw new IllegalArgumentException("Watch expressions require a SLEIGH language"); } - PcodeExecutorState> paired = - state.paired(new AddressOfPcodeExecutorState(language.isBigEndian())); + PcodeExecutorState> paired = new DefaultPcodeExecutorState<>(piece) + .paired(new AddressOfPcodeExecutorStatePiece(language.isBigEndian())); PairedPcodeArithmetic arithmetic = new PairedPcodeArithmetic<>( BytesPcodeArithmetic.forLanguage(language), AddressOfPcodeArithmetic.INSTANCE); - return new ReadDepsPcodeExecutor(state, (SleighLanguage) language, arithmetic, paired); + return new ReadDepsPcodeExecutor(piece, (SleighLanguage) language, arithmetic, paired); } public void setCoordinates(DebuggerCoordinates coordinates) { @@ -271,7 +270,7 @@ public class WatchRow { recompile(); } if (coordinates.isAliveAndReadsPresent()) { - asyncExecutor = TracePcodeUtils.executorForCoordinates(coordinates); + asyncExecutor = DebuggerPcodeUtils.executorForCoordinates(coordinates); } executorWithState = TraceSleighUtils.buildByteWithStateExecutor(trace, coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame()); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorStatePiece.java similarity index 63% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorState.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorStatePiece.java index b0749d842c..a032a694c0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/AbstractReadsTargetPcodeExecutorStatePiece.java @@ -20,7 +20,7 @@ import java.util.concurrent.*; import ghidra.app.services.TraceRecorder; import ghidra.framework.plugintool.PluginTool; import ghidra.pcode.exec.AccessPcodeExecutionException; -import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState; +import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece; import ghidra.pcode.exec.trace.TraceSleighUtils; import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSpace; @@ -31,8 +31,17 @@ import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; import ghidra.util.database.UndoableTransaction; -public abstract class AbstractReadsTargetPcodeExecutorState - extends TraceCachedWriteBytesPcodeExecutorState { +/** + * An executor state piece that knows to read live state if applicable + * + *

+ * This takes a handle to the trace's recorder, if applicable, and will check if the source snap is + * the recorder's snap. If so, it will direct the recorder to capture the desired state, if they're + * not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to 1 + * second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}). + */ +public abstract class AbstractReadsTargetPcodeExecutorStatePiece + extends BytesTracePcodeExecutorStatePiece { abstract class AbstractReadsTargetCachedSpace extends CachedSpace { public AbstractReadsTargetCachedSpace(Language language, AddressSpace space, @@ -86,30 +95,47 @@ public abstract class AbstractReadsTargetPcodeExecutorState protected final TraceRecorder recorder; protected final PluginTool tool; - public AbstractReadsTargetPcodeExecutorState(PluginTool tool, Trace trace, long snap, + public AbstractReadsTargetPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap, TraceThread thread, int frame, TraceRecorder recorder) { super(trace, snap, thread, frame); this.tool = tool; this.recorder = recorder; } - protected abstract AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s, - TraceMemorySpace tms); + /** + * Get the tool that manages this state's emulator. + * + *

+ * This is necessary to obtain the static mapping service, in case memory should be filled from + * static images. + * + * @return the tool + */ + public PluginTool getTool() { + return tool; + } - @Override - protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) { - return spaces.computeIfAbsent(space, s -> { - TraceMemorySpace tms; - if (s.isUniqueSpace()) { - tms = null; + /** + * Get the recorder associated with the trace + * + * @return this is used to check for and perform live reads + */ + public TraceRecorder getRecorder() { + return recorder; + } + + /** + * A partially implemented space map which retrieves "backing" objects from the trace's memory + * and register spaces. + */ + protected abstract class TargetBackedSpaceMap + extends CacheingSpaceMap { + @Override + protected TraceMemorySpace getBacking(AddressSpace space) { + try (UndoableTransaction tid = + UndoableTransaction.start(trace, "Create space")) { + return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, true); } - else { - try (UndoableTransaction tid = - UndoableTransaction.start(trace, "Create space")) { - tms = TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, true); - } - } - return createCachedSpace(s, tms); - }); + } } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerTracePcodeEmulator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java similarity index 56% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerTracePcodeEmulator.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java index 60e6542b2f..320cf73163 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerTracePcodeEmulator.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulator.java @@ -17,67 +17,70 @@ package ghidra.app.plugin.core.debug.service.emulation; import ghidra.app.services.TraceRecorder; import ghidra.framework.plugintool.PluginTool; -import ghidra.pcode.emu.BytesPcodeThread; -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.pcode.exec.trace.TracePcodeEmulator; -import ghidra.program.model.lang.Register; -import ghidra.program.model.lang.RegisterValue; +import ghidra.pcode.emu.*; +import ghidra.pcode.exec.trace.BytesTracePcodeEmulator; +import ghidra.pcode.exec.trace.TracePcodeExecutorState; import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemoryRegisterSpace; -import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; /** * A trace emulator that knows how to read target memory when necessary * *

+ * This is the default emulator used by the Debugger UI to perform interpolation and extrapolation. + * For standalone scripting, consider using {@link BytesTracePcodeEmulator} or {@link PcodeEmulator} + * instead. The former readily reads and records its state to traces, while the latter is the + * simplest use case. See scripts ending in {@code EmuExampleScript} for example uses. + * + *

* This emulator must always be run in its own thread, or at least a thread that can never lock the * UI. It blocks on target reads so that execution can proceed synchronously. Probably the most * suitable option is to use a background task. */ -public class DebuggerTracePcodeEmulator extends TracePcodeEmulator { +public class BytesDebuggerPcodeEmulator extends BytesTracePcodeEmulator + implements DebuggerPcodeMachine { protected final PluginTool tool; protected final TraceRecorder recorder; - public DebuggerTracePcodeEmulator(PluginTool tool, Trace trace, long snap, + /** + * Create the emulator + * + * @param tool the tool creating the emulator + * @param trace the trace from which the emulator loads state + * @param snap the snap from which the emulator loads state + * @param recorder if applicable, the recorder for the trace's live target + */ + public BytesDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap, TraceRecorder recorder) { super(trace, snap); this.tool = tool; this.recorder = recorder; } - protected boolean isRegisterKnown(String threadName, Register register) { - TraceThread thread = trace.getThreadManager().getLiveThreadByPath(snap, threadName); - TraceMemoryRegisterSpace space = - trace.getMemoryManager().getMemoryRegisterSpace(thread, false); - if (space == null) { - return false; - } - return space.getState(snap, register) == TraceMemoryState.KNOWN; + @Override + public PluginTool getTool() { + return tool; + } + + @Override + public TraceRecorder getRecorder() { + return recorder; } @Override protected BytesPcodeThread createThread(String name) { BytesPcodeThread thread = super.createThread(name); - Register contextreg = language.getContextBaseRegister(); - if (contextreg != Register.NO_CONTEXT && !isRegisterKnown(name, contextreg)) { - RegisterValue context = trace.getRegisterContextManager() - .getValueWithDefault(language, contextreg, snap, thread.getCounter()); - if (context != null) { // TODO: Why does this happen? - thread.overrideContext(context); - } - } + initializeThreadContext(thread); return thread; } @Override - protected PcodeExecutorState createSharedState() { + public TracePcodeExecutorState createSharedState() { return new ReadsTargetMemoryPcodeExecutorState(tool, trace, snap, null, 0, recorder); } @Override - protected PcodeExecutorState createLocalState(PcodeThread emuThread) { + public TracePcodeExecutorState createLocalState(PcodeThread emuThread) { TraceThread traceThread = trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName()); return new ReadsTargetRegistersPcodeExecutorState(tool, trace, snap, traceThread, 0, diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.java new file mode 100644 index 0000000000..f910afc52e --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/BytesDebuggerPcodeEmulatorFactory.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.app.plugin.core.debug.service.emulation; + +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.trace.model.Trace; + +/** + * The Debugger's default emulator factory + */ +public class BytesDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorFactory { + // TODO: Config options: + // 1) userop library + + @Override + public String getTitle() { + return "Default Concrete P-code Emulator"; + } + + @Override + public DebuggerPcodeMachine create(PluginTool tool, Trace trace, long snap, + TraceRecorder recorder) { + return new BytesDebuggerPcodeEmulator(tool, trace, snap, recorder); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java index f30e00fa48..e163564557 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerEmulationServicePlugin.java @@ -17,14 +17,19 @@ package ghidra.app.plugin.core.debug.service.emulation; import java.io.IOException; import java.util.*; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; + import org.apache.commons.lang3.exception.ExceptionUtils; import com.google.common.collect.Range; import docking.action.DockingAction; +import docking.action.ToggleDockingAction; import ghidra.app.context.ProgramLocationActionContext; import ghidra.app.events.ProgramActivatedPluginEvent; import ghidra.app.events.ProgramClosedPluginEvent; @@ -32,8 +37,7 @@ import ghidra.app.plugin.PluginCategoryNames; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.core.debug.DebuggerPluginPackage; import ghidra.app.plugin.core.debug.event.TraceClosedPluginEvent; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateAddThreadAction; -import ghidra.app.plugin.core.debug.gui.DebuggerResources.EmulateProgramAction; +import ghidra.app.plugin.core.debug.gui.DebuggerResources.*; import ghidra.app.services.*; import ghidra.async.AsyncLazyMap; import ghidra.framework.plugintool.*; @@ -49,6 +53,7 @@ import ghidra.trace.model.time.TraceSnapshot; import ghidra.trace.model.time.schedule.CompareResult; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.Msg; +import ghidra.util.classfinder.ClassSearcher; import ghidra.util.database.UndoableTransaction; import ghidra.util.exception.CancelledException; import ghidra.util.task.Task; @@ -74,20 +79,21 @@ import ghidra.util.task.TaskMonitor; }) public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEmulationService { protected static final int MAX_CACHE_SIZE = 5; - protected static long nextSnap = Long.MIN_VALUE; // HACK protected static class CacheKey implements Comparable { protected final Trace trace; protected final TraceSchedule time; + private final int hashCode; public CacheKey(Trace trace, TraceSchedule time) { - this.trace = trace; - this.time = time; + this.trace = Objects.requireNonNull(trace); + this.time = Objects.requireNonNull(time); + this.hashCode = Objects.hash(trace, time); } @Override public int hashCode() { - return Objects.hash(trace, time); + return hashCode; } @Override @@ -130,9 +136,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } protected static class CachedEmulator { - final DebuggerTracePcodeEmulator emulator; + final DebuggerPcodeMachine emulator; - public CachedEmulator(DebuggerTracePcodeEmulator emulator) { + public CachedEmulator(DebuggerPcodeMachine emulator) { this.emulator = emulator; } } @@ -162,6 +168,9 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } } + protected DebuggerPcodeEmulatorFactory emulatorFactory = + new BytesDebuggerPcodeEmulatorFactory(); + protected final Set eldest = new LinkedHashSet<>(); protected final NavigableMap cache = new TreeMap<>(); protected final AsyncLazyMap requests = @@ -180,6 +189,10 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm DockingAction actionEmulateProgram; DockingAction actionEmulateAddThread; + Map, ToggleDockingAction> // + actionsChooseEmulatorFactory = new HashMap<>(); + + final ChangeListener classChangeListener = this::classesChanged; public DebuggerEmulationServicePlugin(PluginTool tool) { super(tool); @@ -205,6 +218,46 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm .popupWhen(this::emulateAddThreadEnabled) .onAction(this::emulateAddThreadActivated) .buildAndInstall(tool); + ClassSearcher.addChangeListener(classChangeListener); + updateConfigureEmulatorStates(); + } + + private void classesChanged(ChangeEvent e) { + updateConfigureEmulatorStates(); + } + + private ToggleDockingAction createActionChooseEmulator(DebuggerPcodeEmulatorFactory factory) { + ToggleDockingAction action = ConfigureEmulatorAction.builder(this) + .menuPath(DebuggerPluginPackage.NAME, "Configure Emulator", factory.getTitle()) + .onAction(ctx -> configureEmulatorActivated(factory)) + .buildAndInstall(tool); + String[] path = action.getMenuBarData().getMenuPath(); + tool.setMenuGroup(Arrays.copyOf(path, path.length - 1), "zz"); + return action; + } + + private void updateConfigureEmulatorStates() { + Map, DebuggerPcodeEmulatorFactory> byClass = + getEmulatorFactories().stream() + .collect(Collectors.toMap(DebuggerPcodeEmulatorFactory::getClass, + Objects::requireNonNull)); + Iterator, ToggleDockingAction>> it = + actionsChooseEmulatorFactory.entrySet().iterator(); + while (it.hasNext()) { + Entry, ToggleDockingAction> ent = + it.next(); + if (!byClass.keySet().contains(ent.getKey())) { + tool.removeAction(ent.getValue()); + } + } + for (Entry, DebuggerPcodeEmulatorFactory> ent : byClass + .entrySet()) { + if (!actionsChooseEmulatorFactory.containsKey(ent.getKey())) { + ToggleDockingAction action = createActionChooseEmulator(ent.getValue()); + action.setSelected(ent.getKey() == emulatorFactory.getClass()); + actionsChooseEmulatorFactory.put(ent.getKey(), action); + } + } } private boolean emulateProgramEnabled(ProgramLocationActionContext ctx) { @@ -228,7 +281,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm Trace trace = null; try { trace = ProgramEmulationUtils.launchEmulationTrace(program, ctx.getAddress(), this); - traceManager.openTrace(trace); traceManager.activateTrace(trace); } @@ -275,7 +327,6 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } private void emulateAddThreadActivated(ProgramLocationActionContext ctx) { - Program programOrView = ctx.getProgram(); if (programOrView instanceof TraceProgramView) { TraceProgramView view = (TraceProgramView) programOrView; @@ -322,6 +373,35 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } } + private void configureEmulatorActivated(DebuggerPcodeEmulatorFactory factory) { + // TODO: Pull up config page. Tool Options? Program/Trace Options? + setEmulatorFactory(factory); + } + + @Override + public Collection getEmulatorFactories() { + return ClassSearcher.getInstances(DebuggerPcodeEmulatorFactory.class); + } + + @Override + public synchronized void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory) { + emulatorFactory = Objects.requireNonNull(factory); + for (ToggleDockingAction toggle : actionsChooseEmulatorFactory.values()) { + toggle.setSelected(false); + } + ToggleDockingAction chosen = actionsChooseEmulatorFactory.get(factory.getClass()); + if (chosen == null) { + // Must be special or otherwise not discovered. Could happen. + Msg.warn(this, "An undiscovered emulator factory was set via the API: " + factory); + } + chosen.setSelected(true); + } + + @Override + public synchronized DebuggerPcodeEmulatorFactory getEmulatorFactory() { + return emulatorFactory; + } + protected Map.Entry findNearestPrefix(CacheKey key) { synchronized (cache) { Map.Entry candidate = cache.floorEntry(key); @@ -378,7 +458,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm Trace trace = key.trace; TraceSchedule time = key.time; CachedEmulator ce; - DebuggerTracePcodeEmulator emu; + DebuggerPcodeMachine emu; Map.Entry ancestor = findNearestPrefix(key); if (ancestor != null) { CacheKey prevKey = ancestor.getKey(); @@ -396,7 +476,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm time.finish(trace, prevKey.time, emu, monitor); } else { - emu = new DebuggerTracePcodeEmulator(tool, trace, time.getSnap(), + emu = emulatorFactory.create(tool, trace, time.getSnap(), modelService == null ? null : modelService.getRecorder(trace)); ce = new CachedEmulator(emu); monitor.initialize(time.totalTickCount()); @@ -405,7 +485,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm TraceSnapshot destSnap; try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate")) { destSnap = findScratch(trace, time); - emu.writeDown(trace, destSnap.getKey(), time.getSnap(), false); + emu.writeDown(trace, destSnap.getKey(), time.getSnap()); } synchronized (cache) { @@ -436,7 +516,7 @@ public class DebuggerEmulationServicePlugin extends Plugin implements DebuggerEm } @Override - public DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time) { + public DebuggerPcodeMachine getCachedEmulator(Trace trace, TraceSchedule time) { CachedEmulator ce = cache.get(new CacheKey(trace, time)); return ce == null ? null : ce.emulator; } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeEmulatorFactory.java new file mode 100644 index 0000000000..8d99679f6f --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeEmulatorFactory.java @@ -0,0 +1,51 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.service.emulation; + +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.trace.model.Trace; +import ghidra.util.classfinder.ExtensionPoint; + +/** + * A factory for configuring and creating a Debugger-integrated emulator + * + *

+ * See {@link BytesDebuggerPcodeEmulatorFactory} for the default implementation. See the Taint + * Analyzer for the archetype of alternative implementations. + */ +public interface DebuggerPcodeEmulatorFactory extends ExtensionPoint { + // TODO: Config options, use ModelFactory as a model + + /** + * Get the title, to appear in menus and dialogs + * + * @return the title + */ + String getTitle(); + + /** + * Create the emulator + * + * @param tool the tool creating the emulator + * @param trace the user's current trace from which the emulator should load state + * @param snap the user's current snap from which the emulator should load state + * @param recorder if applicable, the recorder for the trace's live target + * @return the emulator + */ + DebuggerPcodeMachine create(PluginTool tool, Trace trace, long snap, + TraceRecorder recorder); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeMachine.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeMachine.java new file mode 100644 index 0000000000..37d4da7270 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/DebuggerPcodeMachine.java @@ -0,0 +1,51 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.service.emulation; + +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; +import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; +import ghidra.pcode.exec.trace.TracePcodeMachine; + +/** + * A Debugger-integrated emulator (or p-code machine) + * + *

+ * This is a "mix in" interface. It is part of the SPI, but not the API. That is, emulator + * developers should use this interface, but emulator clients should not. Clients should use + * {@link PcodeMachine} instead. A common implementation is an emulator with concrete plus some + * auxiliary state. To realize such a machine, please see {@link AuxDebuggerPcodeEmulator} and + * {@link AuxDebuggerEmulatorPartsFactory}. + * + * @param the type of values in the machine's memory and registers + */ +public interface DebuggerPcodeMachine extends TracePcodeMachine { + /** + * Get the tool where this emulator is integrated + * + * @return the tool + */ + PluginTool getTool(); + + /** + * Get the trace's recorder for its live target, if applicable + * + * @return the recorder, or null + */ + TraceRecorder getRecorder(); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java index c8aee6c97d..2cd2a15caf 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorState.java @@ -15,123 +15,29 @@ */ package ghidra.app.plugin.core.debug.service.emulation; -import java.util.Collection; -import java.util.Map.Entry; - -import ghidra.app.services.DebuggerStaticMappingService; -import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; import ghidra.app.services.TraceRecorder; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; -import ghidra.program.model.listing.Program; -import ghidra.program.model.mem.Memory; -import ghidra.program.model.mem.MemoryAccessException; +import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState; import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.thread.TraceThread; -import ghidra.util.MathUtilities; -import ghidra.util.Msg; -import ghidra.util.task.TaskMonitor; - -public class ReadsTargetMemoryPcodeExecutorState - extends AbstractReadsTargetPcodeExecutorState { - - protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace { - - public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space, - TraceMemorySpace backing, long snap) { - super(language, space, backing, snap); - } - - @Override - protected void fillUninitialized(AddressSet uninitialized) { - AddressSet unknown; - unknown = computeUnknown(uninitialized); - if (unknown.isEmpty()) { - return; - } - if (fillUnknownWithRecorder(unknown)) { - unknown = computeUnknown(uninitialized); - if (unknown.isEmpty()) { - return; - } - } - if (fillUnknownWithStaticImages(unknown)) { - unknown = computeUnknown(uninitialized); - if (unknown.isEmpty()) { - return; - } - } - } - - protected boolean fillUnknownWithRecorder(AddressSet unknown) { - if (!isLive()) { - return false; - } - waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false)); - return true; - } - - private boolean fillUnknownWithStaticImages(AddressSet unknown) { - boolean result = false; - // TODO: Expand to block? DON'T OVERWRITE KNOWN! - DebuggerStaticMappingService mappingService = - tool.getService(DebuggerStaticMappingService.class); - byte[] data = new byte[4096]; - for (Entry> ent : mappingService - .getOpenMappedViews(trace, unknown, snap) - .entrySet()) { - Program program = ent.getKey(); - Memory memory = program.getMemory(); - AddressSetView initialized = memory.getLoadedAndInitializedAddressSet(); - - Collection mappedSet = ent.getValue(); - for (MappedAddressRange mappedRng : mappedSet) { - AddressRange srng = mappedRng.getSourceAddressRange(); - long shift = mappedRng.getShift(); - for (AddressRange subsrng : initialized.intersectRange(srng.getMinAddress(), - srng.getMaxAddress())) { - Msg.debug(this, - "Filling in unknown trace memory in emulator using mapped image: " + - program + ": " + subsrng); - long lower = subsrng.getMinAddress().getOffset(); - long fullLen = subsrng.getLength(); - while (fullLen > 0) { - int len = MathUtilities.unsignedMin(data.length, fullLen); - try { - int read = - memory.getBytes(space.getAddress(lower), data, 0, len); - if (read < len) { - Msg.warn(this, - " Partial read of " + subsrng + ". Got " + read + - " bytes"); - } - // write(lower - shift, data, 0 ,read); - bytes.putData(lower - shift, data, 0, read); - } - catch (MemoryAccessException | AddressOutOfBoundsException e) { - throw new AssertionError(e); - } - lower += len; - fullLen -= len; - } - result = true; - } - } - } - return result; - } - } +/** + * A state composing a single {@link ReadsTargetMemoryPcodeExecutorStatePiece} + */ +public class ReadsTargetMemoryPcodeExecutorState extends DefaultTracePcodeExecutorState { + /** + * Create the state + * + * @param tool the tool of the emulator + * @param trace the trace of the emulator + * @param snap the snap of the emulator + * @param thread probably null, since this the shared part + * @param frame probably 0, because frame only matters for non-null thread + * @param recorder the recorder of the emulator + */ public ReadsTargetMemoryPcodeExecutorState(PluginTool tool, Trace trace, long snap, TraceThread thread, int frame, TraceRecorder recorder) { - super(tool, trace, snap, thread, frame, recorder); - } - - @Override - protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s, - TraceMemorySpace tms) { - return new ReadsTargetMemoryCachedSpace(language, s, tms, snap); + super(new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, thread, frame, + recorder)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..4735ff9bdb --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetMemoryPcodeExecutorStatePiece.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.app.plugin.core.debug.service.emulation; + +import java.util.Collection; +import java.util.Map.Entry; +import java.util.concurrent.CompletableFuture; + +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.app.services.DebuggerStaticMappingService.MappedAddressRange; +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.MathUtilities; +import ghidra.util.Msg; +import ghidra.util.task.TaskMonitor; + +/** + * An executor state piece that knows to read live memory if applicable + * + *

+ * This takes a handle to the trace's recorder, if applicable, and will check if the source snap is + * the recorder's snap. If so, it will direct the recorder to capture the block(s) containing the + * read, if they're not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state + * will wait up to 1 second (see + * {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}). + * + *

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

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

+ * If all those defer, the state is read as if filled with 0s. + */ +public class ReadsTargetMemoryPcodeExecutorStatePiece + extends AbstractReadsTargetPcodeExecutorStatePiece { + + /** + * A space, corresponding to a memory space, of this state + * + *

+ * All of the actual read logic is contained here. We override the space map factory so that it + * creates these spaces. + */ + protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace { + + public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space, + TraceMemorySpace backing, long snap) { + super(language, space, backing, snap); + } + + @Override + protected void fillUninitialized(AddressSet uninitialized) { + AddressSet unknown; + unknown = computeUnknown(uninitialized); + if (unknown.isEmpty()) { + return; + } + if (fillUnknownWithRecorder(unknown)) { + unknown = computeUnknown(uninitialized); + if (unknown.isEmpty()) { + return; + } + } + if (fillUnknownWithStaticImages(unknown)) { + unknown = computeUnknown(uninitialized); + if (unknown.isEmpty()) { + return; + } + } + } + + protected boolean fillUnknownWithRecorder(AddressSet unknown) { + if (!isLive()) { + return false; + } + waitTimeout(recorder.readMemoryBlocks(unknown, TaskMonitor.DUMMY, false)); + return true; + } + + private boolean fillUnknownWithStaticImages(AddressSet unknown) { + boolean result = false; + // TODO: Expand to block? DON'T OVERWRITE KNOWN! + DebuggerStaticMappingService mappingService = + tool.getService(DebuggerStaticMappingService.class); + byte[] data = new byte[4096]; + for (Entry> ent : mappingService + .getOpenMappedViews(trace, unknown, snap) + .entrySet()) { + Program program = ent.getKey(); + Memory memory = program.getMemory(); + AddressSetView initialized = memory.getLoadedAndInitializedAddressSet(); + + Collection mappedSet = ent.getValue(); + for (MappedAddressRange mappedRng : mappedSet) { + AddressRange drng = mappedRng.getDestinationAddressRange(); + long shift = mappedRng.getShift(); + for (AddressRange subdrng : initialized.intersectRange(drng.getMinAddress(), + drng.getMaxAddress())) { + Msg.debug(this, + "Filling in unknown trace memory in emulator using mapped image: " + + program + ": " + subdrng); + long lower = subdrng.getMinAddress().getOffset(); + long fullLen = subdrng.getLength(); + while (fullLen > 0) { + int len = MathUtilities.unsignedMin(data.length, fullLen); + try { + int read = + memory.getBytes(space.getAddress(lower), data, 0, len); + if (read < len) { + Msg.warn(this, + " Partial read of " + subdrng + ". Got " + read + + " bytes"); + } + // write(lower - shift, data, 0 ,read); + bytes.putData(lower - shift, data, 0, read); + } + catch (MemoryAccessException | AddressOutOfBoundsException e) { + throw new AssertionError(e); + } + lower += len; + fullLen -= len; + } + result = true; + } + } + } + return result; + } + } + + public ReadsTargetMemoryPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap, + TraceThread thread, int frame, TraceRecorder recorder) { + super(tool, trace, snap, thread, frame, recorder); + } + + @Override + protected AbstractSpaceMap newSpaceMap() { + return new TargetBackedSpaceMap() { + @Override + protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { + return new ReadsTargetMemoryCachedSpace(language, space, backing, snap); + } + }; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorState.java index a7d0d0cc1b..7f438c6358 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorState.java @@ -15,63 +15,29 @@ */ package ghidra.app.plugin.core.debug.service.emulation; -import java.util.HashSet; -import java.util.Set; - import ghidra.app.services.TraceRecorder; import ghidra.framework.plugintool.PluginTool; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Language; -import ghidra.program.model.lang.Register; +import ghidra.pcode.exec.trace.DefaultTracePcodeExecutorState; import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.thread.TraceThread; -import ghidra.util.Msg; - -public class ReadsTargetRegistersPcodeExecutorState - extends AbstractReadsTargetPcodeExecutorState { - - protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace { - - public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space, - TraceMemorySpace source, long snap) { - super(language, space, source, snap); - } - - @Override - protected void fillUninitialized(AddressSet uninitialized) { - if (!isLive()) { - return; - } - AddressSet unknown = computeUnknown(uninitialized); - Set toRead = new HashSet<>(); - for (AddressRange rng : unknown) { - Register register = - language.getRegister(rng.getMinAddress(), (int) rng.getLength()); - if (register == null) { - Msg.error(this, "Could not figure register for " + rng); - } - else if (!recorder.getRegisterMapper(thread) - .getRegistersOnTarget() - .contains(register)) { - Msg.warn(this, "Register not recognized by target: " + register); - } - else { - toRead.add(register); - } - } - waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead)); - } - } +/** + * A state composing a single {@link ReadsTargetRegistersPcodeExecutorStatePiece} + */ +public class ReadsTargetRegistersPcodeExecutorState extends DefaultTracePcodeExecutorState { + /** + * Create the state + * + * @param tool the tool of the emulator + * @param trace the trace of the emulator + * @param snap the snap of the emulator + * @param thread the thread to which the state is assigned + * @param frame the frame to which the state is assigned, probably 0 + * @param recorder the recorder of the emulator + */ public ReadsTargetRegistersPcodeExecutorState(PluginTool tool, Trace trace, long snap, TraceThread thread, int frame, TraceRecorder recorder) { - super(tool, trace, snap, thread, frame, recorder); - } - - @Override - protected AbstractReadsTargetCachedSpace createCachedSpace(AddressSpace s, - TraceMemorySpace tms) { - return new ReadsTargetRegistersCachedSpace(language, s, tms, snap); + super(new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, thread, frame, + recorder)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..564737b618 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/emulation/ReadsTargetRegistersPcodeExecutorStatePiece.java @@ -0,0 +1,108 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.core.debug.service.emulation; + +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.Msg; + +/** + * An executor state piece that knows to read live memory if applicable + * + *

+ * This takes a handle to the trace's recorder, if applicable, and will check if the source snap is + * the recorder's snap. If so, it will direct the recorder to capture the register to be read, if + * it's not already {@link TraceMemoryState#KNOWN}. When such reads occur, the state will wait up to + * 1 second (see {@link AbstractReadsTargetCachedSpace#waitTimeout(CompletableFuture)}). + * + *

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

+ * If all those defer, the state is read as if filled with 0s. + */ +public class ReadsTargetRegistersPcodeExecutorStatePiece + extends AbstractReadsTargetPcodeExecutorStatePiece { + + /** + * A space, corresponding to a register space (really a thread) of this state + * + *

+ * All of the actual read logic is contained here. We override the space map factory so that it + * creates these spaces. + */ + protected class ReadsTargetRegistersCachedSpace extends AbstractReadsTargetCachedSpace { + + public ReadsTargetRegistersCachedSpace(Language language, AddressSpace space, + TraceMemorySpace source, long snap) { + super(language, space, source, snap); + } + + @Override + protected void fillUninitialized(AddressSet uninitialized) { + if (!isLive()) { + return; + } + AddressSet unknown = computeUnknown(uninitialized); + Set toRead = new HashSet<>(); + for (AddressRange rng : unknown) { + Register register = + language.getRegister(rng.getMinAddress(), (int) rng.getLength()); + if (register == null) { + Msg.error(this, "Could not figure register for " + rng); + } + else if (!recorder.getRegisterMapper(thread) + .getRegistersOnTarget() + .contains(register)) { + Msg.warn(this, "Register not recognized by target: " + register); + } + else { + toRead.add(register); + } + } + waitTimeout(recorder.captureThreadRegisters(thread, 0, toRead)); + } + } + + public ReadsTargetRegistersPcodeExecutorStatePiece(PluginTool tool, Trace trace, long snap, + TraceThread thread, int frame, TraceRecorder recorder) { + super(tool, trace, snap, thread, frame, recorder); + } + + @Override + protected AbstractSpaceMap newSpaceMap() { + return new TargetBackedSpaceMap() { + @Override + protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { + return new ReadsTargetRegistersCachedSpace(language, space, backing, snap); + } + }; + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java index 14e760eafa..326803058a 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerModelServicePlugin.java @@ -208,8 +208,7 @@ public class DebuggerModelServicePlugin extends Plugin protected final ChangeListener classChangeListener = new ChangeListenerForFactoryInstances(); protected final ListenerOnRecorders listenerOnRecorders = new ListenerOnRecorders(); - protected final DebuggerSelectMappingOfferDialog offerDialog = - new DebuggerSelectMappingOfferDialog(); + protected final DebuggerSelectMappingOfferDialog offerDialog; protected final DebuggerConnectDialog connectDialog = new DebuggerConnectDialog(); DockingAction actionDisconnectAll; @@ -218,7 +217,7 @@ public class DebuggerModelServicePlugin extends Plugin public DebuggerModelServicePlugin(PluginTool tool) { super(tool); - + offerDialog = new DebuggerSelectMappingOfferDialog(tool); ClassSearcher.addChangeListener(classChangeListener); refreshFactoryInstances(); connectDialog.setModelService(this); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerSelectMappingOfferDialog.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerSelectMappingOfferDialog.java index db57950828..25657d81c0 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerSelectMappingOfferDialog.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/service/model/DebuggerSelectMappingOfferDialog.java @@ -29,6 +29,7 @@ import docking.widgets.table.ColumnSortState.SortDirection; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; import ghidra.app.plugin.core.debug.gui.DebuggerResources; import ghidra.app.plugin.core.debug.mapping.DebuggerMappingOffer; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.lang.*; import ghidra.program.util.DefaultLanguageService; import ghidra.util.table.GhidraTable; @@ -126,8 +127,8 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider { public static class OfferTableModel extends DefaultEnumeratedColumnTableModel { - public OfferTableModel() { - super("Offers", OfferTableColumns.class); + public OfferTableModel(PluginTool tool) { + super(tool, "Offers", OfferTableColumns.class); } @Override @@ -138,14 +139,13 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider { } public static class OfferPanel extends JPanel { - private final OfferTableModel offerTableModel = new OfferTableModel(); - private final GhidraTable offerTable = new GhidraTable(offerTableModel); - private final GhidraTableFilterPanel offerTableFilterPanel = - new GhidraTableFilterPanel<>(offerTable, offerTableModel); + private final OfferTableModel offerTableModel; + private final GhidraTable offerTable; + private final GhidraTableFilterPanel offerTableFilterPanel; private final JLabel descLabel = new JLabel(); private final JCheckBox overrideCheckBox = new JCheckBox("Show Only Recommended Offers"); - private final JScrollPane scrollPane = new JScrollPane(offerTable) { + private final JScrollPane scrollPane = new JScrollPane() { @Override public Dimension getPreferredSize() { Dimension pref = super.getPreferredSize(); @@ -170,7 +170,12 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider { private LanguageID preferredLangID; private CompilerSpecID preferredCsID; - { + protected OfferPanel(PluginTool tool) { + offerTableModel = new OfferTableModel(tool); + offerTable = new GhidraTable(offerTableModel); + offerTableFilterPanel = new GhidraTableFilterPanel<>(offerTable, offerTableModel); + scrollPane.setViewportView(offerTable); + JPanel descPanel = new JPanel(new BorderLayout()); descPanel.setBorder(BorderFactory.createTitledBorder("Description")); descPanel.add(descLabel, BorderLayout.CENTER); @@ -255,13 +260,14 @@ public class DebuggerSelectMappingOfferDialog extends DialogComponentProvider { } } - private final OfferPanel offerPanel = new OfferPanel(); + private final OfferPanel offerPanel; private boolean isCancelled = false; - protected DebuggerSelectMappingOfferDialog() { + protected DebuggerSelectMappingOfferDialog(PluginTool tool) { super(DebuggerResources.AbstractRecordAction.NAME, true, false, true, false); + offerPanel = new OfferPanel(tool); populateComponents(); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DebouncedRowWrappedEnumeratedColumnTableModel.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DebouncedRowWrappedEnumeratedColumnTableModel.java index ab086ea131..b986a821db 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DebouncedRowWrappedEnumeratedColumnTableModel.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/plugin/core/debug/utils/DebouncedRowWrappedEnumeratedColumnTableModel.java @@ -21,6 +21,7 @@ import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableCo import docking.widgets.table.RowWrappedEnumeratedColumnTableModel; import ghidra.async.AsyncDebouncer; import ghidra.async.AsyncTimer; +import ghidra.framework.plugintool.PluginTool; import ghidra.util.Swing; public class DebouncedRowWrappedEnumeratedColumnTableModel & EnumeratedTableColumn, K, R, T> @@ -28,9 +29,10 @@ public class DebouncedRowWrappedEnumeratedColumnTableModel & E AsyncDebouncer debouncer = new AsyncDebouncer(AsyncTimer.DEFAULT_TIMER, 100); - public DebouncedRowWrappedEnumeratedColumnTableModel(String name, Class colType, + public DebouncedRowWrappedEnumeratedColumnTableModel(PluginTool tool, String name, + Class colType, Function keyFunc, Function wrapper) { - super(name, colType, keyFunc, wrapper); + super(tool, name, colType, keyFunc, wrapper); debouncer.addListener(this::settled); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java index f7cd05dccc..ccf79d252b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/app/services/DebuggerEmulationService.java @@ -15,19 +15,59 @@ */ package ghidra.app.services; +import java.util.Collection; import java.util.concurrent.CompletableFuture; -import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; -import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator; +import ghidra.app.plugin.core.debug.service.emulation.*; import ghidra.framework.plugintool.ServiceInfo; import ghidra.trace.model.Trace; import ghidra.trace.model.time.schedule.TraceSchedule; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; +/** + * A service for accessing managed emulators. + * + *

+ * Managed emulators are employed by the UI and trace manager to perform emulation requested by the + * user. Scripts may interact with these managed emulators, or they may instantiate their own + * unmanaged emulators, without using this service. + */ @ServiceInfo(defaultProvider = DebuggerEmulationServicePlugin.class) public interface DebuggerEmulationService { + /** + * Get the available emulator factories + * + * @return the collection of factories + */ + Collection getEmulatorFactories(); + + /** + * Set the current emulator factory + * + *

+ * TODO: Should this be set on a per-program, per-trace basis? Need to decide what is saved to + * the tool and what is saved to the program/trace. My inclination is to save current factory to + * the tool, but the config options for each factory to the program/trace. + * + *

+ * TODO: Should there be some opinion service for choosing default configs? Seem overly + * complicated for what it offers. For now, we won't save anything, we'll default to the + * (built-in) {@link BytesDebuggerPcodeEmulatorFactory}, and we won't have configuration + * options. + * + * @param factory the chosen factory + */ + void setEmulatorFactory(DebuggerPcodeEmulatorFactory factory); + + /** + * Get the current emulator factory + * + * @return the factory + */ + DebuggerPcodeEmulatorFactory getEmulatorFactory(); + /** * Perform emulation to realize the machine state of the given time coordinates * @@ -81,5 +121,5 @@ public interface DebuggerEmulationService { * @param time the time coordinates, including initial snap, steps, and p-code steps * @return the copied p-code frame */ - DebuggerTracePcodeEmulator getCachedEmulator(Trace trace, TraceSchedule time); + DebuggerPcodeMachine getCachedEmulator(Trace trace, TraceSchedule time); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java index 1261552b27..2129911710 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncPcodeExecutor.java @@ -21,6 +21,7 @@ import java.util.concurrent.CompletableFuture; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.async.AsyncUtils; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; @@ -36,12 +37,19 @@ import ghidra.program.model.pcode.Varnode; * until the computation has been performed -- assuming the requested variable actually depends on * that computation. * + *

+ * TODO: Deprecate this? It's clever, but it'd probably be easier to just use a synchronous executor + * on a separate thread. The necessity of {@link #stepAsync(PcodeFrame, PcodeUseropLibrary)}, etc., + * indicates a failure of the interface to encapsulate this use case. We can adjust the interface, + * which would probably not end well, or we can continue to allow the CompletableFuture-specific + * steppers to leak out, or we can just torch this and use another thread. + * * @param the type of values in the state */ public class AsyncPcodeExecutor extends PcodeExecutor> { public AsyncPcodeExecutor(SleighLanguage language, PcodeArithmetic> arithmetic, - PcodeExecutorStatePiece, CompletableFuture> state) { + PcodeExecutorState> state) { super(language, arithmetic, state); } @@ -73,7 +81,7 @@ public class AsyncPcodeExecutor extends PcodeExecutor> { Varnode condVar = op.getInput(1); CompletableFuture cond = state.getVar(condVar); return cond.thenAccept(c -> { - if (arithmetic.isTrue(cond)) { + if (arithmetic.isTrue(cond, Purpose.CONDITION)) { executeBranch(op, frame); } }); diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java index 03750efb7e..0f1473d748 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java @@ -15,21 +15,23 @@ */ package ghidra.pcode.exec; -import java.math.BigInteger; +import java.util.Objects; import java.util.concurrent.CompletableFuture; -import ghidra.pcode.opbehavior.BinaryOpBehavior; -import ghidra.pcode.opbehavior.UnaryOpBehavior; +import ghidra.program.model.lang.Endian; import ghidra.program.model.lang.Language; +/** + * An arithmetic which can operate on futures of a wrapped type + * + * @see AsyncPcodeExecutor for comment regarding potential deprecation or immediate removal + * @param the type of values wrapped + */ public class AsyncWrappedPcodeArithmetic implements PcodeArithmetic> { public static final AsyncWrappedPcodeArithmetic BYTES_BE = new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.BIG_ENDIAN); public static final AsyncWrappedPcodeArithmetic BYTES_LE = new AsyncWrappedPcodeArithmetic<>(BytesPcodeArithmetic.LITTLE_ENDIAN); - @Deprecated(forRemoval = true) // TODO: Not getting used - public static final AsyncWrappedPcodeArithmetic BIGINT = - new AsyncWrappedPcodeArithmetic<>(BigIntegerPcodeArithmetic.INSTANCE); public static AsyncWrappedPcodeArithmetic forEndian(boolean isBigEndian) { return isBigEndian ? BYTES_BE : BYTES_LE; @@ -46,46 +48,71 @@ public class AsyncWrappedPcodeArithmetic implements PcodeArithmetic unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, - CompletableFuture in1) { - return in1.thenApply(t1 -> arithmetic.unaryOp(op, sizeout, sizein1, t1)); + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (this.getClass() != obj.getClass()) { + return false; + } + AsyncWrappedPcodeArithmetic that = (AsyncWrappedPcodeArithmetic) obj; + return Objects.equals(this.arithmetic, that.arithmetic); } @Override - public CompletableFuture binaryOp(BinaryOpBehavior op, int sizeout, int sizein1, + public Endian getEndian() { + return arithmetic.getEndian(); + } + + @Override + public CompletableFuture unaryOp(int opcode, int sizeout, int sizein1, + CompletableFuture in1) { + return in1.thenApply(t1 -> arithmetic.unaryOp(opcode, sizeout, sizein1, t1)); + } + + @Override + public CompletableFuture binaryOp(int opcode, int sizeout, int sizein1, CompletableFuture in1, int sizein2, CompletableFuture in2) { return in1.thenCombine(in2, - (t1, t2) -> arithmetic.binaryOp(op, sizeout, sizein1, t1, sizein2, t2)); + (t1, t2) -> arithmetic.binaryOp(opcode, sizeout, sizein1, t1, sizein2, t2)); } @Override - public CompletableFuture fromConst(long value, int size) { - return CompletableFuture.completedFuture(arithmetic.fromConst(value, size)); + public CompletableFuture modBeforeStore(int sizeout, int sizeinAddress, + CompletableFuture inAddress, int sizeinValue, CompletableFuture inValue) { + return inValue; } @Override - public CompletableFuture fromConst(BigInteger value, int size, boolean isContextreg) { - return CompletableFuture.completedFuture(arithmetic.fromConst(value, size, isContextreg)); + public CompletableFuture modAfterLoad(int sizeout, int sizeinAddress, + CompletableFuture inAddress, int sizeinValue, CompletableFuture inValue) { + return inValue; } @Override - public boolean isTrue(CompletableFuture cond) { - if (!cond.isDone()) { - throw new AssertionError("You need a better 8-ball"); + public CompletableFuture fromConst(byte[] value) { + return CompletableFuture.completedFuture(arithmetic.fromConst(value)); + } + + @Override + public byte[] toConcrete(CompletableFuture value, Purpose purpose) { + if (!value.isDone()) { + throw new ConcretionError("You need a better 8-ball", purpose); } - return arithmetic.isTrue(cond.getNow(null)); + return arithmetic.toConcrete(value.getNow(null), purpose); } @Override - public BigInteger toConcrete(CompletableFuture cond, boolean isContextreg) { - if (!cond.isDone()) { - throw new AssertionError("You need a better 8-ball"); + public long sizeOf(CompletableFuture value) { + if (!value.isDone()) { + // TODO: Make a class which has future and expected size? + throw new RuntimeException("You need a better 8-ball"); } - return arithmetic.toConcrete(cond.getNow(null), isContextreg); + return arithmetic.sizeOf(value.getNow(null)); } @Override - public CompletableFuture sizeOf(CompletableFuture value) { - return value.thenApply(v -> arithmetic.sizeOf(v)); + public CompletableFuture sizeOfAbstract(CompletableFuture value) { + return value.thenApply(v -> arithmetic.sizeOfAbstract(v)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorState.java index 8f5b38e06e..c77ebbf2f4 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorState.java @@ -17,9 +17,14 @@ package ghidra.pcode.exec; import java.util.concurrent.CompletableFuture; -public class AsyncWrappedPcodeExecutorState extends AsyncWrappedPcodeExecutorStatePiece - implements PcodeExecutorState> { - public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece state) { - super(state); +/** + * The state for a {@link AsyncWrappedPcodeExecutorStatePiece} + * + * @param the type of wrapped values + */ +public class AsyncWrappedPcodeExecutorState + extends DefaultPcodeExecutorState> { + public AsyncWrappedPcodeExecutorState(PcodeExecutorStatePiece piece) { + super(new AsyncWrappedPcodeExecutorStatePiece<>(piece)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java index a7541e285b..aadd997822 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeExecutorStatePiece.java @@ -19,17 +19,38 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import ghidra.async.AsyncUtils; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.mem.MemBuffer; +/** + * An executor state piece which can operate on futures of a wrapped type + * + * @see AsyncPcodeExecutor for comment regarding potential deprecation or immediate removal + * @param the type of values wrapped + */ public class AsyncWrappedPcodeExecutorStatePiece implements PcodeExecutorStatePiece, CompletableFuture> { protected final PcodeExecutorStatePiece state; + protected final AsyncWrappedPcodeArithmetic addressArithmetic; + protected final AsyncWrappedPcodeArithmetic arithmetic; private CompletableFuture lastWrite = AsyncUtils.NIL; public AsyncWrappedPcodeExecutorStatePiece(PcodeExecutorStatePiece state) { this.state = state; + this.addressArithmetic = new AsyncWrappedPcodeArithmetic<>(state.getAddressArithmetic()); + this.arithmetic = new AsyncWrappedPcodeArithmetic<>(state.getArithmetic()); + } + + @Override + public AsyncWrappedPcodeArithmetic getAddressArithmetic() { + return addressArithmetic; + } + + @Override + public AsyncWrappedPcodeArithmetic getArithmetic() { + return arithmetic; } protected boolean isWriteDone() { @@ -45,41 +66,36 @@ public class AsyncWrappedPcodeExecutorStatePiece } protected CompletableFuture doSetVar(AddressSpace space, CompletableFuture offset, - int size, boolean truncateAddressableUnit, CompletableFuture val) { + int size, boolean quantize, CompletableFuture val) { return offset.thenCompose(off -> val.thenAccept(v -> { - state.setVar(space, off, size, truncateAddressableUnit, v); + state.setVar(space, off, size, quantize, v); })); } @Override public void setVar(AddressSpace space, CompletableFuture offset, int size, - boolean truncateAddressableUnit, CompletableFuture val) { - nextWrite(() -> doSetVar(space, offset, size, truncateAddressableUnit, val)); + boolean quantize, CompletableFuture val) { + nextWrite(() -> doSetVar(space, offset, size, quantize, val)); } protected CompletableFuture doGetVar(AddressSpace space, CompletableFuture offset, - int size, boolean truncateAddressableUnit) { + int size, boolean quantize) { return offset.thenApply(off -> { - return state.getVar(space, off, size, truncateAddressableUnit); + return state.getVar(space, off, size, quantize); }); } @Override public CompletableFuture getVar(AddressSpace space, CompletableFuture offset, int size, - boolean truncateAddressableUnit) { - return nextRead(() -> doGetVar(space, offset, size, truncateAddressableUnit)); + boolean quantize) { + return nextRead(() -> doGetVar(space, offset, size, quantize)); } @Override - public CompletableFuture longToOffset(AddressSpace space, long l) { - return CompletableFuture.completedFuture(state.longToOffset(space, l)); - } - - @Override - public MemBuffer getConcreteBuffer(Address address) { + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { if (!isWriteDone()) { throw new AssertionError("An async write is still pending"); } - return state.getConcreteBuffer(address); + return state.getConcreteBuffer(address, purpose); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TracePcodeUtils.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java similarity index 78% rename from Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TracePcodeUtils.java rename to Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java index 35c4e49d44..7dd480f8c1 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TracePcodeUtils.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/DebuggerPcodeUtils.java @@ -19,12 +19,22 @@ import java.util.concurrent.CompletableFuture; import ghidra.app.plugin.core.debug.DebuggerCoordinates; import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState; +import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece; import ghidra.program.model.lang.Language; import ghidra.trace.model.Trace; -public enum TracePcodeUtils { +public enum DebuggerPcodeUtils { ; + /** + * Get an executor which can be used to evaluate Sleigh expressions at the given coordinates, + * asynchronously. + * + *

+ * TODO: Change this to be synchronous and have clients evaluate expressions in another thread? + * + * @param coordinates the coordinates + * @return the executor + */ public static AsyncPcodeExecutor executorForCoordinates( DebuggerCoordinates coordinates) { Trace trace = coordinates.getTrace(); @@ -39,7 +49,7 @@ public enum TracePcodeUtils { PcodeExecutorState> state; if (coordinates.getRecorder() == null) { state = new AsyncWrappedPcodeExecutorState<>( - new TraceBytesPcodeExecutorState(trace, coordinates.getViewSnap(), + new DirectBytesTracePcodeExecutorStatePiece(trace, coordinates.getViewSnap(), coordinates.getThread(), coordinates.getFrame())); } else { diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorState.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorState.java index 88476a441e..470bdac82b 100644 --- a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorState.java +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorState.java @@ -15,128 +15,26 @@ */ package ghidra.pcode.exec; -import java.math.BigInteger; -import java.util.*; import java.util.concurrent.CompletableFuture; import ghidra.app.services.TraceRecorder; -import ghidra.pcode.exec.trace.TraceBytesPcodeExecutorState; -import ghidra.pcode.exec.trace.TraceMemoryStatePcodeExecutorStatePiece; -import ghidra.pcode.utils.Utils; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.*; -import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; -import ghidra.util.task.TaskMonitor; +/** + * A state composing a single {@link TraceRecorderAsyncPcodeExecutorStatePiece} + */ public class TraceRecorderAsyncPcodeExecutorState - extends AsyncWrappedPcodeExecutorState { - private final TraceRecorder recorder; - private final TraceBytesPcodeExecutorState traceState; - private final TraceMemoryStatePcodeExecutorStatePiece traceMemState; - + extends DefaultPcodeExecutorState> { + /** + * Create the state + * + * @param recorder the recorder for the trace's live target + * @param snap the user's current snap + * @param thread the user's current thread + * @param frame the user's current frame + */ public TraceRecorderAsyncPcodeExecutorState(TraceRecorder recorder, long snap, TraceThread thread, int frame) { - super(new TraceBytesPcodeExecutorState(recorder.getTrace(), snap, thread, frame)); - this.recorder = recorder; - this.traceState = (TraceBytesPcodeExecutorState) state; - this.traceMemState = - new TraceMemoryStatePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame); - } - - protected CompletableFuture doSetTargetVar(AddressSpace space, long offset, int size, - boolean truncateAddressableUnit, byte[] val) { - return recorder.writeVariable(traceState.getThread(), 0, space.getAddress(offset), val); - } - - protected byte[] knitFromResults(NavigableMap map, Address addr, int size) { - Address floor = map.floorKey(addr); - NavigableMap tail; - if (floor == null) { - tail = map; - } - else { - tail = map.tailMap(floor, true); - } - byte[] result = new byte[size]; - for (Map.Entry ent : tail.entrySet()) { - long off = ent.getKey().subtract(addr); - if (off >= size || off < 0) { - break; - } - int subSize = Math.min(size - (int) off, ent.getValue().length); - System.arraycopy(ent.getValue(), 0, result, (int) off, subSize); - } - return result; - } - - protected CompletableFuture doGetTargetVar(AddressSpace space, long offset, - int size, boolean truncateAddressableUnit) { - if (space.isMemorySpace()) { - Address addr = space.getAddress(truncateOffset(space, offset)); - AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1)); - CompletableFuture> future = - recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, true); - return future.thenApply(map -> { - return knitFromResults(map, addr, size); - }); - } - assert space.isRegisterSpace(); - - Language lang = recorder.getTrace().getBaseLanguage(); - Register register = lang.getRegister(space, offset, size); - if (register == null) { - // TODO: Is this too restrictive? - throw new IllegalArgumentException( - "read from register space must be from one register"); - } - Register baseRegister = register.getBaseRegister(); - - CompletableFuture> future = - recorder.captureThreadRegisters(traceState.getThread(), traceState.getFrame(), - Set.of(baseRegister)); - return future.thenApply(map -> { - RegisterValue baseVal = map.get(baseRegister); - if (baseVal == null) { - return state.getVar(space, offset, size, truncateAddressableUnit); - } - BigInteger val = baseVal.getRegisterValue(register).getUnsignedValue(); - return Utils.bigIntegerToBytes(val, size, - recorder.getTrace().getBaseLanguage().isBigEndian()); - }); - } - - protected boolean isTargetSpace(AddressSpace space) { - return traceState.getSnap() == recorder.getSnap() && !space.isConstantSpace() && - !space.isUniqueSpace(); - } - - @Override - protected CompletableFuture doSetVar(AddressSpace space, - CompletableFuture offset, int size, boolean truncateAddressableUnit, - CompletableFuture val) { - if (!isTargetSpace(space)) { - return super.doSetVar(space, offset, size, truncateAddressableUnit, val); - } - return offset.thenCompose(off -> val.thenCompose(v -> { - return doSetTargetVar(space, traceState.offsetToLong(off), size, - truncateAddressableUnit, v); - })); - } - - @Override - protected CompletableFuture doGetVar(AddressSpace space, - CompletableFuture offset, int size, boolean truncateAddressableUnit) { - if (!isTargetSpace(space)) { - return super.doGetVar(space, offset, size, truncateAddressableUnit); - } - return offset.thenCompose(off -> { - TraceMemoryState ms = traceMemState.getVar(space, off, size, truncateAddressableUnit); - if (ms == TraceMemoryState.KNOWN) { - return super.doGetVar(space, offset, size, truncateAddressableUnit); - } - return doGetTargetVar(space, traceState.offsetToLong(off), size, - truncateAddressableUnit); - }); + super(new TraceRecorderAsyncPcodeExecutorStatePiece(recorder, snap, thread, frame)); } } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorStatePiece.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..649234ccc6 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecutorStatePiece.java @@ -0,0 +1,154 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import java.math.BigInteger; +import java.util.*; +import java.util.concurrent.CompletableFuture; + +import ghidra.app.services.TraceRecorder; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.trace.DirectBytesTracePcodeExecutorStatePiece; +import ghidra.pcode.exec.trace.TraceMemoryStatePcodeExecutorStatePiece; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.*; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.task.TaskMonitor; + +/** + * An executor state which can asynchronously read and write a live target, if applicable + * + *

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

+ * TODO: It might be easier to re-factor this to operate synchronously, executing Sleigh programs in + * a separate thread. + */ +public class TraceRecorderAsyncPcodeExecutorStatePiece + extends AsyncWrappedPcodeExecutorStatePiece { + private final TraceRecorder recorder; + private final DirectBytesTracePcodeExecutorStatePiece traceState; + private final TraceMemoryStatePcodeExecutorStatePiece traceMemState; + + public TraceRecorderAsyncPcodeExecutorStatePiece(TraceRecorder recorder, long snap, + TraceThread thread, int frame) { + super( + new DirectBytesTracePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame)); + this.recorder = recorder; + this.traceState = (DirectBytesTracePcodeExecutorStatePiece) state; + this.traceMemState = + new TraceMemoryStatePcodeExecutorStatePiece(recorder.getTrace(), snap, thread, frame); + } + + protected CompletableFuture doSetTargetVar(AddressSpace space, long offset, int size, + boolean quantize, byte[] val) { + return recorder.writeVariable(traceState.getThread(), 0, space.getAddress(offset), val); + } + + protected byte[] knitFromResults(NavigableMap map, Address addr, int size) { + Address floor = map.floorKey(addr); + NavigableMap tail; + if (floor == null) { + tail = map; + } + else { + tail = map.tailMap(floor, true); + } + byte[] result = new byte[size]; + for (Map.Entry ent : tail.entrySet()) { + long off = ent.getKey().subtract(addr); + if (off >= size || off < 0) { + break; + } + int subSize = Math.min(size - (int) off, ent.getValue().length); + System.arraycopy(ent.getValue(), 0, result, (int) off, subSize); + } + return result; + } + + protected CompletableFuture doGetTargetVar(AddressSpace space, long offset, + int size, boolean quantize) { + if (space.isMemorySpace()) { + Address addr = space.getAddress(quantizeOffset(space, offset)); + AddressSet set = new AddressSet(addr, space.getAddress(offset + size - 1)); + CompletableFuture> future = + recorder.readMemoryBlocks(set, TaskMonitor.DUMMY, true); + return future.thenApply(map -> { + return knitFromResults(map, addr, size); + }); + } + assert space.isRegisterSpace(); + + Language lang = recorder.getTrace().getBaseLanguage(); + Register register = lang.getRegister(space, offset, size); + if (register == null) { + // TODO: Is this too restrictive? + throw new IllegalArgumentException( + "read from register space must be from one register"); + } + Register baseRegister = register.getBaseRegister(); + + CompletableFuture> future = + recorder.captureThreadRegisters(traceState.getThread(), traceState.getFrame(), + Set.of(baseRegister)); + return future.thenApply(map -> { + RegisterValue baseVal = map.get(baseRegister); + if (baseVal == null) { + return state.getVar(space, offset, size, quantize); + } + BigInteger val = baseVal.getRegisterValue(register).getUnsignedValue(); + return Utils.bigIntegerToBytes(val, size, + recorder.getTrace().getBaseLanguage().isBigEndian()); + }); + } + + protected boolean isTargetSpace(AddressSpace space) { + return traceState.getSnap() == recorder.getSnap() && !space.isConstantSpace() && + !space.isUniqueSpace(); + } + + @Override + protected CompletableFuture doSetVar(AddressSpace space, + CompletableFuture offset, int size, boolean quantize, + CompletableFuture val) { + if (!isTargetSpace(space)) { + return super.doSetVar(space, offset, size, quantize, val); + } + return offset.thenCompose(off -> val.thenCompose(v -> { + long lOff = traceState.getAddressArithmetic().toLong(off, Purpose.STORE); + return doSetTargetVar(space, lOff, size, quantize, v); + })); + } + + @Override + protected CompletableFuture doGetVar(AddressSpace space, + CompletableFuture offset, int size, boolean quantize) { + if (!isTargetSpace(space)) { + return super.doGetVar(space, offset, size, quantize); + } + return offset.thenCompose(off -> { + TraceMemoryState ms = traceMemState.getVar(space, off, size, quantize); + if (ms == TraceMemoryState.KNOWN) { + return super.doGetVar(space, offset, size, quantize); + } + long lOff = traceState.getAddressArithmetic().toLong(off, Purpose.LOAD); + return doGetTargetVar(space, lOff, size, quantize); + }); + } +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java new file mode 100644 index 0000000000..1144f0e8bb --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerEmulatorPartsFactory.java @@ -0,0 +1,100 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.debug.auxiliary; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.app.plugin.core.debug.service.emulation.*; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; +import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; +import ghidra.pcode.exec.trace.PairedTracePcodeExecutorStatePiece; +import ghidra.pcode.exec.trace.TracePcodeExecutorState; +import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; +import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; + +/** + * The most capable auxiliary emulator parts factory + * + *

+ * This can manufacture parts for an emulator that is fully integrated with the Debugger UI, as well + * as all the parts for the less integrated forms of the same emulator. The pattern of use is + * generally to implement {@link DebuggerPcodeEmulatorFactory}, allowing the UI to discover and + * instantiate the emulator, though they could also be created directly by scripts or plugins. + * + *

+ * For an example of a fully-integrated solution using this interface, see the Taint Analyzer. Its + * project serves as an archetype for similar dynamic analysis employing p-code emulation. + * + *

+ * We recommend implementors start with the methods declared in {@link AuxEmulatorPartsFactory} with + * the aim of creating a derivative of {@link AuxPcodeEmulator}. Note that one Debugger-integrated + * emulator parts factory can be used with all three of {@link AuxPcodeEmulator}, + * {@link AuxTracePcodeEmulator}, {@link AuxTraceEmulatorPartsFactory}. Once the stand-alone + * emulator has been tested, proceed to the methods in {@link AuxTraceEmulatorPartsFactory} with the + * aim of creating a derivative of {@link AuxTracePcodeEmulator}. Most of the work here is in + * factoring the state objects and pieces to reduce code duplication among the stand-alone and + * trace-integrated states. Once the trace-integrated emulator is tested, then proceed to the + * methods declared here in {@link AuxDebuggerEmulatorPartsFactory} with the aim of creating a + * derivative of {@link AuxDebuggerPcodeEmulator}. Again, most of the work is in factoring the + * states to avoid code duplication. Once the Debugger-integrated emulator is tested, the final bit + * is to implement a {@link DebuggerPcodeEmulatorFactory} so that users can configure and create the + * emulator. Other UI pieces, e.g., actions, fields, and table columns, may be needed to facilitate + * user access to the emulator's auxiliary state. Furthermore, a userop library for accessing the + * auxiliary state is recommended, since Sleigh code can be executed by the user. + * + * @param the type of auxiliary values + */ +public interface AuxDebuggerEmulatorPartsFactory extends AuxTraceEmulatorPartsFactory { + /** + * Create the shared (memory) state of a new Debugger-integrated emulator + * + *

+ * This state is usually composed of pieces using {@link PairedTracePcodeExecutorStatePiece}, + * but it does not have to be. It must incorporate the concrete piece provided. The state must + * be capable of lazily loading state from a trace, from a live target, and from mapped static + * programs. It must also be able to write its cache into the trace at another snapshot. The + * given concrete piece is already capable of doing that for concrete values. The auxiliary + * piece can, at its discretion, delegate to the concrete piece in order to derive its values. + * It should be able to independently load its state from the trace and mapped static program, + * since this is one way a user expects to initialize the auxiliary values. + * + * @param emulator the emulator + * @param concrete the concrete piece + * @return the composed state + */ + TracePcodeExecutorState> createDebuggerSharedState( + AuxDebuggerPcodeEmulator emulator, + ReadsTargetMemoryPcodeExecutorStatePiece concrete); + + /** + * Create the local (register) state of a new Debugger-integrated thread + * + *

+ * Like + * {@link #createDebuggerSharedState(AuxDebuggerPcodeEmulator, ReadsTargetMemoryPcodeExecutorStatePiece)} + * this state must also be capable of lazily loading state from a trace and from a live target. + * Static programs can't be mapped into register space, so they do not apply here. + * + * @param emulator the emulator + * @param thread the new thread + * @param concrete the concrete piece + * @return the composed state + */ + TracePcodeExecutorState> createDebuggerLocalState( + AuxDebuggerPcodeEmulator emulator, PcodeThread> thread, + ReadsTargetRegistersPcodeExecutorStatePiece concrete); +} diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java new file mode 100644 index 0000000000..75bd0e7267 --- /dev/null +++ b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/debug/auxiliary/AuxDebuggerPcodeEmulator.java @@ -0,0 +1,91 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.debug.auxiliary; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.app.plugin.core.debug.service.emulation.*; +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; +import ghidra.pcode.exec.trace.TracePcodeExecutorState; +import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; +import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; +import ghidra.trace.model.Trace; + +/** + * An Debugger-integrated emulator whose parts are manufactured by a + * {@link AuxDebuggerEmulatorPartsFactory} + * + *

+ * See the parts factory interface and its super interfaces: + *

+ * + * @param the type of auxiliary values + */ +public abstract class AuxDebuggerPcodeEmulator extends AuxTracePcodeEmulator + implements DebuggerPcodeMachine> { + protected final PluginTool tool; + protected final TraceRecorder recorder; + + /** + * Create a new emulator + * + * @param tool the user's tool where the emulator is integrated + * @param trace the user's current trace from which the emulator loads state + * @param snap the user's current snapshot from which the emulator loads state + * @param recorder if applicable, the trace's recorder for its live target + */ + public AuxDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap, + TraceRecorder recorder) { + super(trace, snap); + this.tool = tool; + this.recorder = recorder; + } + + @Override + protected abstract AuxDebuggerEmulatorPartsFactory getPartsFactory(); + + @Override + public PluginTool getTool() { + return tool; + } + + @Override + public TraceRecorder getRecorder() { + return recorder; + } + + @Override + public TracePcodeExecutorState> createSharedState() { + return getPartsFactory().createDebuggerSharedState(this, + new ReadsTargetMemoryPcodeExecutorStatePiece(tool, trace, snap, null, 0, recorder)); + } + + @Override + public TracePcodeExecutorState> createLocalState( + PcodeThread> thread) { + return getPartsFactory().createDebuggerLocalState(this, thread, + new ReadsTargetRegistersPcodeExecutorStatePiece(tool, trace, snap, + getTraceThread(thread), 0, + recorder)); + } +} 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 b01d02f7b1..a32207af13 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 @@ -567,7 +567,12 @@ public abstract class AbstractGhidraHeadedDebuggerGUITest @After public void tearDown() { waitForTasks(); - runSwing(() -> traceManager.setSaveTracesByDefault(false)); + runSwing(() -> { + if (traceManager == null) { + return; + } + traceManager.setSaveTracesByDefault(false); + }); if (tb != null) { if (traceManager != null && traceManager.getOpenTraces().contains(tb.trace)) { diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java index d90195a8e6..d231126178 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/gui/pcode/DebuggerPcodeStepperProviderTest.java @@ -29,7 +29,7 @@ import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; import ghidra.app.plugin.core.debug.gui.listing.DebuggerListingPlugin; import ghidra.app.plugin.core.debug.gui.pcode.DebuggerPcodeStepperProvider.PcodeRowHtmlFormatter; -import ghidra.app.plugin.core.debug.service.emulation.DebuggerTracePcodeEmulator; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine; import ghidra.app.plugin.core.debug.service.tracemgr.DebuggerTraceManagerServicePlugin; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.services.DebuggerEmulationService; @@ -142,10 +142,10 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg traceManager.activateTime(schedule2); waitForPass(() -> assertEquals(schedule2, pcodeProvider.current.getTime())); - DebuggerTracePcodeEmulator emu = + DebuggerPcodeMachine emu = waitForValue(() -> emuService.getCachedEmulator(tb.trace, schedule2)); assertNotNull(emu); - PcodeThread et = emu.getThread(thread.getPath(), false); + PcodeThread et = emu.getThread(thread.getPath(), false); waitForPass(() -> assertNull(et.getFrame())); /** @@ -171,7 +171,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", sleigh, PcodeUseropLibrary.nil()); PcodeExecutor executor = - new PcodeExecutor<>(language, PcodeArithmetic.BYTES_BE, null); + new PcodeExecutor<>(language, BytesPcodeArithmetic.BIG_ENDIAN, null); PcodeFrame frame = executor.begin(prog); PcodeRowHtmlFormatter formatter = pcodeProvider.new PcodeRowHtmlFormatter(language, frame); return formatter.getRows(); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java index 4baf378161..e343ea7797 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/service/editing/DebuggerStateEditingServiceTest.java @@ -33,7 +33,7 @@ import ghidra.app.services.DebuggerStateEditingService.StateEditor; import ghidra.app.services.TraceRecorder; import ghidra.dbg.target.TargetRegisterBank; import ghidra.pcode.exec.AsyncPcodeExecutor; -import ghidra.pcode.exec.TracePcodeUtils; +import ghidra.pcode.exec.DebuggerPcodeUtils; import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemoryAccessException; import ghidra.trace.database.DBTraceUtils; @@ -143,8 +143,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge try (UndoableTransaction tid = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread TraceThread thread = tb.getOrAddThread("Threads[0]", 0); - AsyncPcodeExecutor executor = - TracePcodeUtils.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); + AsyncPcodeExecutor executor = DebuggerPcodeUtils + .executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); asm.assemble(tb.addr(0x00400000), "imm r0,#123"); @@ -181,8 +181,8 @@ public class DebuggerStateEditingServiceTest extends AbstractGhidraHeadedDebugge try (UndoableTransaction tid = tb.startTransaction()) { // NB. TraceManager should automatically activate the first thread thread = tb.getOrAddThread("Threads[0]", 0); - AsyncPcodeExecutor executor = - TracePcodeUtils.executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); + AsyncPcodeExecutor executor = DebuggerPcodeUtils + .executorForCoordinates(DebuggerCoordinates.NOWHERE.thread(thread)); Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); asm.assemble(tb.addr(0x00400000), "imm r0,#123"); diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/pcode/exec/TraceRecorderAsyncPcodeExecTest.java index cab0c1166d..02a498ac48 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 @@ -29,6 +29,7 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.services.ActionSource; import ghidra.app.services.TraceRecorder; import ghidra.dbg.model.TestTargetRegisterBankInThread; +import ghidra.pcode.exec.*; import ghidra.pcode.utils.Utils; import ghidra.program.model.lang.Register; import ghidra.trace.model.Trace; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java similarity index 57% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java index a539f0db9a..98704c9c0a 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -24,8 +24,17 @@ import ghidra.trace.model.Trace; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.thread.TraceThread; -public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState - extends TraceCachedWriteBytesPcodeExecutorState { +/** + * A state piece which can check for uninitialized reads + * + *

+ * Depending on the use case, it may be desirable to ensure all reads through the course of + * emulation are from initialized parts of memory. For traces, there's an additional consideration + * as to whether the values are present, but state. Again, depending on the use case, that may be + * acceptable. See the extensions of this class for "stock" implementations. + */ +public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece + extends BytesTracePcodeExecutorStatePiece { protected class CheckedCachedSpace extends CachedSpace { public CheckedCachedSpace(Language language, AddressSpace space, TraceMemorySpace source, @@ -45,16 +54,31 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState } } - public AbstractCheckedTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, + public AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, int frame) { super(trace, snap, thread, frame); } @Override - protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { - return new CheckedCachedSpace(language, space, backing, snap); + protected AbstractSpaceMap newSpaceMap() { + return new TraceBackedSpaceMap() { + @Override + protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { + return new CheckedCachedSpace(language, space, backing, snap); + } + }; } + /** + * Decide what to do, give that a portion of a read is uninitialized + * + * @param backing the object backing the address space that was read + * @param start the starting address of the requested read + * @param size the size of the requested read + * @param uninitialized the portion of the read that is uninitialized + * @return the adjusted size of the read + * @throws Exception to interrupt the emulator + */ protected abstract int checkUninitialized(TraceMemorySpace backing, Address start, int size, AddressSet uninitialized); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java new file mode 100644 index 0000000000..eb9d79cd2e --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulator.java @@ -0,0 +1,65 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeThread; +import ghidra.trace.model.Trace; +import ghidra.trace.model.thread.TraceThread; + +/** + * An emulator that can read initial state from a trace and record its state back into it + */ +public class BytesTracePcodeEmulator extends PcodeEmulator implements TracePcodeMachine { + protected final Trace trace; + protected final long snap; + + /** + * Create a trace-bound emulator + * + * @param trace the trace + * @param snap the snap from which it lazily reads its state + */ + public BytesTracePcodeEmulator(Trace trace, long snap) { + super(trace.getBaseLanguage()); + this.trace = trace; + this.snap = snap; + } + + @Override + public Trace getTrace() { + return trace; + } + + @Override + public long getSnap() { + return snap; + } + + protected TracePcodeExecutorState newState(TraceThread thread) { + return new BytesTracePcodeExecutorState(trace, snap, thread, 0); + } + + @Override + public TracePcodeExecutorState createSharedState() { + return newState(null); + } + + @Override + public TracePcodeExecutorState createLocalState(PcodeThread thread) { + return newState(getTraceThread(thread)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.java new file mode 100644 index 0000000000..0c34dab9c3 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorState.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.pcode.exec.trace; + +import ghidra.trace.model.Trace; +import ghidra.trace.model.thread.TraceThread; + +/** + * A state composing a single {@link BytesTracePcodeExecutorStatePiece} + */ +class BytesTracePcodeExecutorState extends DefaultTracePcodeExecutorState { + /** + * Create the state + * + * @param trace the trace from which bytes are loaded + * @param snap the snap from which bytes are loaded + * @param thread if applicable, the thread identifying the register space + * @param frame if applicable, the frame identifying the register space + */ + public BytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) { + super(new BytesTracePcodeExecutorStatePiece(trace, snap, thread, frame)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java similarity index 73% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceCachedWriteBytesPcodeExecutorState.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java index 817cecaeb4..dffeadc342 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesTracePcodeExecutorStatePiece.java @@ -20,9 +20,9 @@ import java.nio.ByteBuffer; import com.google.common.collect.*; import com.google.common.primitives.UnsignedLong; -import ghidra.pcode.exec.AbstractBytesPcodeExecutorState; +import ghidra.pcode.exec.AbstractBytesPcodeExecutorStatePiece; import ghidra.pcode.exec.BytesPcodeExecutorStateSpace; -import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState.CachedSpace; +import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece.CachedSpace; import ghidra.program.model.address.AddressSet; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; @@ -32,22 +32,23 @@ import ghidra.trace.model.thread.TraceThread; import ghidra.util.MathUtilities; /** - * A state which reads bytes from a trace, but caches writes internally. + * A state piece which reads bytes from a trace, but caches writes internally. * *

* This provides for "read-only" emulation on a trace. Writes do not affect the source trace, but * rather are cached in this state. If desired, those cached writes can be written back out at a * later time. */ -public class TraceCachedWriteBytesPcodeExecutorState - extends AbstractBytesPcodeExecutorState { +public class BytesTracePcodeExecutorStatePiece + extends AbstractBytesPcodeExecutorStatePiece + implements TracePcodeExecutorStatePiece { protected final Trace trace; protected final long snap; protected final TraceThread thread; protected final int frame; - public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, + public BytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, int frame) { super(trace.getBaseLanguage()); this.trace = trace; @@ -56,7 +57,7 @@ public class TraceCachedWriteBytesPcodeExecutorState this.frame = frame; } - public static class CachedSpace extends BytesPcodeExecutorStateSpace { + protected static class CachedSpace extends BytesPcodeExecutorStateSpace { protected final RangeSet written = TreeRangeSet.create(); protected final long snap; @@ -126,50 +127,69 @@ public class TraceCachedWriteBytesPcodeExecutorState } } + /** + * Get the state's source trace + * + * @return the trace + */ public Trace getTrace() { return trace; } + /** + * Get the source snap + * + * @return the snap + */ public long getSnap() { return snap; } + /** + * Get the source thread, if a local state + * + * @return the thread + */ public TraceThread getThread() { return thread; } + /** + * Get the source frame, if a local state + * + * @return the frame, probably 0 + */ public int getFrame() { return frame; } - /** - * Write the accumulated writes into the given trace - * - *

- * NOTE: This method requires a transaction to have already been started on the destination - * trace. - * - * @param trace the trace to modify - * @param snap the snap within the trace - * @param thread the thread to take register writes - * @param frame the frame for register writes - */ - public void writeCacheDown(Trace trace, long snap, TraceThread thread, int frame) { + @Override + public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { if (trace.getBaseLanguage() != language) { throw new IllegalArgumentException("Destination trace must be same language as source"); } - for (CachedSpace cached : spaces.values()) { + for (CachedSpace cached : spaceMap.values()) { cached.writeDown(trace, snap, thread, frame); } } - @Override - protected TraceMemorySpace getBacking(AddressSpace space) { - return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false); + /** + * A space map which binds spaces to corresponding spaces in the trace + */ + protected class TraceBackedSpaceMap extends CacheingSpaceMap { + @Override + protected TraceMemorySpace getBacking(AddressSpace space) { + return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false); + } + + @Override + protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { + return new CachedSpace(language, space, backing, snap); + } } @Override - protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { - return new CachedSpace(language, space, backing, snap); + protected AbstractSpaceMap newSpaceMap() { + return new TraceBackedSpaceMap(); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java new file mode 100644 index 0000000000..ee664b416c --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DefaultTracePcodeExecutorState.java @@ -0,0 +1,47 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import ghidra.pcode.exec.DefaultPcodeExecutorState; +import ghidra.trace.model.Trace; +import ghidra.trace.model.thread.TraceThread; + +/** + * An adapter that implements {@link TracePcodeExecutorState} given a + * {@link TracePcodeExecutorStatePiece} whose address and value types already match + * + * @param the type of values + */ +public class DefaultTracePcodeExecutorState extends DefaultPcodeExecutorState + implements TracePcodeExecutorState { + + protected final TracePcodeExecutorStatePiece piece; + + /** + * Wrap a state piece + * + * @param piece the piece + */ + public DefaultTracePcodeExecutorState(TracePcodeExecutorStatePiece piece) { + super(piece); + this.piece = piece; + } + + @Override + public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { + piece.writeDown(trace, snap, thread, frame); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java new file mode 100644 index 0000000000..5fb30d4e50 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorState.java @@ -0,0 +1,68 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.exec.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; + +/** + * A state composing a single {@link DirectBytesTracePcodeExecutorStatePiece} + * + *

+ * Note this does not implement {@link DefaultTracePcodeExecutorState} because it treats the trace + * as if it were a stand-alone state. The interface expects implementations to lazily load into a + * cache and write it back down later. This does not do that. + * + * @see TraceSleighUtils + */ +public class DirectBytesTracePcodeExecutorState extends DefaultPcodeExecutorState { + private final Trace trace; + private final long snap; + private final TraceThread thread; + private final int frame; + + /** + * Create the state + * + * @param trace the trace the executor will access + * @param snap the snap the executor will access + * @param thread the thread for reading and writing registers + * @param frame the frame for reading and writing registers + */ + public DirectBytesTracePcodeExecutorState(Trace trace, long snap, TraceThread thread, + int frame) { + super(new DirectBytesTracePcodeExecutorStatePiece(trace, snap, thread, frame)); + this.trace = trace; + this.snap = snap; + this.thread = thread; + this.frame = frame; + } + + /** + * Pair this state with an auxiliary {@link TraceMemoryState} piece + * + * @return the new state, composing this state with the new piece + * @see TraceSleighUtils#buildByteWithStateExecutor(Trace, long, TraceThread, int) + */ + public PcodeExecutorState> withMemoryState() { + return new PairedPcodeExecutorState<>(this, + new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java similarity index 57% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceBytesPcodeExecutorState.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java index 8ae9af63e8..1dd8aa363e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/DirectBytesTracePcodeExecutorStatePiece.java @@ -17,14 +17,14 @@ package ghidra.pcode.exec.trace; import java.nio.ByteBuffer; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import ghidra.generic.util.datastruct.SemisparseByteArray; import ghidra.pcode.exec.*; -import ghidra.pcode.utils.Utils; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; import ghidra.program.model.mem.MemBuffer; import ghidra.trace.model.Trace; import ghidra.trace.model.memory.TraceMemorySpace; @@ -32,8 +32,20 @@ import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; import ghidra.trace.util.DefaultTraceTimeViewport; -public class TraceBytesPcodeExecutorState - extends AbstractLongOffsetPcodeExecutorState { +/** + * An executor state piece that operates directly on trace memory and registers + * + *

+ * This differs from {@link BytesTracePcodeExecutorStatePiece} in that writes performed by the + * emulator immediately affect the trace. There is no caching. In effect, the trace is the + * state. This is used primarily in testing to initialize trace state using Sleigh, which is more + * succinct than accessing trace memory and registers via the trace API. It may also be incorporated + * into the UI at a later time. + * + * @see TraceSleighUtils + */ +public class DirectBytesTracePcodeExecutorStatePiece + extends AbstractLongOffsetPcodeExecutorStatePiece { protected final SemisparseByteArray unique = new SemisparseByteArray(); private final Trace trace; @@ -43,8 +55,10 @@ public class TraceBytesPcodeExecutorState private final DefaultTraceTimeViewport viewport; - public TraceBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) { - super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage())); + protected DirectBytesTracePcodeExecutorStatePiece(Language language, + PcodeArithmetic arithmetic, Trace trace, long snap, TraceThread thread, + int frame) { + super(language, arithmetic, arithmetic); this.trace = trace; this.snap = snap; this.thread = thread; @@ -54,48 +68,65 @@ public class TraceBytesPcodeExecutorState this.viewport.setSnap(snap); } - public PcodeExecutorState> withMemoryState() { - return new PairedPcodeExecutorState<>(this, - new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)) { - - @Override - public void setVar(AddressSpace space, Pair offset, int size, - boolean truncateAddressableUnit, Pair val) { - if (offset.getRight() == TraceMemoryState.KNOWN) { - super.setVar(space, offset, size, truncateAddressableUnit, val); - return; - } - super.setVar(space, offset, size, truncateAddressableUnit, - new ImmutablePair<>(val.getLeft(), TraceMemoryState.UNKNOWN)); - } - - @Override - public Pair getVar(AddressSpace space, - Pair offset, int size, - boolean truncateAddressableUnit) { - Pair result = - super.getVar(space, offset, size, truncateAddressableUnit); - if (offset.getRight() == TraceMemoryState.KNOWN) { - return result; - } - return new ImmutablePair<>(result.getLeft(), TraceMemoryState.UNKNOWN); - } - }; + protected DirectBytesTracePcodeExecutorStatePiece(Language language, Trace trace, long snap, + TraceThread thread, int frame) { + this(language, BytesPcodeArithmetic.forLanguage(language), trace, snap, thread, frame); } + public DirectBytesTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, + int frame) { + this(trace.getBaseLanguage(), trace, snap, thread, frame); + } + + /** + * Create a state which computes an expression's {@link TraceMemoryState} as an auxiliary + * attribute + * + *

+ * An address can be read so long as it is {@link TraceMemoryState#KNOWN} for any non-scratch snap + * up to and including the given snap. + */ +public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece + extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece { + + public RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap, + TraceThread thread, int frame) { + super(trace, snap, thread, frame); + } + + @Override + protected AddressSetView getKnown(TraceMemorySpace source) { + return source.getAddressesWithState(Range.closed(0L, snap), + s -> s == TraceMemoryState.KNOWN); + } + + @Override + protected AccessPcodeExecutionException excFor(AddressSetView unknown) { + throw new AccessPcodeExecutionException("Memory at " + unknown + " has never been known."); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java index 201fbf989b..9dc7ed770f 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java @@ -15,46 +15,26 @@ */ package ghidra.pcode.exec.trace; -import ghidra.pcode.exec.AccessPcodeExecutionException; -import ghidra.program.model.address.*; import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; -import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; +/** + * A state composing a single {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorState} + */ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState - extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorState { + extends DefaultTracePcodeExecutorState { + /** + * Create the state + * + * @param trace the trace from which to load state + * @param snap the snap from which to load state + * @param thread if applicable, the thread identifying the register space + * @param frame if applicable, the frame identifying the register space + */ public RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) { - super(trace, snap, thread, frame); - } - - protected AddressSetView getKnown(TraceMemorySpace source) { - return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN); - } - - protected AccessPcodeExecutionException excFor(AddressSetView unknown) { - return new AccessPcodeExecutionException("Memory at " + unknown + " is unknown."); - } - - @Override - protected int checkUninitialized(TraceMemorySpace backing, Address start, int size, - AddressSet uninitialized) { - if (backing == null) { - if (!uninitialized.contains(start)) { - return (int) uninitialized.getMinAddress().subtract(start); - } - throw excFor(uninitialized); - } - // TODO: Could find first instead? - AddressSetView unknown = uninitialized.subtract(getKnown(backing)); - if (unknown.isEmpty()) { - return size; - } - if (!unknown.contains(start)) { - return (int) unknown.getMinAddress().subtract(start); - } - throw excFor(unknown); + super(new RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread, + frame)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..3cf120eac4 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -0,0 +1,67 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import ghidra.pcode.exec.AccessPcodeExecutionException; +import ghidra.program.model.address.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; + +/** + * A space which requires reads to be completely {@link TraceMemorySpace#KNOWN} memory. + * + *

+ * If a read can be partially completed, then it will proceed up to but not including the first + * non-known address. If the start address is non-known, the emulator will be interrupted. + */ +public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece + extends AbstractCheckedTraceCachedWriteBytesPcodeExecutorStatePiece { + + public RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece(Trace trace, long snap, + TraceThread thread, int frame) { + super(trace, snap, thread, frame); + } + + protected AddressSetView getKnown(TraceMemorySpace source) { + return source.getAddressesWithState(snap, s -> s == TraceMemoryState.KNOWN); + } + + protected AccessPcodeExecutionException excFor(AddressSetView unknown) { + return new AccessPcodeExecutionException("Memory at " + unknown + " is unknown."); + } + + @Override + protected int checkUninitialized(TraceMemorySpace backing, Address start, int size, + AddressSet uninitialized) { + if (backing == null) { + if (!uninitialized.contains(start)) { + return (int) uninitialized.getMinAddress().subtract(start); + } + throw excFor(uninitialized); + } + // TODO: Could find first instead? + AddressSetView unknown = uninitialized.subtract(getKnown(backing)); + if (unknown.isEmpty()) { + return size; + } + if (!unknown.contains(start)) { + return (int) unknown.getMinAddress().subtract(start); + } + throw excFor(unknown); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeArithmetic.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeArithmetic.java index b86dd8183d..dd90ef2eb5 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeArithmetic.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceMemoryStatePcodeArithmetic.java @@ -17,22 +17,41 @@ package ghidra.pcode.exec.trace; import java.math.BigInteger; +import ghidra.pcode.exec.ConcretionError; import ghidra.pcode.exec.PcodeArithmetic; import ghidra.pcode.opbehavior.BinaryOpBehavior; -import ghidra.pcode.opbehavior.UnaryOpBehavior; +import ghidra.program.model.lang.Endian; import ghidra.trace.model.memory.TraceMemoryState; +/** + * The p-code arithmetic for {@link TraceMemoryState} + * + *

+ * This arithmetic is meant to be used as an auxiliary to a concrete arithmetic. It should be used + * with a state that knows how to load state markings from the same trace as the concrete state, so + * that it can compute the "state" of a Sleigh expression's value. It essentially works like a + * rudimentary taint analyzer: If any part of any input to the expression in tainted, i.e., not + * {@link TraceMemoryState#KNOWN}, then the result is {@link TraceMemoryState#UNKNOWN}. This is best + * exemplified in + * {@link #binaryOp(BinaryOpBehavior, int, int, TraceMemoryState, int, TraceMemoryState)}. + */ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic { + /** The singleton instance */ INSTANCE; @Override - public TraceMemoryState unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, + public Endian getEndian() { + return null; + } + + @Override + public TraceMemoryState unaryOp(int opcode, int sizeout, int sizein1, TraceMemoryState in1) { return in1; } @Override - public TraceMemoryState binaryOp(BinaryOpBehavior op, int sizeout, int sizein1, + public TraceMemoryState binaryOp(int opcode, int sizeout, int sizein1, TraceMemoryState in1, int sizein2, TraceMemoryState in2) { if (in1 == TraceMemoryState.KNOWN && in2 == TraceMemoryState.KNOWN) { return TraceMemoryState.KNOWN; @@ -41,7 +60,22 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic + * This state piece is meant to be used as an auxiliary to a concrete trace-bound state. See + * {@link DirectBytesTracePcodeExecutorState#withMemoryState()}. It should be used with + * {@link TraceMemoryStatePcodeArithmetic} as a means of computing the "state" of a Sleigh + * expression's value. It essentially works like a rudimentary taint analyzer: If any part of any + * input to the expression in tainted, i.e., not {@link TraceMemoryState#KNOWN}, then the result is + * {@link TraceMemoryState#UNKNOWN}. This is best exemplified in {@link #getUnique(long, int)}, + * though it's also exemplified in {@link #getFromSpace(TraceMemorySpace, long, int)}. + */ public class TraceMemoryStatePcodeExecutorStatePiece extends AbstractLongOffsetPcodeExecutorStatePiece { @@ -43,7 +55,9 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends public TraceMemoryStatePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, int frame) { - super(trace.getBaseLanguage(), TraceMemoryStatePcodeArithmetic.INSTANCE); + super(trace.getBaseLanguage(), + BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage()), + TraceMemoryStatePcodeArithmetic.INSTANCE); this.trace = trace; this.snap = snap; this.thread = thread; @@ -99,16 +113,6 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends } } - @Override - protected long offsetToLong(byte[] offset) { - return Utils.bytesToLong(offset, offset.length, language.isBigEndian()); - } - - @Override - public byte[] longToOffset(AddressSpace space, long l) { - return Utils.longToBytes(l, space.getPointerSize(), language.isBigEndian()); - } - @Override protected void setUnique(long offset, int size, TraceMemoryState val) { unique.put(range(offset, size), val); @@ -158,7 +162,7 @@ public class TraceMemoryStatePcodeExecutorStatePiece extends } @Override - public MemBuffer getConcreteBuffer(Address address) { - throw new AssertionError("Cannot make TraceMemoryState into a concrete buffer"); + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + throw new ConcretionError("Cannot make TraceMemoryState into a concrete buffer", purpose); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeEmulator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeEmulator.java deleted file mode 100644 index 48a7e7949f..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeEmulator.java +++ /dev/null @@ -1,101 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.pcode.exec.trace; - -import com.google.common.collect.Range; - -import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcode.emu.PcodeEmulator; -import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.program.model.lang.Language; -import ghidra.trace.model.Trace; -import ghidra.trace.model.stack.TraceStack; -import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.model.thread.TraceThreadManager; - -/** - * An emulator that can read initial state from a trace - */ -public class TracePcodeEmulator extends PcodeEmulator { - private static SleighLanguage assertSleigh(Language language) { - if (!(language instanceof SleighLanguage)) { - throw new IllegalArgumentException("Emulation requires a sleigh language"); - } - return (SleighLanguage) language; - } - - protected final Trace trace; - protected final long snap; - - public TracePcodeEmulator(Trace trace, long snap) { - super(assertSleigh(trace.getBaseLanguage())); - this.trace = trace; - this.snap = snap; - } - - protected PcodeExecutorState newState(TraceThread thread) { - return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0); - } - - @Override - protected PcodeExecutorState createSharedState() { - return newState(null); - } - - @Override - protected PcodeExecutorState createLocalState(PcodeThread emuThread) { - return newState(trace.getThreadManager().getLiveThreadByPath(snap, emuThread.getName())); - } - - /** - * Write the accumulated writes into the given trace at the given snap - * - *

- * NOTE: This method requires a transaction to have already been started on the destination - * trace. The destination threads must have equal names/paths at the given threadsSnap. When - * using scratch space, threadsSnap should be the source snap. If populating a new trace, - * threadsSnap should probably be the destination snap. - * - * @param trace the trace to modify - * @param destSnap the destination snap within the trace - * @param threadsSnap the snap at which to find corresponding threads - * @param synthesizeStacks true to synthesize the innermost stack frame of each thread - */ - public void writeDown(Trace trace, long destSnap, long threadsSnap, boolean synthesizeStacks) { - TraceCachedWriteBytesPcodeExecutorState ss = - (TraceCachedWriteBytesPcodeExecutorState) getSharedState(); - ss.writeCacheDown(trace, destSnap, null, 0); - TraceThreadManager threadManager = trace.getThreadManager(); - for (PcodeThread emuThread : threads.values()) { - TraceCachedWriteBytesPcodeExecutorState ls = - (TraceCachedWriteBytesPcodeExecutorState) emuThread.getState().getLocalState(); - TraceThread traceThread = threadManager.getLiveThreadByPath( - threadsSnap, emuThread.getName()); - if (traceThread == null) { - throw new IllegalArgumentException( - "Given trace does not have thread with name/path '" + emuThread.getName() + - "' at snap " + destSnap); - } - ls.writeCacheDown(trace, destSnap, traceThread, 0); - if (synthesizeStacks) { - TraceStack stack = trace.getStackManager().getStack(traceThread, destSnap, true); - stack.getFrame(0, true) - .setProgramCounter(Range.atLeast(destSnap), emuThread.getCounter()); - } - } - } -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.java new file mode 100644 index 0000000000..c66fecd6c2 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorState.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.pcode.exec.trace; + +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.trace.model.Trace; +import ghidra.trace.model.thread.TraceThread; + +/** + * An interface for trace-bound states + * + *

+ * In particular, because this derives from {@link TracePcodeExecutorStatePiece}, such states are + * required to implement {@link #writeDown(Trace, long, TraceThread, int)}. This interface also + * derives from {@link PcodeExecutorState} so that, as the name implies, they can be used where a + * state is required. + * + * @param the type of values + */ +public interface TracePcodeExecutorState + extends PcodeExecutorState, TracePcodeExecutorStatePiece { + // Nothing to add. Simply a composition of interfaces. +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java new file mode 100644 index 0000000000..67371bf960 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeExecutorStatePiece.java @@ -0,0 +1,43 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import ghidra.pcode.exec.PcodeExecutorStatePiece; +import ghidra.trace.model.Trace; +import ghidra.trace.model.thread.TraceThread; + +/** + * A state piece which knows how to write its values back into a trace + * + * @param the type of address offsets + * @param the type of values + */ +public interface TracePcodeExecutorStatePiece extends PcodeExecutorStatePiece { + /** + * Write the accumulated values (cache) into the given trace + * + *

+ * NOTE: This method requires a transaction to have already been started on the + * destination trace. + * + * @param trace the trace to modify + * @param snap the snap within the trace + * @param thread the thread to take register writes + * @param frame the frame for register writes + * @see TracePcodeMachine#writeDown(Trace, long, long) + */ + void writeDown(Trace trace, long snap, TraceThread thread, int frame); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.java new file mode 100644 index 0000000000..5bcb479529 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TracePcodeMachine.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.pcode.exec.trace; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.PcodeThread; +import ghidra.program.model.lang.Register; +import ghidra.program.model.lang.RegisterValue; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemoryRegisterSpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.thread.TraceThreadManager; + +/** + * A p-code machine which sources its state from a trace and can record back into it + * + *

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

+ * NOTE: This method requires a transaction to have already been started on the + * destination trace. The destination threads must have equal names/paths at the given + * threadsSnap. When using scratch space, threadsSnap should be the source snap. If populating a + * new trace, threadsSnap should probably be the destination snap. + * + * @param trace the trace to modify + * @param destSnap the destination snap within the trace + * @param threadsSnap the snap at which to find corresponding threads, usually the same as + * {@link #getSnap()} + */ + default void writeDown(Trace trace, long destSnap, long threadsSnap) { + TracePcodeExecutorState ss = (TracePcodeExecutorState) getSharedState(); + ss.writeDown(trace, destSnap, null, 0); + TraceThreadManager threadManager = trace.getThreadManager(); + for (PcodeThread emuThread : getAllThreads()) { + TracePcodeExecutorState ls = + (TracePcodeExecutorState) emuThread.getState().getLocalState(); + TraceThread traceThread = + threadManager.getLiveThreadByPath(threadsSnap, emuThread.getName()); + if (traceThread == null) { + throw new IllegalArgumentException( + "Given trace does not have thread with name/path '" + emuThread.getName() + + "' at snap " + destSnap); + } + ls.writeDown(trace, destSnap, traceThread, 0); + } + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java index 7d65f6293d..ef1f3afb62 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/TraceSleighUtils.java @@ -32,9 +32,26 @@ import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; +/** + * Various utilities for using Sleigh with traces + */ public enum TraceSleighUtils { ; + /** + * Get the trace memory space for the given "coordinates" + * + *

+ * This is used to find "backing" objects for a p-code executor state bound to a trace, whether + * direct or cached. + * + * @param space the address space + * @param trace the trace + * @param thread the thread, if a register space + * @param frame the frame, if a register space + * @param toWrite true if the state intends to write to the space, i.e., the space must exist + * @return the space, or null if it doesn't exist + */ public static TraceMemorySpace getSpaceForExecution(AddressSpace space, Trace trace, TraceThread thread, int frame, boolean toWrite) { if (space.isRegisterSpace()) { @@ -47,10 +64,24 @@ public enum TraceSleighUtils { return trace.getMemoryManager().getMemorySpace(space, toWrite); } + /** + * Build a p-code executor that operates directly on bytes of the given trace + * + *

+ * This execute is most suitable for evaluating Sleigh expression on a given trace snapshot, and + * for manipulating or initializing variables using Sleigh code. It is generally not suitable + * for use in an emulator. For that, consider {@link BytesTracePcodeEmulator}. + * + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the executor + */ public static PcodeExecutor buildByteExecutor(Trace trace, long snap, TraceThread thread, int frame) { - TraceBytesPcodeExecutorState state = - new TraceBytesPcodeExecutorState(trace, snap, thread, frame); + DirectBytesTracePcodeExecutorState state = + new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame); Language language = trace.getBaseLanguage(); if (!(language instanceof SleighLanguage)) { throw new IllegalArgumentException("Trace must use a SLEIGH language"); @@ -59,10 +90,24 @@ public enum TraceSleighUtils { BytesPcodeArithmetic.forLanguage(language), state); } + /** + * Build a p-code executor that operates directly on bytes and memory state of the given trace + * + *

+ * This executor is most suitable for evaluating Sleigh expressions on a given trace snapshot, + * when the client would also like to know if all variables involved are + * {@link TraceMemoryState#KNOWN}. + * + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the executor + */ public static PcodeExecutor> buildByteWithStateExecutor( Trace trace, long snap, TraceThread thread, int frame) { - TraceBytesPcodeExecutorState state = - new TraceBytesPcodeExecutorState(trace, snap, thread, frame); + DirectBytesTracePcodeExecutorState state = + new DirectBytesTracePcodeExecutorState(trace, snap, thread, frame); PcodeExecutorState> paired = state.withMemoryState(); Language language = trace.getBaseLanguage(); if (!(language instanceof SleighLanguage)) { @@ -73,6 +118,16 @@ public enum TraceSleighUtils { paired); } + /** + * Evaluate a compiled p-code expression on the given trace + * + * @param expr the expression + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the value of the expression as a byte array + */ public static byte[] evaluateBytes(PcodeExpression expr, Trace trace, long snap, TraceThread thread, int frame) { SleighLanguage language = expr.getLanguage(); @@ -84,6 +139,16 @@ public enum TraceSleighUtils { return expr.evaluate(executor); } + /** + * Evaluate a compiled p-code expression on the given trace + * + * @param expr the expression + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the value of the expression as a big integer + */ public static BigInteger evaluate(PcodeExpression expr, Trace trace, long snap, TraceThread thread, int frame) { byte[] bytes = evaluateBytes(expr, trace, snap, thread, frame); @@ -91,6 +156,16 @@ public enum TraceSleighUtils { false); } + /** + * Evaluate a compiled p-code expression on the given trace + * + * @param expr the expression + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the value and state of the expression + */ public static Pair evaluateBytesWithState(PcodeExpression expr, Trace trace, long snap, TraceThread thread, int frame) { SleighLanguage language = expr.getLanguage(); @@ -104,6 +179,16 @@ public enum TraceSleighUtils { return expr.evaluate(executor); } + /** + * Evaluate a compiled p-code expression on the given trace + * + * @param expr the expression + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the value and state of the expression + */ public static Pair evaluateWithState(PcodeExpression expr, Trace trace, long snap, TraceThread thread, int frame) { Pair bytesPair = @@ -114,6 +199,16 @@ public enum TraceSleighUtils { bytesPair.getRight()); } + /** + * Evaluate a Sleigh expression on the given trace + * + * @param expr the expression + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the value of the expression as a byte array + */ public static byte[] evaluateBytes(String expr, Trace trace, long snap, TraceThread thread, int frame) { Language language = trace.getBaseLanguage(); @@ -125,6 +220,16 @@ public enum TraceSleighUtils { trace, snap, thread, frame); } + /** + * Evaluate a Sleigh expression on the given trace + * + * @param expr the expression + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the value of the expression as a big integer + */ public static BigInteger evaluate(String expr, Trace trace, long snap, TraceThread thread, int frame) { Language language = trace.getBaseLanguage(); @@ -135,6 +240,16 @@ public enum TraceSleighUtils { trace, snap, thread, frame); } + /** + * Evaluate a Sleigh expression on the given trace + * + * @param expr the expression + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the value and state of the expression + */ public static Entry evaluateBytesWithState(String expr, Trace trace, long snap, TraceThread thread, int frame) { Language language = trace.getBaseLanguage(); @@ -146,6 +261,16 @@ public enum TraceSleighUtils { trace, snap, thread, frame); } + /** + * Evaluate a Sleigh expression on the given trace + * + * @param expr the expression + * @param trace the trace + * @param snap the snap + * @param thread the thread, required if register space is used + * @param frame the frame, for when register space is used + * @return the value and state of the expression + */ public static Entry evaluateWithState(String expr, Trace trace, long snap, TraceThread thread, int frame) { Language language = trace.getBaseLanguage(); @@ -157,6 +282,18 @@ public enum TraceSleighUtils { trace, snap, thread, frame); } + /** + * Generate the expression for retrieving a memory range + * + *

+ * In general, it does not make sense to use this directly with the above evaluation methods. + * More likely, this is used in the UI to aid the user in generating an expression. From the + * API, it's much easier to access the memory state directly. + * + * @param language the language + * @param range the range + * @return the expression + */ public static String generateExpressionForRange(Language language, AddressRange range) { AddressSpace space = range.getAddressSpace(); long length = range.getLength(); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java new file mode 100644 index 0000000000..46d0af3ff8 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTraceEmulatorPartsFactory.java @@ -0,0 +1,69 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.auxiliary; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; +import ghidra.pcode.exec.trace.*; + +/** + * An auxiliary emulator parts factory capable of integrating with a trace + * + *

+ * This can manufacture parts for an emulator that reads and writes its state (concrete and + * auxiliary pieces) from and to a trace, as well as all the parts for the less integrated forms of + * the same emulator. The pattern of use is generally to read from a given "source" snap, execute + * some stepping schedule, then write the cache to a given "destination" snap. + * + * @param the type of auxiliary values + */ +public interface AuxTraceEmulatorPartsFactory extends AuxEmulatorPartsFactory { + /** + * Create the shared (memory) state of a new trace-integrated emulator + * + *

+ * This is usually composed of pieces using {@link PairedTracePcodeExecutorStatePiece}, but it + * does not have to be. It must incorporate the concrete piece provided. The state must be + * capable of lazily loading state from a trace and later writing its cache back into the trace + * at another snapshot. The given concrete piece is already capable of doing that for concrete + * values. The auxiliary piece should be able to independently load its state from the trace, + * since this is one way a user expects to initialize the auxiliary values. + * + * @param emulator the emulator + * @param concrete the concrete piece + * @return the composed state + */ + TracePcodeExecutorState> createTraceSharedState( + AuxTracePcodeEmulator emulator, BytesTracePcodeExecutorStatePiece concrete); + + /** + * Create the local (register) state of a new trace-integrated thread + * + *

+ * This must have the same capabilities as + * {@link #createTraceSharedState(AuxTracePcodeEmulator, BytesTracePcodeExecutorStatePiece)}. + * + * @param emulator the emulator + * @param thread the new thread + * @param concrete the concrete piece + * @return the composed state + */ + TracePcodeExecutorState> createTraceLocalState( + AuxTracePcodeEmulator emulator, PcodeThread> thread, + BytesTracePcodeExecutorStatePiece concrete); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java new file mode 100644 index 0000000000..c9ee332266 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/auxiliary/AuxTracePcodeEmulator.java @@ -0,0 +1,89 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace.auxiliary; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; +import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; +import ghidra.pcode.exec.trace.*; +import ghidra.trace.model.Trace; + +/** + * An trace-integrated emulator whose parts are manufactured by a + * {@link AuxTraceEmulatorPartsFactory} + * + *

+ * See the parts factory interface and its super interfaces: + *

+ * + * @param the type of auxiliary values + */ +public abstract class AuxTracePcodeEmulator extends AuxPcodeEmulator + implements TracePcodeMachine> { + + protected final Trace trace; + protected final long snap; + + /** + * Create a new emulator + * + * @param trace the trace from which the emulator loads state + * @param snap the snap from which the emulator loads state + */ + public AuxTracePcodeEmulator(Trace trace, long snap) { + super(trace.getBaseLanguage()); + this.trace = trace; + this.snap = snap; + } + + @Override + protected abstract AuxTraceEmulatorPartsFactory getPartsFactory(); + + @Override + public Trace getTrace() { + return trace; + } + + @Override + public long getSnap() { + return snap; + } + + @Override + protected PcodeThread> createThread(String name) { + PcodeThread> thread = super.createThread(name); + initializeThreadContext(thread); + return thread; + } + + @Override + public TracePcodeExecutorState> createSharedState() { + return getPartsFactory().createTraceSharedState(this, + new BytesTracePcodeExecutorStatePiece(trace, snap, null, 0)); + } + + @Override + public TracePcodeExecutorState> createLocalState( + PcodeThread> thread) { + return getPartsFactory().createTraceLocalState(this, thread, + new BytesTracePcodeExecutorStatePiece(trace, snap, getTraceThread(thread), 0)); + } +} 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 980bcf8cc8..1205306589 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 @@ -37,9 +37,19 @@ import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.Abstract import ghidra.util.database.DBAnnotatedObject; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; +/** + * Various utilities used for implementing the trace database + * + *

+ * Some of these are also useful from the API perspective. TODO: We should probably separate trace + * API utilities into another class. + */ public enum DBTraceUtils { ; + /** + * A tuple used to index/locate a block in the trace's byte stores (memory manager) + */ public static class OffsetSnap { public final long offset; public final long snap; @@ -83,6 +93,9 @@ public enum DBTraceUtils { } // TODO: Should this be in by default? + /** + * A codec or URLs + */ public static class URLDBFieldCodec extends AbstractDBFieldCodec { public URLDBFieldCodec(Class objectType, Field field, int column) { @@ -125,6 +138,9 @@ public enum DBTraceUtils { } } + /** + * A codec for language IDs + */ public static class LanguageIDDBFieldCodec extends AbstractDBFieldCodec { @@ -162,6 +178,9 @@ public enum DBTraceUtils { } } + /** + * A codec for compiler spec IDs + */ public static class CompilerSpecIDDBFieldCodec extends AbstractDBFieldCodec { @@ -199,6 +218,9 @@ public enum DBTraceUtils { } } + /** + * A (abstract) codec for the offset-snap tuple + */ public abstract static class AbstractOffsetSnapDBFieldCodec extends AbstractDBFieldCodec { @@ -248,6 +270,7 @@ public enum DBTraceUtils { /** * Codec for storing {@link OffsetSnap}s as {@link BinaryField}s. * + *

* Encodes the address space ID followed by the address then the snap. * * @param the type of the object whose field is encoded/decoded. @@ -277,6 +300,9 @@ public enum DBTraceUtils { } } + /** + * A codec for reference types + */ public static class RefTypeDBFieldCodec extends AbstractDBFieldCodec { public RefTypeDBFieldCodec(Class objectType, Field field, int column) { @@ -309,27 +335,103 @@ public enum DBTraceUtils { } } + /** + * A method outline for setting an entry in a range map where coalescing is desired + * + * @param the type of entries + * @param the type of range bounds + * @param the type of ranges + * @param the type of values + */ public static abstract class RangeMapSetter, R, V> { + /** + * Get the range of the given entry + * + * @param entry the entry + * @return the range + */ protected abstract R getRange(E entry); + /** + * Get the value of the given entry + * + * @param entry the entry + * @return the value + */ protected abstract V getValue(E entry); + /** + * Remove an entry from the map + * + * @param entry the entry + */ protected abstract void remove(E entry); + /** + * Get the lower bound of the range + * + * @param range the range + * @return the lower bound + */ protected abstract D getLower(R range); + /** + * Get the upper bound of the range + * + * @param range the range + * @return the upper bound + */ protected abstract D getUpper(R range); + /** + * Create a closed range with the given bounds + * + * @param lower the lower bound + * @param upper the upper bound + * @return the range + */ protected abstract R toRange(D lower, D upper); + /** + * Get the number immediately preceding the given bound + * + * @param d the bound + * @return the previous bound, or null if it doesn't exist + */ protected abstract D getPrevious(D d); + /** + * Get the number immediately following the given bound + * + * @param d the bound + * @return the next bound, or null if it doesn't exist + */ protected abstract D getNext(D d); + /** + * Get all entries intersecting the closed range formed by the given bounds + * + * @param lower the lower bound + * @param upper the upper bound + * @return the intersecting entries + */ protected abstract Iterable getIntersecting(D lower, D upper); + /** + * Place an entry into the map + * + * @param range the range of the entry + * @param value the value of the entry + * @return the new entry (or an existing entry) + */ protected abstract E put(R range, V value); + /** + * Get the previous bound or this same bound, if the previous doesn't exist + * + * @param d the bound + * @return the previous or same bound + */ protected D getPreviousOrSame(D d) { D prev = getPrevious(d); if (prev == null) { @@ -338,6 +440,12 @@ public enum DBTraceUtils { return prev; } + /** + * Get the next bound or this same bound, if the next doesn't exist + * + * @param d the bound + * @return the next or same bound + */ protected D getNextOrSame(D d) { D next = getNext(d); if (next == null) { @@ -346,15 +454,40 @@ public enum DBTraceUtils { return next; } + /** + * Check if the two ranges are connected + * + *

+ * The ranges are connected if they intersect, or if their bounds abut. + * + * @param r1 the first range + * @param r2 the second range + * @return true if connected + */ protected boolean connects(R r1, R r2) { return getPreviousOrSame(getLower(r1)).compareTo(getUpper(r2)) <= 0 || getPreviousOrSame(getLower(r2)).compareTo(getUpper(r1)) <= 0; } + /** + * Entry point: Set the given range to the given value, coalescing where possible + * + * @param range the range + * @param value the value + * @return the entry containing the value + */ public E set(R range, V value) { return set(getLower(range), getUpper(range), value); } + /** + * Entry point: Set the given range to the given value, coalescing where possible + * + * @param lower the lower bound + * @param upper the upper bound + * @param value the value + * @return the entry containing the value + */ public E set(D lower, D upper, V value) { // Go one out to find abutting ranges, too. D prev = getPreviousOrSame(lower); @@ -395,6 +528,12 @@ public enum DBTraceUtils { } } + /** + * A setter which works on ranges of addresses + * + * @param the type of entry + * @param the type of value + */ public static abstract class AddressRangeMapSetter extends RangeMapSetter { @Override @@ -423,6 +562,12 @@ public enum DBTraceUtils { } } + /** + * A setter which operates on spans of snapshot keys + * + * @param the type of entry + * @param the type of value + */ public static abstract class LifespanMapSetter extends RangeMapSetter, V> { @@ -458,6 +603,16 @@ public enum DBTraceUtils { } } + /** + * Get the lower endpoint as stored in the database + * + *

+ * {@link Long#MIN_VALUE} represents no lower bound. Endpoints should always be closed unless + * unbounded. If open, it will be converted to closed (at one greater). + * + * @param range the range + * @return the endpoint + */ public static long lowerEndpoint(Range range) { if (!range.hasLowerBound()) { return Long.MIN_VALUE; @@ -468,6 +623,16 @@ public enum DBTraceUtils { return range.lowerEndpoint().longValue() + 1; } + /** + * Get the upper endpoint as stored in the database + * + *

+ * {@link Long#MAX_VALUE} represents no upper bound. Endpoints should alwyas be closed unless + * unbounded. If open, it will be converted to closed (at one less). + * + * @param range the range + * @return the endpoint + */ public static long upperEndpoint(Range range) { if (!range.hasUpperBound()) { return Long.MAX_VALUE; @@ -478,6 +643,13 @@ public enum DBTraceUtils { return range.upperEndpoint().longValue() - 1; } + /** + * Convert the given enpoints to a range + * + * @param lowerEndpoint the lower endpoint, where {@link Long#MIN_VALUE} indicates unbounded + * @param upperEndpoint the upper endpoint, where {@link Long#MAX_VALUE} indicates unbounded + * @return the range + */ public static Range toRange(long lowerEndpoint, long upperEndpoint) { if (lowerEndpoint == Long.MIN_VALUE && upperEndpoint == Long.MAX_VALUE) { return Range.all(); @@ -491,10 +663,27 @@ public enum DBTraceUtils { return Range.closed(lowerEndpoint, upperEndpoint); } + /** + * Create the range starting at the given snap, to infinity + * + * @param snap the starting snap + * @return the range [snap, +inf) + */ public static Range toRange(long snap) { return toRange(snap, Long.MAX_VALUE); } + /** + * Check if the two ranges intersect + * + *

+ * This is a bit obtuse in Guava's API, so here's the convenience method + * + * @param the type of range endpoints + * @param a the first range + * @param b the second range + * @return true if they intersect + */ public static > boolean intersect(Range a, Range b) { // Because we're working with a discrete domain, we have to be careful to never use open // lower bounds. Otherwise, the following two inputs would cause a true return value when, @@ -502,10 +691,45 @@ public enum DBTraceUtils { return a.isConnected(b) && !a.intersection(b).isEmpty(); } + /** + * Check if a given snapshot key is designated as scratch space + * + *

+ * Conventionally, negative snaps are scratch space. + * + * @param snap the snap + * @return true if scratch space + */ public static boolean isScratch(long snap) { return snap < 0; } + /** + * Form a range starting at the given snap that does not traverse both scratch and non-scratch + * space + * + * @param start the starting snap + * @return the range [start,0] if start is in scratch space, or [start, +inf) if start is not in + * scratch space + */ + public static Range atLeastMaybeScratch(long start) { + if (start < 0) { + return Range.closed(start, -1L); + } + return Range.atLeast(start); + } + + /** + * "Compare" two ranges + * + *

+ * This is just to impose a sorting order for display. + * + * @param the type of endpoints + * @param a the first range + * @param b the second range + * @return the result as in {@link Comparable#compareTo(Object)} + */ public static > int compareRanges(Range a, Range b) { int result; if (!a.hasLowerBound() && b.hasLowerBound()) { @@ -548,6 +772,15 @@ public enum DBTraceUtils { return 0; } + /** + * Derive the table name for a given addres/register space + * + * @param baseName the base name of the table group + * @param space the address space + * @param threadKey the thread key, -1 usually indicating "no thread" + * @param frameLevel the frame level + * @return the table name + */ public static String tableName(String baseName, AddressSpace space, long threadKey, int frameLevel) { if (space.isRegisterSpace()) { @@ -560,15 +793,17 @@ public enum DBTraceUtils { } /** - * TODO: Document me + * Truncate or delete an entry to make room * *

- * Only call this method for entries which definitely intersect the given span + * Only call this method for entries which definitely intersect the given span. This does not + * verify intersection. If the data's start snap is contained in the span to clear, the entry is + * deleted. Otherwise, it's end snap is set to one less than the span's start snap. * - * @param data - * @param span - * @param lifespanSetter - * @param deleter + * @param data the entry subject to truncation or deletion + * @param span the span to clear up + * @param lifespanSetter the method used to truncate the entry + * @param deleter the method used to delete the entry */ public static > void makeWay( DR data, Range span, BiConsumer> lifespanSetter, @@ -582,6 +817,13 @@ public enum DBTraceUtils { lifespanSetter.accept(data, toRange(data.getY1(), lowerEndpoint(span) - 1)); } + /** + * Sutract two ranges, yielding 0, 1, or 2 ranges + * + * @param a the first range + * @param b the second range + * @return the list of ranges + */ public static List> subtract(Range a, Range b) { RangeSet set = TreeRangeSet.create(); set.add(a); @@ -592,12 +834,25 @@ public enum DBTraceUtils { .collect(Collectors.toList()); } + /** + * Cast an iterator to a less-specific type, given that it cannot insert elements + * + * @param the desired type + * @param it the iterator of more specific type + * @return the same iterator + */ @SuppressWarnings("unchecked") public static Iterator covariantIterator(Iterator it) { // Iterators only support read and remove, not insert. Safe to cast. return (Iterator) it; } + /** + * Iterate over all the longs contained in a given range + * + * @param span the range + * @return the iterator + */ public static Iterator iterateSpan(Range span) { return new Iterator<>() { final long end = upperEndpoint(span); @@ -617,6 +872,17 @@ public enum DBTraceUtils { }; } + /** + * Get all the addresses in a factory, starting at the given place + * + *

+ * If backward, this yields all addresses coming before start + * + * @param factory the factory + * @param start the start (or end) address + * @param forward true for all after, false for all before + * @return the address set + */ public static AddressSetView getAddressSet(AddressFactory factory, Address start, boolean forward) { AddressSet all = factory.getAddressSet(); @@ -628,6 +894,14 @@ public enum DBTraceUtils { return factory.getAddressSet(min, start); } + /** + * Create an address range, checking the endpoints + * + * @param min the min address, which must be less than or equal to max + * @param max the max address, which must be greater than or equal to min + * @return the range + * @throws IllegalArgumentException if max is less than min + */ public static AddressRange toRange(Address min, Address max) { if (min.compareTo(max) > 0) { throw new IllegalArgumentException("min must precede max"); 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 43fe788b75..1ac277fd00 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 @@ -206,17 +206,16 @@ public class DBTraceDataSettingsAdapter } @Override - protected DBTraceAddressSnapRangePropertyMapSpace createSpace( - AddressSpace space, DBTraceSpaceEntry ent) throws VersionException, IOException { + protected DBTraceDataSettingsSpace createSpace(AddressSpace space, DBTraceSpaceEntry ent) + throws VersionException, IOException { return new DBTraceDataSettingsSpace( tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(), lock, space, dataType, dataFactory); } @Override - protected DBTraceAddressSnapRangePropertyMapRegisterSpace createRegisterSpace( - AddressSpace space, TraceThread thread, DBTraceSpaceEntry ent) - throws VersionException, IOException { + protected DBTraceDataSettingsRegisterSpace createRegisterSpace(AddressSpace space, + TraceThread thread, DBTraceSpaceEntry ent) throws VersionException, IOException { return new DBTraceDataSettingsRegisterSpace( tableName(space, ent.getThreadKey(), ent.getFrameLevel()), trace.getStoreFactory(), lock, space, thread, ent.getFrameLevel(), dataType, 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 8d28ddb2c8..7145aa3485 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 @@ -84,10 +84,10 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter @Override default void setProperty(String name, Class valueClass, T value) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) { - TracePropertySetter setter = - getTrace().getInternalAddressPropertyManager() - .getOrCreatePropertySetter(name, valueClass); - setter.set(getLifespan(), getAddress(), value); + TracePropertyMap map = getTrace().getInternalAddressPropertyManager() + .getOrCreatePropertyMapSuper(name, valueClass); + TracePropertyMapSpace space = map.getPropertyMapSpace(getTraceSpace(), true); + space.set(getLifespan(), getAddress(), value); } } @@ -122,9 +122,18 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter @Override default T getProperty(String name, Class valueClass) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) { - TracePropertyGetter getter = - getTrace().getInternalAddressPropertyManager().getPropertyGetter(name, valueClass); - return getter.get(getStartSnap(), getAddress()); + TracePropertyMap map = + getTrace().getInternalAddressPropertyManager() + .getPropertyMapExtends(name, valueClass); + if (map == null) { + return null; + } + TracePropertyMapSpace space = + map.getPropertyMapSpace(getTraceSpace(), false); + if (space == null) { + return null; + } + return space.get(getStartSnap(), getAddress()); } } @@ -150,7 +159,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter @Override default boolean hasProperty(String name) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) { - TracePropertyMap map = + TracePropertyMapOperations map = getTrace().getInternalAddressPropertyManager().getPropertyMap(name); if (map == null) { return false; @@ -163,13 +172,18 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter @Override default boolean getVoidProperty(String name) { // NOTE: Nearly identical to hasProperty, except named property must be Void type + // NOTE: No need to use Extends. Nothing extends Void. try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) { - TracePropertyGetter getter = - getTrace().getInternalAddressPropertyManager().getPropertyGetter(name, Void.class); - if (getter == null) { + TracePropertyMap map = + getTrace().getInternalAddressPropertyManager().getPropertyMap(name, Void.class); + if (map == null) { return false; } - return getter.getAddressSetView(Range.singleton(getStartSnap())).contains(getAddress()); + TracePropertyMapSpace space = map.getPropertyMapSpace(getTraceSpace(), false); + if (space == null) { + return false; + } + return map.getAddressSetView(Range.singleton(getStartSnap())).contains(getAddress()); } } @@ -184,7 +198,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter @Override default void removeProperty(String name) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().writeLock())) { - TracePropertyMap map = + TracePropertyMapOperations map = getTrace().getInternalAddressPropertyManager().getPropertyMap(name); if (map == null) { return; @@ -196,7 +210,7 @@ public interface DBTraceCodeUnitAdapter extends TraceCodeUnit, MemBufferAdapter @Override default void visitProperty(PropertyVisitor visitor, String propertyName) { try (LockHold hold = LockHold.lock(getTrace().getReadWriteLock().readLock())) { - TracePropertyMap map = + TracePropertyMapOperations map = getTrace().getInternalAddressPropertyManager().getPropertyMap(propertyName); if (map == null) { return; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/AbstractDBTracePropertyMap.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/AbstractDBTracePropertyMap.java index a15dbaae83..bcedac9909 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/AbstractDBTracePropertyMap.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/map/AbstractDBTracePropertyMap.java @@ -18,26 +18,29 @@ package ghidra.trace.database.map; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.util.Collection; import java.util.Map.Entry; import java.util.concurrent.locks.ReadWriteLock; import com.google.common.collect.Range; import db.*; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressRange; +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.map.DBTraceAddressSnapRangePropertyMapTree.AbstractDBTraceAddressSnapRangePropertyMapData; import ghidra.trace.database.map.DBTraceAddressSnapRangePropertyMapTree.TraceAddressSnapRangeQuery; import ghidra.trace.database.thread.DBTraceThreadManager; +import ghidra.trace.model.Trace; import ghidra.trace.model.TraceAddressSnapRange; -import ghidra.trace.model.property.TracePropertyMap; +import ghidra.trace.model.property.*; +import ghidra.trace.model.thread.TraceThread; import ghidra.util.*; import ghidra.util.database.*; import ghidra.util.database.DBCachedObjectStoreFactory.AbstractDBFieldCodec; import ghidra.util.database.annot.*; +import ghidra.util.exception.NotYetImplementedException; import ghidra.util.exception.VersionException; import ghidra.util.task.TaskMonitor; @@ -53,10 +56,12 @@ public abstract class AbstractDBTracePropertyMap entry, Range span) { // TODO: Would rather not rely on implementation knowledge here - // The shape is the database record in AbstracctDBTraceAddressSnapRangePropertyMapData + // The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData makeWay((DR) entry.getKey(), span); } @@ -87,12 +92,21 @@ public abstract class AbstractDBTracePropertyMap span, AddressRange range) { + public Collection> getEntries(Range lifespan, + AddressRange range) { + return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries(); + } + + @Override + public boolean clear(Range span, AddressRange range) { try (LockHold hold = LockHold.lock(lock.writeLock())) { + boolean result = false; for (Entry entry : reduce( TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) { makeWay(entry, span); + result = true; } + return result; } } @@ -107,6 +121,211 @@ public abstract class AbstractDBTracePropertyMap getPropertyMapSpace(AddressSpace space, + boolean createIfAbsent) { + return (DBTracePropertyMapSpace) getForSpace(space, createIfAbsent); + } + + @Override + public TracePropertyMapRegisterSpace getPropertyMapRegisterSpace(TraceThread thread, + int frameLevel, boolean createIfAbsent) { + return (DBTracePropertyMapRegisterSpace) getForRegisterSpace(thread, frameLevel, + createIfAbsent); + } + + @Override + public void delete() { + throw new NotYetImplementedException(); + } + + public class DBTracePropertyMapSpace + extends DBTraceAddressSnapRangePropertyMapSpace + implements TracePropertyMapSpace { + + public DBTracePropertyMapSpace(String tableName, DBCachedObjectStoreFactory storeFactory, + ReadWriteLock lock, AddressSpace space, Class dataType, + DBTraceAddressSnapRangePropertyMapDataFactory dataFactory) + throws VersionException, IOException { + super(tableName, storeFactory, lock, space, dataType, dataFactory); + } + + @Override + public Trace getTrace() { + return trace; + } + + @Override + public Class getValueClass() { + return AbstractDBTracePropertyMap.this.getValueClass(); + } + + @SuppressWarnings("unchecked") + protected void makeWay(Entry entry, Range span) { + // TODO: Would rather not rely on implementation knowledge here + // The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData + makeWay((DR) entry.getKey(), span); + } + + protected void makeWay(DR data, Range span) { + DBTraceUtils.makeWay(data, span, (d, s) -> d.doSetLifespan(s), d -> deleteData(d)); + // TODO: Any events? + } + + @Override + public void set(Range lifespan, Address address, T value) { + put(address, lifespan, value); + } + + @Override + public void set(Range lifespan, AddressRange range, T value) { + put(range, lifespan, value); + } + + @Override + public T get(long snap, Address address) { + return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue(); + } + + @Override + public Entry getEntry(long snap, Address address) { + return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstEntry(); + } + + @Override + public Collection> getEntries(Range lifespan, + AddressRange range) { + return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries(); + } + + @Override + public boolean clear(Range span, AddressRange range) { + try (LockHold hold = LockHold.lock(lock.writeLock())) { + boolean result = false; + for (Entry entry : reduce( + TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) { + makeWay(entry, span); + result = true; + } + return result; + } + } + + @Override + public T put(TraceAddressSnapRange shape, T value) { + try (LockHold hold = LockHold.lock(lock.writeLock())) { + for (Entry entry : reduce( + TraceAddressSnapRangeQuery.intersecting(shape)).entries()) { + makeWay(entry, shape.getLifespan()); + } + return super.put(shape, value); + } + } + } + + public class DBTracePropertyMapRegisterSpace + extends DBTraceAddressSnapRangePropertyMapRegisterSpace + implements TracePropertyMapRegisterSpace { + + public DBTracePropertyMapRegisterSpace(String tableName, + DBCachedObjectStoreFactory storeFactory, ReadWriteLock lock, AddressSpace space, + TraceThread thread, int frameLevel, Class dataType, + DBTraceAddressSnapRangePropertyMapDataFactory dataFactory) + throws VersionException, IOException { + super(tableName, storeFactory, lock, space, thread, frameLevel, dataType, dataFactory); + // TODO Auto-generated constructor stub + } + + @Override + public Trace getTrace() { + return trace; + } + + @Override + public Class getValueClass() { + return AbstractDBTracePropertyMap.this.getValueClass(); + } + + @SuppressWarnings("unchecked") + protected void makeWay(Entry entry, Range span) { + // TODO: Would rather not rely on implementation knowledge here + // The shape is the database record in AbstractDBTraceAddressSnapRangePropertyMapData + makeWay((DR) entry.getKey(), span); + } + + protected void makeWay(DR data, Range span) { + DBTraceUtils.makeWay(data, span, (d, s) -> d.doSetLifespan(s), d -> deleteData(d)); + // TODO: Any events? + } + + @Override + public void set(Range lifespan, Address address, T value) { + put(address, lifespan, value); + } + + @Override + public void set(Range lifespan, AddressRange range, T value) { + put(range, lifespan, value); + } + + @Override + public T get(long snap, Address address) { + return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstValue(); + } + + @Override + public Entry getEntry(long snap, Address address) { + return reduce(TraceAddressSnapRangeQuery.at(address, snap)).firstEntry(); + } + + @Override + public Collection> getEntries(Range lifespan, + AddressRange range) { + return reduce(TraceAddressSnapRangeQuery.intersecting(range, lifespan)).entries(); + } + + @Override + public boolean clear(Range span, AddressRange range) { + try (LockHold hold = LockHold.lock(lock.writeLock())) { + boolean result = false; + for (Entry entry : reduce( + TraceAddressSnapRangeQuery.intersecting(range, span)).entries()) { + makeWay(entry, span); + result = true; + } + return result; + } + } + + @Override + public T put(TraceAddressSnapRange shape, T value) { + try (LockHold hold = LockHold.lock(lock.writeLock())) { + for (Entry entry : reduce( + TraceAddressSnapRangeQuery.intersecting(shape)).entries()) { + makeWay(entry, shape.getLifespan()); + } + return super.put(shape, value); + } + } + } + public static class DBTraceIntPropertyMap extends AbstractDBTracePropertyMap { 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 805d632f24..b52fc5c7f7 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 @@ -47,7 +47,7 @@ import ghidra.trace.model.listing.*; import ghidra.trace.model.memory.TraceMemoryRegion; import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.program.TraceProgramViewListing; -import ghidra.trace.model.property.TracePropertyMap; +import ghidra.trace.model.property.TracePropertyMapOperations; import ghidra.trace.model.symbol.TraceFunctionSymbol; import ghidra.trace.util.*; import ghidra.util.*; @@ -361,7 +361,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV // TODO: Other "special" property types // TODO: Cover this in testing - TracePropertyMap map = + TracePropertyMapOperations map = program.trace.getInternalAddressPropertyManager().getPropertyMap(property); if (map == null) { return new WrappingCodeUnitIterator(Collections.emptyIterator()); @@ -383,7 +383,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV // TODO: Other "special" property types // TODO: Cover this in testing - TracePropertyMap map = + TracePropertyMapOperations map = program.trace.getInternalAddressPropertyManager().getPropertyMap(property); if (map == null) { return new WrappingCodeUnitIterator(Collections.emptyIterator()); @@ -406,7 +406,7 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV // TODO: Other "special" property types // TODO: Cover this in testing - TracePropertyMap map = + TracePropertyMapOperations map = program.trace.getInternalAddressPropertyManager().getPropertyMap(property); if (map == null) { return new WrappingCodeUnitIterator(Collections.emptyIterator()); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewPropertyMapManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewPropertyMapManager.java index 24f6f00339..89373721b1 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewPropertyMapManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/program/DBTraceProgramViewPropertyMapManager.java @@ -17,112 +17,435 @@ package ghidra.trace.database.program; import java.util.Iterator; -import ghidra.program.model.address.Address; +import com.google.common.collect.Range; + +import ghidra.program.model.address.*; import ghidra.program.model.util.*; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.model.property.TracePropertyMap; +import ghidra.util.LockHold; import ghidra.util.Saveable; -import ghidra.util.exception.CancelledException; -import ghidra.util.exception.DuplicateNameException; +import ghidra.util.exception.*; +import ghidra.util.prop.PropertyVisitor; import ghidra.util.task.TaskMonitor; public class DBTraceProgramViewPropertyMapManager implements PropertyMapManager { protected final DBTraceProgramView program; + protected abstract class AbstractDBTraceProgramViewPropertyMap implements PropertyMap { + protected final TracePropertyMap map; + protected final String name; + + public AbstractDBTraceProgramViewPropertyMap(TracePropertyMap map, String name) { + this.map = map; + this.name = name; + } + + @Override + public String getName() { + return name; + } + + protected AddressSetView getAddressSetView() { + return map.getAddressSetView(Range.singleton(program.snap)); + } + + @Override + public boolean intersects(Address start, Address end) { + return getAddressSetView().intersects(start, end); + } + + @Override + public boolean intersects(AddressSetView set) { + return getAddressSetView().intersects(set); + } + + @Override + public boolean removeRange(Address start, Address end) { + return map.clear(Range.singleton(program.snap), new AddressRangeImpl(start, end)); + } + + @Override + public boolean remove(Address addr) { + return removeRange(addr, addr); + } + + @Override + public boolean hasProperty(Address addr) { + return intersects(addr, addr); + } + + @Override + public T getObject(Address addr) { + return map.get(program.snap, addr); + } + + @Override + public Address getNextPropertyAddress(Address addr) { + Address next = addr.next(); + if (next == null) { + return null; + } + AddressRangeIterator it = getAddressSetView().getAddressRanges(next, true); + if (!it.hasNext()) { + return null; + } + AddressRange range = it.next(); + if (!range.contains(next)) { + return next; + } + return range.getMinAddress(); + } + + @Override + public Address getPreviousPropertyAddress(Address addr) { + Address prev = addr.previous(); + if (prev == null) { + return null; + } + AddressRangeIterator it = getAddressSetView().getAddressRanges(prev, false); + if (!it.hasNext()) { + return null; + } + AddressRange range = it.next(); + if (!range.contains(prev)) { + return prev; + } + return range.getMaxAddress(); + } + + @Override + public Address getFirstPropertyAddress() { + return getAddressSetView().getMinAddress(); + } + + @Override + public Address getLastPropertyAddress() { + return getAddressSetView().getMaxAddress(); + } + + @Override + public int getSize() { + return (int) getAddressSetView().getNumAddresses(); + } + + @Override + public AddressIterator getPropertyIterator(Address start, Address end) { + return getPropertyIterator(start, end, true); + } + + @Override + public AddressIterator getPropertyIterator(Address start, Address end, boolean forward) { + return getAddressSetView().intersectRange(start, end).getAddresses(forward); + } + + @Override + public AddressIterator getPropertyIterator() { + return getAddressSetView().getAddresses(true); + } + + @Override + public AddressIterator getPropertyIterator(AddressSetView asv) { + return getPropertyIterator(asv, true); + } + + @Override + public AddressIterator getPropertyIterator(AddressSetView asv, boolean forward) { + return getAddressSetView().intersect(asv).getAddresses(forward); + } + + @Override + public AddressIterator getPropertyIterator(Address start, boolean forward) { + return getAddressSetView().getAddresses(start, forward); + } + + @Override + public void moveRange(Address start, Address end, Address newStart) { + throw new UnsupportedOperationException(); + } + } + + protected class DBTraceProgramViewIntPropertyMap + extends AbstractDBTraceProgramViewPropertyMap implements IntPropertyMap { + + public DBTraceProgramViewIntPropertyMap(TracePropertyMap map, String name) { + super(map, name); + } + + @Override + public void applyValue(PropertyVisitor visitor, Address addr) { + Integer value = getObject(addr); + if (value == null) { + return; + } + visitor.visit(value.intValue()); + } + + @Override + public void add(Address addr, int value) { + map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value); + } + + @Override + public int getInt(Address addr) throws NoValueException { + Integer value = getObject(addr); + if (value == null) { + throw new NoValueException(); + } + return value; + } + } + + protected class DBTraceProgramViewLongPropertyMap + extends AbstractDBTraceProgramViewPropertyMap implements LongPropertyMap { + + public DBTraceProgramViewLongPropertyMap(TracePropertyMap map, String name) { + super(map, name); + } + + @Override + public void applyValue(PropertyVisitor visitor, Address addr) { + Long value = getObject(addr); + if (value == null) { + return; + } + // TODO: In program, this throws NotYetImplemented.... + visitor.visit(value.longValue()); + } + + @Override + public void add(Address addr, long value) { + map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value); + } + + @Override + public long getLong(Address addr) throws NoValueException { + Long value = getObject(addr); + if (value == null) { + throw new NoValueException(); + } + return value; + } + } + + protected class DBTraceProgramViewStringPropertyMap + extends AbstractDBTraceProgramViewPropertyMap implements StringPropertyMap { + + public DBTraceProgramViewStringPropertyMap(TracePropertyMap map, String name) { + super(map, name); + } + + @Override + public void applyValue(PropertyVisitor visitor, Address addr) { + String value = getObject(addr); + visitor.visit(value); + } + + @Override + public void add(Address addr, String value) { + map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, value); + } + + @Override + public String getString(Address addr) { + return getObject(addr); + } + } + + protected class DBTraceProgramViewObjectPropertyMap + extends AbstractDBTraceProgramViewPropertyMap implements ObjectPropertyMap { + + public DBTraceProgramViewObjectPropertyMap(TracePropertyMap map, String name) { + super(map, name); + } + + @Override + public void applyValue(PropertyVisitor visitor, Address addr) { + Saveable value = getObject(addr); + visitor.visit(value); + } + + @Override + public void add(Address addr, Saveable value) { + map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, + map.getValueClass().cast(value)); + } + + @Override + public Class getObjectClass() { + return map.getValueClass(); + } + } + + protected class DBTraceProgramViewVoidPropertyMap + extends AbstractDBTraceProgramViewPropertyMap implements VoidPropertyMap { + + public DBTraceProgramViewVoidPropertyMap(TracePropertyMap map, String name) { + super(map, name); + } + + @Override + public void applyValue(PropertyVisitor visitor, Address addr) { + if (!hasProperty(addr)) { + return; + } + visitor.visit(); + } + + @Override + public void add(Address addr) { + map.set(DBTraceUtils.atLeastMaybeScratch(program.snap), addr, null); + } + } + public DBTraceProgramViewPropertyMapManager(DBTraceProgramView program) { this.program = program; } @Override public IntPropertyMap createIntPropertyMap(String propertyName) throws DuplicateNameException { - // TODO Auto-generated method stub - return null; + return new DBTraceProgramViewIntPropertyMap(program.trace.getAddressPropertyManager() + .createPropertyMap(propertyName, Integer.class), + propertyName); } @Override public LongPropertyMap createLongPropertyMap(String propertyName) throws DuplicateNameException { - // TODO Auto-generated method stub - return null; + return new DBTraceProgramViewLongPropertyMap(program.trace.getAddressPropertyManager() + .createPropertyMap(propertyName, Long.class), + propertyName); } @Override public StringPropertyMap createStringPropertyMap(String propertyName) throws DuplicateNameException { - // TODO Auto-generated method stub - return null; + return new DBTraceProgramViewStringPropertyMap(program.trace.getAddressPropertyManager() + .createPropertyMap(propertyName, String.class), + propertyName); } @Override public ObjectPropertyMap createObjectPropertyMap(String propertyName, Class objectClass) throws DuplicateNameException { - // TODO Auto-generated method stub - return null; + return new DBTraceProgramViewObjectPropertyMap<>(program.trace.getAddressPropertyManager() + .createPropertyMap(propertyName, objectClass), + propertyName); } @Override public VoidPropertyMap createVoidPropertyMap(String propertyName) throws DuplicateNameException { - // TODO Auto-generated method stub - return null; + return new DBTraceProgramViewVoidPropertyMap(program.trace.getAddressPropertyManager() + .createPropertyMap(propertyName, Void.class), + propertyName); } @Override + @SuppressWarnings("unchecked") public PropertyMap getPropertyMap(String propertyName) { - // TODO Auto-generated method stub - return null; + TracePropertyMap map = + program.trace.getAddressPropertyManager().getPropertyMap(propertyName); + if (map == null) { + return null; + } + Class cls = map.getValueClass(); + if (cls == Integer.class) { + return new DBTraceProgramViewIntPropertyMap((TracePropertyMap) map, + propertyName); + } + if (cls == Long.class) { + return new DBTraceProgramViewLongPropertyMap((TracePropertyMap) map, + propertyName); + } + if (cls == String.class) { + return new DBTraceProgramViewStringPropertyMap((TracePropertyMap) map, + propertyName); + } + if (cls == Void.class) { + return new DBTraceProgramViewVoidPropertyMap((TracePropertyMap) map, + propertyName); + } + if (Saveable.class.isAssignableFrom(cls)) { + return new DBTraceProgramViewObjectPropertyMap<>( + (TracePropertyMap) map, propertyName); + } + throw new AssertionError("Where did this property map type come from? " + cls); } @Override public IntPropertyMap getIntPropertyMap(String propertyName) { - // TODO Auto-generated method stub - return null; + TracePropertyMap map = program.trace.getAddressPropertyManager() + .getPropertyMap(propertyName, Integer.class); + return map == null ? null : new DBTraceProgramViewIntPropertyMap(map, propertyName); } @Override public LongPropertyMap getLongPropertyMap(String propertyName) { - // TODO Auto-generated method stub - return null; + TracePropertyMap map = program.trace.getAddressPropertyManager() + .getPropertyMap(propertyName, Long.class); + return map == null ? null : new DBTraceProgramViewLongPropertyMap(map, propertyName); } @Override public StringPropertyMap getStringPropertyMap(String propertyName) { - // TODO Auto-generated method stub - return null; + TracePropertyMap map = program.trace.getAddressPropertyManager() + .getPropertyMap(propertyName, String.class); + return map == null ? null : new DBTraceProgramViewStringPropertyMap(map, propertyName); } @Override + @SuppressWarnings("unchecked") public ObjectPropertyMap getObjectPropertyMap(String propertyName) { - // TODO Auto-generated method stub - return null; + TracePropertyMap map = + program.trace.getAddressPropertyManager().getPropertyMap(propertyName); + if (map == null) { + return null; + } + if (!Saveable.class.isAssignableFrom(map.getValueClass())) { + throw new TypeMismatchException("Property " + propertyName + " is not object type"); + } + return new DBTraceProgramViewObjectPropertyMap<>((TracePropertyMap) map, + propertyName); } @Override public VoidPropertyMap getVoidPropertyMap(String propertyName) { - // TODO Auto-generated method stub - return null; + TracePropertyMap map = program.trace.getAddressPropertyManager() + .getPropertyMap(propertyName, Void.class); + return map == null ? null : new DBTraceProgramViewVoidPropertyMap(map, propertyName); } @Override public boolean removePropertyMap(String propertyName) { - // TODO Auto-generated method stub - return false; + // It would delete for entire trace, not just this view + throw new UnsupportedOperationException(); } @Override public Iterator propertyManagers() { - // TODO Auto-generated method stub - return null; + return program.trace.getAddressPropertyManager().getAllProperties().keySet().iterator(); + } + + protected void removeAll(Range span, AddressRange range) { + try (LockHold hold = program.trace.lockWrite()) { + for (TracePropertyMap map : program.trace.getAddressPropertyManager() + .getAllProperties() + .values()) { + map.clear(span, range); + } + } } @Override public void removeAll(Address addr) { - // TODO Auto-generated method stub - + removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap), new AddressRangeImpl(addr, addr)); } @Override public void removeAll(Address startAddr, Address endAddr, TaskMonitor monitor) throws CancelledException { - // TODO Auto-generated method stub - + removeAll(DBTraceUtils.atLeastMaybeScratch(program.snap), + new AddressRangeImpl(startAddr, endAddr)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/property/DBTraceAddressPropertyManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/property/DBTraceAddressPropertyManager.java index 284e8cced8..fcfb4d734e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/property/DBTraceAddressPropertyManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/property/DBTraceAddressPropertyManager.java @@ -30,7 +30,8 @@ import ghidra.trace.database.DBTraceManager; import ghidra.trace.database.map.AbstractDBTracePropertyMap; import ghidra.trace.database.map.AbstractDBTracePropertyMap.*; import ghidra.trace.database.thread.DBTraceThreadManager; -import ghidra.trace.model.property.*; +import ghidra.trace.model.property.TraceAddressPropertyManager; +import ghidra.trace.model.property.TracePropertyMap; import ghidra.util.*; import ghidra.util.database.*; import ghidra.util.database.annot.*; @@ -197,6 +198,23 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage } } + @Override + @SuppressWarnings("unchecked") + public TracePropertyMap getPropertyMapExtends(String name, + Class valueClass) { + try (LockHold hold = LockHold.lock(lock.readLock())) { + AbstractDBTracePropertyMap map = propertyMapsByName.get(name); + if (map == null) { + return null; + } + if (!valueClass.isAssignableFrom(map.getValueClass())) { + throw new TypeMismatchException("Property " + name + " has type " + + map.getValueClass() + ", which does not extend " + valueClass); + } + return (TracePropertyMap) map; + } + } + @Override public AbstractDBTracePropertyMap getOrCreatePropertyMap(String name, Class valueClass) { @@ -216,23 +234,7 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage @Override @SuppressWarnings("unchecked") - public TracePropertyGetter getPropertyGetter(String name, Class valueClass) { - try (LockHold hold = LockHold.lock(lock.readLock())) { - AbstractDBTracePropertyMap map = propertyMapsByName.get(name); - if (map == null) { - return null; - } - if (!valueClass.isAssignableFrom(map.getValueClass())) { - throw new TypeMismatchException("Property " + name + " has type " + - map.getValueClass() + ", which does not extend " + valueClass); - } - return (TracePropertyGetter) map; - } - } - - @Override - @SuppressWarnings("unchecked") - public TracePropertySetter getOrCreatePropertySetter(String name, + public TracePropertyMap getOrCreatePropertyMapSuper(String name, Class valueClass) { try (LockHold hold = LockHold.lock(lock.writeLock())) { AbstractDBTracePropertyMap map = propertyMapsByName.get(name); @@ -248,7 +250,7 @@ public class DBTraceAddressPropertyManager implements TraceAddressPropertyManage throw new TypeMismatchException("Property " + name + " has type " + map.getValueClass() + ", which is not a super-type of " + valueClass); } - return (TracePropertyMap) map; + return (TracePropertyMap) map; } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/property/DBTraceAddressPropertyManagerApiView.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/property/DBTraceAddressPropertyManagerApiView.java index 3a3b8455fe..ece618ceea 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/property/DBTraceAddressPropertyManagerApiView.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/property/DBTraceAddressPropertyManagerApiView.java @@ -19,7 +19,8 @@ import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; -import ghidra.trace.model.property.*; +import ghidra.trace.model.property.TraceAddressPropertyManager; +import ghidra.trace.model.property.TracePropertyMap; import ghidra.util.exception.DuplicateNameException; class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManager { @@ -43,20 +44,21 @@ class DBTraceAddressPropertyManagerApiView implements TraceAddressPropertyManage } @Override - public TracePropertyMap getOrCreatePropertyMap(String name, Class valueClass) { + public TracePropertyMap getPropertyMapExtends(String name, + Class valueClass) { + return internalView.getPropertyMapExtends(API_PREFIX + name, valueClass); + } + + @Override + public TracePropertyMap getOrCreatePropertyMap(String name, + Class valueClass) { return internalView.getOrCreatePropertyMap(API_PREFIX + name, valueClass); } @Override - public TracePropertyGetter getPropertyGetter(String name, + public TracePropertyMap getOrCreatePropertyMapSuper(String name, Class valueClass) { - return internalView.getPropertyGetter(API_PREFIX + name, valueClass); - } - - @Override - public TracePropertySetter getOrCreatePropertySetter(String name, - Class valueClass) { - return internalView.getOrCreatePropertySetter(API_PREFIX + name, valueClass); + return internalView.getOrCreatePropertyMapSuper(API_PREFIX + name, valueClass); } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TraceAddressPropertyManager.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TraceAddressPropertyManager.java index c0bb2107d2..0769e566b6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TraceAddressPropertyManager.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TraceAddressPropertyManager.java @@ -21,6 +21,13 @@ import ghidra.program.model.util.TypeMismatchException; import ghidra.util.Saveable; import ghidra.util.exception.DuplicateNameException; +/** + * The manager for user properties of a trace + * + *

+ * Clients may create property maps of various value types. Each map is named, also considered the + * "property name," and can be retrieve by that name. + */ public interface TraceAddressPropertyManager { /** * Create a property map with the given name having the given type @@ -59,6 +66,16 @@ public interface TraceAddressPropertyManager { */ TracePropertyMap getPropertyMap(String name, Class valueClass); + /** + * Get the property map with the given name, if its values extend the given type + * + * @param name the name + * @param valueClass the expected type of values + * @return the property map, or null if it does not exist + * @throws TypeMismatchException if it exists but does not have the expected type + */ + TracePropertyMap getPropertyMapExtends(String name, Class valueClass); + /** * Get the property map with the given name, creating it if necessary, of the given type * @@ -70,23 +87,17 @@ public interface TraceAddressPropertyManager { TracePropertyMap getOrCreatePropertyMap(String name, Class valueClass); /** - * Get the property map with the given name, if its type extends the given type + * Get the property map with the given name, creating it if necessary, of the given type * + *

+ * If the map already exists, then its values' type must be a super type of that given. + * + * @see #getOrCreatePropertyMap(String, Class) * @param name the name - * @param valueClass the expected type of values to get - * @return the property map suitable for getting values of the given type + * @param valueClass the expected type of values + * @return the (possibly new) property map */ - TracePropertyGetter getPropertyGetter(String name, Class valueClass); - - /** - * Get the property map with the given name, if its type is a super-type of the given type - * - * @see #createPropertyMap(String, Class) - * @param name the name - * @param valueClass the expected type of values to set - * @return the property map suitable for setting values of the given type - */ - TracePropertySetter getOrCreatePropertySetter(String name, Class valueClass); + TracePropertyMap getOrCreatePropertyMapSuper(String name, Class valueClass); /** * Get the property map with the given name. @@ -94,8 +105,8 @@ public interface TraceAddressPropertyManager { *

* Note that no type checking is performed (there is no {@code valueClass} parameter). Thus, the * returned map is suitable only for clearing and querying where the property is present. The - * caller may perform run-time type checking via the {@link TracePropertyMap#getValueClass()} - * method. + * caller may perform run-time type checking via the + * {@link TracePropertyMapOperations#getValueClass()} method. * * @param name the name * @return the property map diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyGetter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyGetter.java deleted file mode 100644 index bc1edcfd74..0000000000 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyGetter.java +++ /dev/null @@ -1,47 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.trace.model.property; - -import com.google.common.collect.Range; - -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSetView; - -public interface TracePropertyGetter { - /** - * Get the class for values of the map - * - * @return the value class - */ - Class getValueClass(); - - /** - * Get the value at the given address-snap pair - * - * @param snap the snap - * @param address the address - * @return the value - */ - T get(long snap, Address address); - - /** - * Get the union of address ranges for entries which intersect the given span - * - * @param span the range of snaps - * @return the address set - */ - AddressSetView getAddressSetView(Range span); -} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMap.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMap.java index 341500f04b..799e457314 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMap.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMap.java @@ -15,34 +15,82 @@ */ package ghidra.trace.model.property; -import java.util.Map; - -import ghidra.program.model.address.Address; -import ghidra.trace.model.Trace; -import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.program.model.address.AddressSpace; +import ghidra.trace.model.stack.TraceStackFrame; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceAddressSpace; /** - * A map from address-snap pairs to user-defined values in a {@link Trace} + * A range map for storing properties in a trace + * + *

+ * Technically, each range is actually a "box" in two dimensions: time and space. Time is + * represented by the span of snapshots covered, and space is represented by the range of addresses + * covered. Currently, no effort is made to optimize coverage for entries having the same value. For + * operations on entries, see {@link TracePropertyMapOperations}. + * + *

+ * This interface is the root of a multi-space property map. For memory spaces, clients can + * generally use the operations inherited on this interface. For register spaces, clients must use + * {@link #getPropertyMapRegisterSpace(TraceThread, int, boolean)} or similar. + * + * @param the type of values */ -public interface TracePropertyMap extends TracePropertySetter, TracePropertyGetter { +public interface TracePropertyMap extends TracePropertyMapOperations { /** - * Get the class for values of the map + * Get the map space for the given address space * - * @return the value class + * @param space the address space + * @param createIfAbsent true to create the map space if it doesn't already exist + * @return the space, or null */ - @Override - Class getValueClass(); + TracePropertyMapSpace getPropertyMapSpace(AddressSpace space, boolean createIfAbsent); /** - * Get the entry at the given address-snap pair + * Get the map space for the registers of a given thread and frame + * + * @param thread the thread + * @param frameLevel the frame level, 0 being the innermost + * @param createIfAbsent true to create the map space if it doesn't already exist + * @return the space, or null + */ + TracePropertyMapRegisterSpace getPropertyMapRegisterSpace(TraceThread thread, int frameLevel, + boolean createIfAbsent); + + /** + * Get the map space for the registers of a given frame (which knows its thread) + * + * @param frame the frame + * @param createIfAbsent true to create the map space if it doesn't already exist + * @return the space, or null + */ + default TracePropertyMapRegisterSpace getPropertyMapRegisterSpace(TraceStackFrame frame, + boolean createIfAbsent) { + return getPropertyMapRegisterSpace(frame.getStack().getThread(), frame.getLevel(), + createIfAbsent); + } + + /** + * Get the map space for the given trace space + * + * @param traceSpace the trace space, giving the memory space or thread/frame register space + * @param createIfAbsent true to create the map space if it doesn't already exist + * @return the space, or null + */ + default TracePropertyMapSpace getPropertyMapSpace(TraceAddressSpace traceSpace, + boolean createIfAbsent) { + if (traceSpace.getAddressSpace().isRegisterSpace()) { + return getPropertyMapRegisterSpace(traceSpace.getThread(), traceSpace.getFrameLevel(), + createIfAbsent); + } + return getPropertyMapSpace(traceSpace.getAddressSpace(), createIfAbsent); + } + + /** + * Delete this property and remove all of its maps * *

- * Because there exists {@link Map.Entry#setValue(Object)}, this method cannot be in - * {@link TracePropertyGetter}. - * - * @param snap the snap - * @param address the address - * @return the entry, which includes the ranges and the value + * The property can be re-created with the same or different value type. */ - Map.Entry getEntry(long snap, Address address); + void delete(); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertySetter.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapOperations.java similarity index 57% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertySetter.java rename to Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapOperations.java index 622711b4bd..3a2c90eb72 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertySetter.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapOperations.java @@ -15,18 +15,25 @@ */ package ghidra.trace.model.property; +import java.util.Collection; +import java.util.Map; + import com.google.common.collect.Range; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressRange; +import ghidra.program.model.address.*; +import ghidra.trace.model.Trace; +import ghidra.trace.model.TraceAddressSnapRange; -public interface TracePropertySetter { +/** + * A map from address-snap pairs to user-defined values in a {@link Trace} + */ +public interface TracePropertyMapOperations { /** * Get the class for values of the map * * @return the value class */ - Class getValueClass(); + Class getValueClass(); /** * Set a value at the given address over the given lifespan @@ -59,7 +66,47 @@ public interface TracePropertySetter { void set(Range lifespan, AddressRange range, T value); /** - * Remove or truncate entries so that the given span and range has no values + * Get the value at the given address-snap pair + * + * @param snap the snap + * @param address the address + * @return the value + */ + T get(long snap, Address address); + + /** + * Get the entry at the given address-snap pair + * + *

+ * Because there exists {@link Map.Entry#setValue(Object)}, this method cannot be in + * {@link TracePropertyGetter}. + * + * @param snap the snap + * @param address the address + * @return the entry, which includes the ranges and the value + */ + Map.Entry getEntry(long snap, Address address); + + /** + * Get the entries intersecting the given bounds + * + * @param lifespan the range of snaps + * @param range the range of addresses + * @return the entries + */ + Collection> getEntries(Range lifespan, + AddressRange range); + + /** + * Get the union of address ranges for entries which intersect the given span + * + * @param span the range of snaps + * @return the address set + */ + AddressSetView getAddressSetView(Range span); + + /** + * Remove or truncate entries so that the given box contains no entries * *

* This applies the same truncation rule as in {@link #set(Range, AddressRange, Object)}, except @@ -67,6 +114,7 @@ public interface TracePropertySetter { * * @param span the range of snaps * @param range the address range + * @return true if any entry was affected */ - void clear(Range span, AddressRange range); + boolean clear(Range span, AddressRange range); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapRegisterSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapRegisterSpace.java new file mode 100644 index 0000000000..a4e2f6962d --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapRegisterSpace.java @@ -0,0 +1,84 @@ +/* ### + * 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.property; + +import java.util.Collection; +import java.util.Map; + +import com.google.common.collect.Range; + +import ghidra.program.model.lang.Register; +import ghidra.trace.model.TraceAddressSnapRange; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.util.TraceRegisterUtils; + +/** + * A property map space for a thread and frame + * + *

+ * Aside from providing the bound thread and frame, this also adds conveniences for setting and + * getting properties on {@link Register}s. + * + * @param the type of values + */ +public interface TracePropertyMapRegisterSpace extends TracePropertyMapSpace { + /** + * Get the thread for this space + * + * @return the thread + */ + TraceThread getThread(); + + /** + * Get the frame level for this space + * + * @return the frame level, 0 being the innermost + */ + int getFrameLevel(); + + /** + * Set a property on the given register for the given lifespan + * + * @param lifespan the range of snaps + * @param register the register + * @param value the value to set + */ + default void set(Range lifespan, Register register, T value) { + set(lifespan, TraceRegisterUtils.rangeForRegister(register), value); + } + + /** + * Get all entries intersecting the given register and lifespan + * + * @param lifespan the range of snaps + * @param register the register + * @return the entries + */ + default Collection> getEntries(Range lifespan, + Register register) { + return getEntries(lifespan, TraceRegisterUtils.rangeForRegister(register)); + } + + /** + * Remove or truncate entries so that the given box (register and lifespan) contains no entries + * + * @param span the range of snaps + * @param register the register + */ + default void clear(Range span, Register register) { + clear(span, TraceRegisterUtils.rangeForRegister(register)); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapSpace.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapSpace.java new file mode 100644 index 0000000000..4ad889af98 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/property/TracePropertyMapSpace.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.property; + +import ghidra.program.model.address.AddressSpace; +import ghidra.trace.model.Trace; + +/** + * A property map space for a memory space + * + *

+ * Note this interface is inherited by {@link TracePropertyMapRegisterSpace}, so "memory space" can + * also mean the {@code register} space. + * + * @param the type of values + */ +public interface TracePropertyMapSpace extends TracePropertyMapOperations { + /** + * Get the trace + * + * @return the trace + */ + Trace getTrace(); + + /** + * Get the address space for this space + * + *

+ * If this is the {@code register} space, then {@link TracePropertyMapRegisterSpace#getThread()} + * and {@link TracePropertyMapRegisterSpace#getFrameLevel()} are necessary to uniquely identify + * this space. + * + * @return the address space + */ + AddressSpace getAddressSpace(); +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java new file mode 100644 index 0000000000..ca921f02a8 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/AbstractTracePcodeEmulatorTest.java @@ -0,0 +1,92 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import java.util.*; + +import com.google.common.collect.Range; + +import ghidra.app.plugin.assembler.Assembler; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.exec.*; +import ghidra.program.model.address.AddressRange; +import ghidra.program.model.listing.Instruction; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.model.memory.TraceMemoryFlag; +import ghidra.trace.model.memory.TraceMemoryManager; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.Msg; +import ghidra.util.database.UndoableTransaction; + +public class AbstractTracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTest { + + public TraceThread initTrace(ToyDBTraceBuilder tb, List stateInit, + List assembly) throws Throwable { + return initTrace(tb, tb.range(0x00400000, 0x0040ffff), tb.range(0x00100000, 0x0010ffff), + stateInit, assembly); + } + + /** + * Build a trace with a program ready for emulation + * + *

+ * This creates a relatively bare-bones trace with initial state for testing trace + * emulation/interpolation. It adds ".text" and "stack" regions, creates a thread, assembles + * given instructions, and then executes the given SLEIGH source (in the context of the new + * thread) to finish initializing the trace. Note, though given first, the SLEIGH is executed + * after assembly. Thus, it can be used to modify the resulting machine code by modifying the + * memory where it was assembled. + * + * @param tb the trace builder + * @param stateInit SLEIGH source lines to execute to initialize the trace state before + * emulation. Each line must end with ";" + * @param assembly lines of assembly to place starting at {@code 0x00400000} + * @return a new trace thread, whose register state is initialized as specified + * @throws Throwable if anything goes wrong + */ + public TraceThread initTrace(ToyDBTraceBuilder tb, AddressRange text, AddressRange stack, + List stateInit, List assembly) throws Throwable { + TraceMemoryManager mm = tb.trace.getMemoryManager(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + thread = tb.getOrAddThread("Thread1", 0); + mm.addRegion("Regions[bin:.text]", Range.atLeast(0L), text, + TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); + mm.addRegion("Regions[stack1]", Range.atLeast(0L), stack, + TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); + Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); + Iterator block = assembly.isEmpty() ? Collections.emptyIterator() + : asm.assemble(text.getMinAddress(), assembly.toArray(String[]::new)); + Instruction last = null; + while (block.hasNext()) { + last = block.next(); + } + Msg.info(this, "Assembly ended at: " + (last == null ? "null" : last.getMaxAddress())); + if (!stateInit.isEmpty()) { + PcodeExecutor exec = + TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0); + PcodeProgram initProg = SleighProgramCompiler.compileProgram( + (SleighLanguage) tb.language, "test", stateInit, + PcodeUseropLibrary.nil()); + exec.execute(initProg, PcodeUseropLibrary.nil()); + } + } + return thread; + } + +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TracePcodeEmulatorTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java similarity index 83% rename from Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TracePcodeEmulatorTest.java rename to Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java index 8405873dbd..5e75c61640 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TracePcodeEmulatorTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/BytesTracePcodeEmulatorTest.java @@ -21,7 +21,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.math.BigInteger; import java.nio.ByteBuffer; -import java.util.*; +import java.util.List; import org.junit.Test; @@ -30,75 +30,18 @@ import com.google.common.collect.Range; import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; -import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.exec.*; -import ghidra.program.model.address.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressRangeImpl; import ghidra.program.model.lang.*; -import ghidra.program.model.listing.Instruction; -import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.trace.database.ToyDBTraceBuilder; import ghidra.trace.database.context.DBTraceRegisterContextManager; -import ghidra.trace.model.memory.TraceMemoryFlag; -import ghidra.trace.model.memory.TraceMemoryManager; import ghidra.trace.model.thread.TraceThread; -import ghidra.util.Msg; import ghidra.util.NumericUtilities; import ghidra.util.database.UndoableTransaction; -public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTest { - - public TraceThread initTrace(ToyDBTraceBuilder tb, List stateInit, - List assembly) throws Throwable { - return initTrace(tb, tb.range(0x00400000, 0x0040ffff), tb.range(0x00100000, 0x0010ffff), - stateInit, assembly); - } - - /** - * Build a trace with a program ready for emulation - * - *

- * This creates a relatively bare-bones trace with initial state for testing trace - * emulation/interpolation. It adds ".text" and "stack" regions, creates a thread, assembles - * given instructions, and then executes the given SLEIGH source (in the context of the new - * thread) to finish initializing the trace. Note, though given first, the SLEIGH is executed - * after assembly. Thus, it can be used to modify the resulting machine code by modifying the - * memory where it was assembled. - * - * @param tb the trace builder - * @param stateInit SLEIGH source lines to execute to initialize the trace state before - * emulation. Each line must end with ";" - * @param assembly lines of assembly to place starting at {@code 0x00400000} - * @return a new trace thread, whose register state is initialized as specified - * @throws Throwable if anything goes wrong - */ - public TraceThread initTrace(ToyDBTraceBuilder tb, AddressRange text, AddressRange stack, - List stateInit, List assembly) throws Throwable { - TraceMemoryManager mm = tb.trace.getMemoryManager(); - TraceThread thread; - try (UndoableTransaction tid = tb.startTransaction()) { - thread = tb.getOrAddThread("Thread1", 0); - mm.addRegion("Regions[bin:.text]", Range.atLeast(0L), text, - TraceMemoryFlag.READ, TraceMemoryFlag.EXECUTE); - mm.addRegion("Regions[stack1]", Range.atLeast(0L), stack, - TraceMemoryFlag.READ, TraceMemoryFlag.WRITE); - Assembler asm = Assemblers.getAssembler(tb.trace.getFixedProgramView(0)); - Iterator block = assembly.isEmpty() ? Collections.emptyIterator() - : asm.assemble(text.getMinAddress(), assembly.toArray(String[]::new)); - Instruction last = null; - while (block.hasNext()) { - last = block.next(); - } - Msg.info(this, "Assembly ended at: " + last.getMaxAddress()); - PcodeExecutor exec = - TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0); - PcodeProgram initProg = SleighProgramCompiler.compileProgram( - (SleighLanguage) tb.language, "test", stateInit, - PcodeUseropLibrary.nil()); - exec.execute(initProg, PcodeUseropLibrary.nil()); - } - return thread; - } +public class BytesTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest { /** * Test a single instruction @@ -118,7 +61,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "PUSH 0xdeadbeef")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); @@ -130,7 +73,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes TraceSleighUtils.evaluate("*:4 0x0010fffc:8", tb.trace, 0, thread, 0)); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, true); + emu.writeDown(tb.trace, 1, 1); } // 4, not 8 bytes pushed? @@ -138,12 +81,6 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes TraceSleighUtils.evaluate("RSP", tb.trace, 1, thread, 0)); assertEquals(BigInteger.valueOf(0xdeadbeefL), TraceSleighUtils.evaluate("*:4 RSP", tb.trace, 1, thread, 0)); - - assertEquals(tb.addr(0x00400006), - tb.trace.getStackManager() - .getStack(thread, 1, false) - .getFrame(0, false) - .getProgramCounter(1)); } } @@ -165,14 +102,14 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x0010fff8), @@ -206,7 +143,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "MOV EAX,0xdeadbeef", // 5 bytes "MOV ECX,0xbaadf00d")); // 5 bytes - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); @@ -218,7 +155,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -267,7 +204,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes asm.patchProgram(mov, tb.addr(0x00401000)); } - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); @@ -284,7 +221,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -311,13 +248,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "imm r0, #1234")); // decimal - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -345,14 +282,14 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "imm r0, #2020", "imm r1, #2021")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); // brds and 1st imm executed emuThread.stepInstruction(); // 3rd imm executed try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x00400008), @@ -389,7 +326,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "XOR byte ptr [0x00400007], 0xcc", // 7 bytes "MOV EAX,0xdeadbeef")); // 5 bytes - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); @@ -397,7 +334,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x00110000), @@ -427,7 +364,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); assertNull(emuThread.getFrame()); @@ -451,7 +388,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes assertNull(emuThread.getFrame()); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x0040000c), @@ -495,7 +432,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { @Override protected PcodeUseropLibrary createUseropLibrary() { return hexLib; @@ -543,7 +480,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { @Override protected PcodeUseropLibrary createUseropLibrary() { return hexLib; @@ -576,7 +513,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes dumped.delete(0, dumped.length()); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0xbaadf00dL), @@ -599,7 +536,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); emu.addBreakpoint(tb.addr(0x00400000), "RAX == 1"); emu.addBreakpoint(tb.addr(0x00400006), "RAX == 0"); PcodeThread emuThread = emu.newThread(thread.getPath()); @@ -632,13 +569,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "clz r1, r0")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(16), @@ -666,7 +603,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "MOVAPS XMM0, xmmword ptr [0x00600000]")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); @@ -676,7 +613,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes emuThread.getState().getVar(pc)); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(new BigInteger("0123456789abcdeffedcba9876543210", 16), @@ -704,7 +641,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "SAR EAX, CL")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); @@ -714,7 +651,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes emuThread.getState().getVar(pc)); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x7ffffff), @@ -734,14 +671,14 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "XOR AH, AH", "MOV RCX, RAX")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x12340078), @@ -758,9 +695,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "MOV RCX,RAX")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { @Override - protected PcodeExecutorState newState(TraceThread thread) { + protected TracePcodeExecutorState newState(TraceThread thread) { return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0); } @@ -781,9 +718,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "MOV RCX,RAX")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { @Override - protected PcodeExecutorState newState(TraceThread thread) { + protected TracePcodeExecutorState newState(TraceThread thread) { return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0); } @@ -806,9 +743,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "MOV RCX,RAX")); // Start emulator one snap later - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 1) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 1) { @Override - protected PcodeExecutorState newState(TraceThread thread) { + protected TracePcodeExecutorState newState(TraceThread thread) { return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0); } @@ -831,9 +768,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "MOV RCX,RAX")); // Start emulator one snap later, but with "has-known" checks - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 1) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 1) { @Override - protected PcodeExecutorState newState(TraceThread thread) { + protected TracePcodeExecutorState newState(TraceThread thread) { return new RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0); } @@ -855,9 +792,9 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "MOV RAX,0", // Have the program initialize it "MOV RCX,RAX")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0) { @Override - protected PcodeExecutorState newState(TraceThread thread) { + protected TracePcodeExecutorState newState(TraceThread thread) { return new RequireIsKnownTraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 0); } @@ -896,7 +833,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes tb.trace.getMemoryManager().getBytes(0, tb.addr(0x00400000), buf); assertArrayEquals(tb.arr(0x48, 0x89, 0xc1), buf.array()); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); // TODO: Seems the Trace-bound thread ought to know to do this in reInitialize() ctxVal = ctxManager.getValueWithDefault(lang, ctxReg, 0, tb.addr(0x00400000)); @@ -905,7 +842,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x00400003), @@ -934,13 +871,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "MOV EAX, dword ptr [RBP + -0x4]")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x12345678), @@ -968,7 +905,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "MOV EAX, dword ptr [RBP + -0x2]")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); @@ -991,7 +928,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "MOV EAX, dword ptr [EBP + -0x2]")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); @@ -1014,7 +951,7 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "unimpl")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); emuThread.stepInstruction(); @@ -1037,13 +974,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes List.of( "mov.w [W1], W0")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0); + BytesTracePcodeEmulator emu = new BytesTracePcodeEmulator(tb.trace, 0); PcodeThread emuThread = emu.newThread(thread.getPath()); //emuThread.overrideContextWithDefault(); // default context is null? emuThread.stepInstruction(); try (UndoableTransaction tid = tb.startTransaction()) { - emu.writeDown(tb.trace, 1, 1, false); + emu.writeDown(tb.trace, 1, 1); } assertEquals(BigInteger.valueOf(0x000102), diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java index 7102a6d127..ccceb87863 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/pcode/exec/trace/TraceSleighUtilsTest.java @@ -221,7 +221,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest PcodeExecutor executor = new PcodeExecutor<>(sp.getLanguage(), BytesPcodeArithmetic.forLanguage(b.language), - new TraceBytesPcodeExecutorState(b.trace, 0, thread, 0)); + new DirectBytesTracePcodeExecutorState(b.trace, 0, thread, 0)); sp.execute(executor, PcodeUseropLibrary.nil()); } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/ToyDBTraceBuilder.java index a26bb2e78c..35a681947c 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 @@ -53,6 +53,7 @@ import ghidra.trace.database.thread.DBTraceThreadManager; import ghidra.trace.model.*; import ghidra.trace.model.guest.TraceGuestPlatform; import ghidra.trace.model.guest.TracePlatform; +import ghidra.trace.model.symbol.TraceReferenceManager; import ghidra.trace.model.thread.TraceThread; import ghidra.util.Msg; import ghidra.util.database.DBOpenMode; @@ -60,12 +61,33 @@ import ghidra.util.database.UndoableTransaction; import ghidra.util.exception.*; import ghidra.util.task.ConsoleTaskMonitor; +/** + * A convenient means of creating a {@link Trace} for testing + * + *

+ * There are two patterns for using this: 1) {@code try-with-resources}, and 2) in set up and tear + * down. Some of our abstract test cases include one of these already. The constructors can build or + * take a trace from a variety of sources, and it provides many methods for accessing parts of the + * trace and/or program API more conveniently, esp., for generating addresses. + * + *

+ * The builder is a consumer of the trace and will automatically release it in {@link #close()}. + */ public class ToyDBTraceBuilder implements AutoCloseable { public final Language language; public final DBTrace trace; public final TracePlatform host; public final LanguageService languageService = DefaultLanguageService.getLanguageService(); + /** + * Open a .gzf compressed trace + * + * @param file the .gzf file containing the trace + * @throws CancelledException never, since the monitor cannot be cancelled + * @throws VersionException if the trace's version is not as expected + * @throws LanguageNotFoundException if the trace's language cannot be found + * @throws IOException if there's an issue accessing the file + */ public ToyDBTraceBuilder(File file) throws CancelledException, VersionException, LanguageNotFoundException, IOException { DBHandle handle = new DBHandle(file); @@ -74,6 +96,13 @@ public class ToyDBTraceBuilder implements AutoCloseable { this.host = trace.getPlatformManager().getHostPlatform(); } + /** + * Create a new trace with the given name and language + * + * @param name the name + * @param langID the id of the language, as in {@link LanguageID} + * @throws IOException if there's an issue creating the trace's database file(s) + */ // TODO: A constructor for specifying compiler, too public ToyDBTraceBuilder(String name, String langID) throws IOException { this.language = languageService.getLanguage(new LanguageID(langID)); @@ -81,6 +110,14 @@ public class ToyDBTraceBuilder implements AutoCloseable { this.host = trace.getPlatformManager().getHostPlatform(); } + /** + * Adopt the given trace + * + *

+ * The builder will add itself as a consumer of the trace, so the caller may safely release it. + * + * @param trace the trace + */ public ToyDBTraceBuilder(Trace trace) { this.language = trace.getBaseLanguage(); this.trace = (DBTrace) trace; @@ -88,6 +125,14 @@ public class ToyDBTraceBuilder implements AutoCloseable { trace.addConsumer(this); } + /** + * Manipulate the trace's memory and registers using Sleigh + * + * @param snap the snap to modify + * @param frame the frame to modify + * @param thread the thread to modify, can be {@code null} if only memory is used + * @param sleigh the lines of Sleigh, including semicolons. + */ public void exec(long snap, int frame, TraceThread thread, List sleigh) { PcodeProgram program = SleighProgramCompiler.compileProgram((SleighLanguage) language, "builder", sleigh, PcodeUseropLibrary.nil()); @@ -95,74 +140,212 @@ public class ToyDBTraceBuilder implements AutoCloseable { .execute(program, PcodeUseropLibrary.nil()); } + /** + * Get the named register + * + * @param name the name + * @return the register or null if it doesn't exist + */ + public Register reg(String name) { + return language.getRegister(name); + } + + /** + * A shortcut for {@code space.getAdddress(offset)} + * + * @param space the space + * @param offset the offset + * @return the address + */ public Address addr(AddressSpace space, long offset) { return space.getAddress(offset); } + /** + * Create an address in the given language's default space + * + * @param lang the langauge + * @param offset the offset + * @return the address + */ public Address addr(Language lang, long offset) { return addr(lang.getDefaultSpace(), offset); } + /** + * Create an address in the trace's default space + * + * @param offset the offset + * @return the address + */ public Address addr(long offset) { return addr(language, offset); } - public Address addr(TracePlatform lang, long offset) { - return lang.getLanguage().getDefaultSpace().getAddress(offset); + /** + * Create an address in the given platform's default space + * + * @param platform the platform + * @param offset the offset + * @return the address + */ + public Address addr(TracePlatform platform, long offset) { + return platform.getLanguage().getDefaultSpace().getAddress(offset); } + /** + * Create an address in the given language's default data space + * + * @param lang the language + * @param offset the offset + * @return the address + */ public Address data(Language lang, long offset) { return addr(lang.getDefaultDataSpace(), offset); } + /** + * Create an address in the trace's default data space + * + * @param offset the offset + * @return the address + */ public Address data(long offset) { return data(language, offset); } - public Address data(TraceGuestPlatform lang, long offset) { - return data(lang.getLanguage(), offset); + /** + * Create an address in the given platform's default data space + * + * @param platform the platform + * @param offset the offset + * @return the address + */ + public Address data(TraceGuestPlatform platform, long offset) { + return data(platform.getLanguage(), offset); } + /** + * Create an address range: shortcut for {@link new AddressRangeImpl(start, end)} + * + * @param start the start address + * @param end the end address + * @return the range + */ public AddressRange range(Address start, Address end) { return new AddressRangeImpl(start, end); } + /** + * Create an address range in the given space with the given start and end offsets + * + * @param space the space + * @param start the start offset + * @param end the end offset + * @return the range + */ public AddressRange range(AddressSpace space, long start, long end) { return range(addr(space, start), addr(space, end)); } + /** + * Create an address range in the given language's default space + * + * @param lang the language + * @param start the start offset + * @param end the end offset + * @return the range + */ public AddressRange range(Language lang, long start, long end) { return range(lang.getDefaultSpace(), start, end); } + /** + * Create an address range in the trace's default space + * + * @param start the start offset + * @param end the end offset + * @return the range + */ public AddressRange range(long start, long end) { return range(language, start, end); } + /** + * Create a singleton address range in the trace's default space + * + * @param singleton the offset + * @return the range + */ public AddressRange range(long singleton) { return range(singleton, singleton); } + /** + * Create an address-span box in the trace's default space with a singleton snap + * + * @param snap the snap + * @param start the start address offset + * @param end the end address offset + * @return the box + */ public TraceAddressSnapRange srange(long snap, long start, long end) { return new ImmutableTraceAddressSnapRange(addr(start), addr(end), snap, snap); } + /** + * Create an address range in the given language's default data space + * + * @param lang the language + * @param start the start offset + * @param end the end offset + * @return the range + */ public AddressRange drng(Language lang, long start, long end) { return range(language.getDefaultDataSpace(), start, end); } + /** + * Create an address range in the trace's default data space + * + * @param start the start offset + * @param end the end offset + * @return the range + */ public AddressRange drng(long start, long end) { return drng(language, start, end); } - public AddressRange range(TraceGuestPlatform lang, long start, long end) { - return range(lang.getLanguage(), start, end); + /** + * Create an address range in the given platform's default space + * + * @param platform the platform + * @param start the start offset + * @param end the end offset + * @return the range + */ + public AddressRange range(TracePlatform platform, long start, long end) { + return range(platform.getLanguage(), start, end); } - public AddressRange drng(TraceGuestPlatform lang, long start, long end) { - return drng(lang.getLanguage(), start, end); + /** + * Create an address range in the given platform's default data space + * + * @param platform the platform + * @param start the start offset + * @param end the end offset + * @return the range + */ + public AddressRange drng(TracePlatform platform, long start, long end) { + return drng(platform.getLanguage(), start, end); } + /** + * Create an address set from the given ranges + * + * @param ranges the ranges + * @return the set + */ public AddressSetView set(AddressRange... ranges) { AddressSet result = new AddressSet(); for (AddressRange rng : ranges) { @@ -171,6 +354,17 @@ public class ToyDBTraceBuilder implements AutoCloseable { return result; } + /** + * Create a byte array + * + *

+ * This is basically syntactic sugar, since expressing a byte array literal can get obtuse in + * Java. {@code new byte[] {0, 1, 2, (byte) 0x80, (byte) 0xff}} vs + * {@code arr(0, 1, 2, 0x80, 0xff)}. + * + * @param e the bytes' values + * @return the array + */ public byte[] arr(int... e) { byte[] result = new byte[e.length]; for (int i = 0; i < e.length; i++) { @@ -179,10 +373,22 @@ public class ToyDBTraceBuilder implements AutoCloseable { return result; } + /** + * Create a byte buffer + * + * @param e the bytes' values + * @return the buffer, positioned at 0 + */ public ByteBuffer buf(int... e) { return ByteBuffer.wrap(arr(e)); } + /** + * Create a byte buffer, filled with a UTF-8 encoded string + * + * @param str the string to encode + * @return the buffer, positioned at 0 + */ public ByteBuffer buf(String str) { CharsetEncoder ce = Charset.forName("UTF-8").newEncoder(); ByteBuffer result = @@ -192,15 +398,39 @@ public class ToyDBTraceBuilder implements AutoCloseable { return result.flip(); } + /** + * Start a transaction on the trace + * + *

+ * Use this in a {@code try-with-resources} block + * + * @return the transaction handle + */ public UndoableTransaction startTransaction() { return UndoableTransaction.start(trace, "Testing"); } + /** + * Ensure the given bookmark type exists and retrieve it + * + * @param name the name of the type + * @return the type + */ public DBTraceBookmarkType getOrAddBookmarkType(String name) { DBTraceBookmarkManager manager = trace.getBookmarkManager(); return manager.defineBookmarkType(name, null, Color.red, 1); } + /** + * Add a bookmark to the trace + * + * @param snap the starting snap + * @param addr the address + * @param typeName the name of its type + * @param category the category + * @param comment an optional comment + * @return the new bookmark + */ public DBTraceBookmark addBookmark(long snap, long addr, String typeName, String category, String comment) { DBTraceBookmarkType type = getOrAddBookmarkType(typeName); @@ -216,8 +446,19 @@ public class ToyDBTraceBuilder implements AutoCloseable { return bm; } + /** + * Add a bookmark on a register in the trace + * + * @param snap the starting snap + * @param threadName the name of the thread + * @param registerName the name of the regsiter + * @param typeName the name of its type + * @param category the category + * @param comment an optional comment + * @return the new bookmark + */ public DBTraceBookmark addRegisterBookmark(long snap, String threadName, String registerName, - String typeName, String category, String comment) throws DuplicateNameException { + String typeName, String category, String comment) { Register register = language.getRegister(registerName); assertNotNull(register); TraceThread thread = getOrAddThread(threadName, snap); @@ -234,12 +475,32 @@ public class ToyDBTraceBuilder implements AutoCloseable { return bm; } + /** + * Create a data unit + * + * @param snap the starting snap + * @param start the min address + * @param type the data type of the unit + * @param length the length, or -1 for the type's default + * @return the new data unit + * @throws CodeUnitInsertionException if the unit cannot be created + */ public DBTraceDataAdapter addData(long snap, Address start, DataType type, int length) throws CodeUnitInsertionException { DBTraceCodeManager code = trace.getCodeManager(); return code.definedData().create(Range.atLeast(snap), start, type, length); } + /** + * Create a data unit, first placing the given bytes + * + * @param snap the starting snap + * @param start the min address + * @param type the data type of the unit + * @param buf the bytes to place, which will become the unit's bytes + * @return the new data unit + * @throws CodeUnitInsertionException if the unit cannot be created + */ public DBTraceDataAdapter addData(long snap, Address start, DataType type, ByteBuffer buf) throws CodeUnitInsertionException { int length = buf.remaining(); @@ -250,6 +511,15 @@ public class ToyDBTraceBuilder implements AutoCloseable { return data; } + /** + * Create an instruction unit by disassembling existing bytes + * + * @param snap the starting snap + * @param start the min address + * @param platform the platform for the language to disassemble + * @return the instruction unit + * @throws CodeUnitInsertionException if the instruction cannot be created + */ public DBTraceInstruction addInstruction(long snap, Address start, TracePlatform platform) throws CodeUnitInsertionException { DBTraceCodeManager code = trace.getCodeManager(); @@ -267,6 +537,16 @@ public class ToyDBTraceBuilder implements AutoCloseable { .create(Range.atLeast(snap), start, platform, pseudoIns.getPrototype(), pseudoIns); } + /** + * Create an instruction unit, first placing the given bytes, and disassembling + * + * @param snap the starting snap + * @param start the min address + * @param platform the platform the the language to disassemble + * @param buf the bytes to place, which will become the unit's bytes + * @return the instruction unit + * @throws CodeUnitInsertionException if the instruction cannot be created + */ public DBTraceInstruction addInstruction(long snap, Address start, TracePlatform platform, ByteBuffer buf) throws CodeUnitInsertionException { int length = buf.remaining(); @@ -277,20 +557,48 @@ public class ToyDBTraceBuilder implements AutoCloseable { return instruction; } - public TraceThread getOrAddThread(String name, long creationSnap) - throws DuplicateNameException { + /** + * Ensure the given thread exists and retrieve it + * + * @param name the thread's name + * @param creationSnap the snap where the thread must exist + * @return the thread + */ + public TraceThread getOrAddThread(String name, long creationSnap) { DBTraceThreadManager manager = trace.getThreadManager(); Collection threads = manager.getThreadsByPath(name); if (threads != null && !threads.isEmpty()) { return threads.iterator().next(); } - return manager.createThread(name, creationSnap); + try { + return manager.createThread(name, creationSnap); + } + catch (DuplicateNameException e) { + throw new AssertionError(e); + } } + /** + * Add a mnemonic memory reference + * + * @param creationSnap the starting snap + * @param from the from address + * @param to the to address + * @return the reference + */ public DBTraceReference addMemoryReference(long creationSnap, Address from, Address to) { return addMemoryReference(creationSnap, from, to, -1); } + /** + * Add an operand memory reference + * + * @param creationSnap the starting snap + * @param from the from address + * @param to the to address + * @param operandIndex the operand index, or -1 for mnemonic + * @return the reference + */ public DBTraceReference addMemoryReference(long creationSnap, Address from, Address to, int operandIndex) { return trace.getReferenceManager() @@ -298,6 +606,17 @@ public class ToyDBTraceBuilder implements AutoCloseable { RefType.DATA, SourceType.DEFAULT, operandIndex); } + /** + * Add a base-offset memory reference + * + * @param creationSnap the starting snap + * @param from the from address + * @param to the to address + * @param toAddrIsBase true if {@code to} is the base address, implying offset must be added to + * get the real to address. + * @param offset the offset + * @return the reference + */ public DBTraceReference addOffsetReference(long creationSnap, Address from, Address to, boolean toAddrIsBase, long offset) { return trace.getReferenceManager() @@ -305,6 +624,21 @@ public class ToyDBTraceBuilder implements AutoCloseable { offset, RefType.DATA, SourceType.DEFAULT, -1); } + /** + * Add a shifted memory reference + * + *

+ * TODO: This uses opIndex -1, which doesn't make sense for a shifted reference. The "to" + * address is computed (I assume by the analyzer which places such reference) as the operand + * value shifted by the given shift amount. What is the opIndex for a data unit? Probably 0, + * since the "mnemonic" would be its type? Still, this suffices for testing the database. + * + * @param creationSnap the starting snap + * @param from the from address + * @param to the to address + * @param shift the shift + * @return the reference + */ public DBTraceReference addShiftedReference(long creationSnap, Address from, Address to, int shift) { return trace.getReferenceManager() @@ -312,18 +646,51 @@ public class ToyDBTraceBuilder implements AutoCloseable { to, shift, RefType.DATA, SourceType.DEFAULT, -1); } + /** + * Add a register reference + * + *

+ * See + * {@link TraceReferenceManager#addRegisterReference(Range, Address, Register, RefType, SourceType, int)} + * regarding potential confusion of the word "register" in this context. + * + * @param creationSnap the starting snap + * @param from the from register + * @param to the to address + * @return the reference + */ public DBTraceReference addRegisterReference(long creationSnap, Address from, String to) { return trace.getReferenceManager() .addRegisterReference(Range.atLeast(creationSnap), from, language.getRegister(to), RefType.DATA, SourceType.DEFAULT, -1); } + /** + * Add a stack reference + * + *

+ * See + * {@link TraceReferenceManager#addStackReference(Range, Address, int, RefType, SourceType, int)} + * regarding potential confusion of the word "stack" in this context. + * + * @param creationSnap the starting snap + * @param from the from address + * @param to the to stack offset + * @return the reference + */ public DBTraceReference addStackReference(long creationSnap, Address from, int to) { return trace.getReferenceManager() .addStackReference(Range.atLeast(creationSnap), from, to, RefType.DATA, SourceType.DEFAULT, -1); } + /** + * Save the trace to a temporary .gzf file + * + * @return the new file + * @throws IOException if the trace could not be saved + * @throws CancelledException never, since the monitor cannot be cancelled + */ public File save() throws IOException, CancelledException { Path tmp = Files.createTempFile("test", ".db"); Files.delete(tmp); // saveAs must create the file @@ -331,19 +698,35 @@ public class ToyDBTraceBuilder implements AutoCloseable { return tmp.toFile(); } + /** + * Get the language with the given ID, as in {@link LangaugeID} + * + * @param id the ID + * @return the langauge + * @throws LanguageNotFoundException if the language does not exist + */ + public Language getLanguage(String id) throws LanguageNotFoundException { + return languageService.getLanguage(new LanguageID(id)); + } + + /** + * Get the compiler spec with the given language and compiler IDs + * + * @param langID the language ID as in {@link LanguageID} + * @param compID the compiler ID as in {@link CompilerSpecID} + * @return the compiler spec + * @throws CompilerSpecNotFoundException if the compiler spec does not exist + * @throws LanguageNotFoundException if the langauge does not exist + */ + public CompilerSpec getCompiler(String langID, String compID) + throws CompilerSpecNotFoundException, LanguageNotFoundException { + return getLanguage(langID).getCompilerSpecByID(new CompilerSpecID(compID)); + } + @Override public void close() { if (trace.getConsumerList().contains(this)) { trace.release(this); } } - - public Language getLanguage(String id) throws LanguageNotFoundException { - return languageService.getLanguage(new LanguageID(id)); - } - - public CompilerSpec getCompiler(String langID, String compID) - throws CompilerSpecNotFoundException, LanguageNotFoundException { - return getLanguage(langID).getCompilerSpecByID(new CompilerSpecID(compID)); - } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/property/DBTraceAddressPropertyManagerTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/property/DBTraceAddressPropertyManagerTest.java index c907ea12b3..e80db39570 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/property/DBTraceAddressPropertyManagerTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/database/property/DBTraceAddressPropertyManagerTest.java @@ -216,7 +216,7 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt @Test public void testGetPropertyMap() throws Exception { assertNull(propertyManager.getPropertyMap("MyProp")); - TracePropertyMap map; + TracePropertyMapOperations map; try (UndoableTransaction tid = tb.startTransaction()) { map = propertyManager.createPropertyMap("MyProp", String.class); } @@ -236,7 +236,7 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt @Test public void testGetOrCreatePropertyMap() throws Exception { assertNull(propertyManager.getPropertyMap("MyProp")); - TracePropertyMap map; + TracePropertyMapOperations map; try (UndoableTransaction tid = tb.startTransaction()) { map = propertyManager.getOrCreatePropertyMap("MyProp", String.class); } @@ -252,53 +252,10 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt } } - @Test - public void testGetPropertyGetter() throws Exception { - assertNull(propertyManager.getPropertyGetter("MyProp", String.class)); - TracePropertyMap map; - try (UndoableTransaction tid = tb.startTransaction()) { - map = propertyManager.createPropertyMap("MyProp", String.class); - } - assertNotNull(map); - TracePropertyGetter getter = - propertyManager.getPropertyGetter("MyProp", String.class); - assertSame(map, getter); - assertSame(map, propertyManager.getPropertyGetter("MyProp", Object.class)); - - try { - propertyManager.getPropertyGetter("MyProp", Integer.class); - fail(); - } - catch (TypeMismatchException e) { - // pass - } - } - - @Test - public void testGetOrCreatePropertySetter() throws Exception { - TracePropertyMap map; - try (UndoableTransaction tid = tb.startTransaction()) { - map = propertyManager.createPropertyMap("MyProp", MySaveable.class); - } - assertNotNull(map); - TracePropertySetter setter = - propertyManager.getOrCreatePropertySetter("MyProp", ExtMySaveable.class); - assertSame(map, setter); - assertSame(map, propertyManager.getOrCreatePropertySetter("MyProp", MySaveable.class)); - - try { - propertyManager.getOrCreatePropertySetter("MyProp", Saveable.class); - fail(); - } - catch (TypeMismatchException e) { - // pass - } - } - @Test public void testGetAllProperties() throws Exception { assertEquals(0, propertyManager.getAllProperties().size()); - TracePropertyMap map; + TracePropertyMapOperations map; try (UndoableTransaction tid = tb.startTransaction()) { map = propertyManager.createPropertyMap("MyProp", String.class); } @@ -309,7 +266,7 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt @Test public void testMapGetValueClass() throws Exception { - TracePropertyMap map; + TracePropertyMapOperations map; try (UndoableTransaction tid = tb.startTransaction()) { map = propertyManager.createPropertyMap("MyProp", String.class); } @@ -318,7 +275,7 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt protected void doTestMap(Class valueClass, T value) throws Exception { try (UndoableTransaction tid = tb.startTransaction()) { - TracePropertyMap map = + TracePropertyMapOperations map = propertyManager.createPropertyMap("MyProp", valueClass); assertSame(valueClass, map.getValueClass()); @@ -342,7 +299,8 @@ public class DBTraceAddressPropertyManagerTest extends AbstractGhidraHeadlessInt try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder(file)) { TraceAddressPropertyManager propertyManager = tb.trace.getAddressPropertyManager(); - TracePropertyMap map = propertyManager.getPropertyMap("MyProp", valueClass); + TracePropertyMapOperations map = + propertyManager.getPropertyMap("MyProp", valueClass); assertNotNull(map); Entry entry = map.getEntry(4, tb.addr(0x00400001)); diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java index 0d213ad1dc..9ea82ce756 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestMachine.java @@ -20,14 +20,26 @@ import java.util.List; import ghidra.pcode.emu.AbstractPcodeMachine; import ghidra.pcode.emu.PcodeThread; -import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.pcode.exec.PcodeUseropLibrary; +import ghidra.pcode.exec.*; +/** + * A mocked out machine that creates mocked out threads + * + *

+ * The purpose is to record the sequence of steps actually executed when testing + * {@link TraceSchedule}. + */ class TestMachine extends AbstractPcodeMachine { + /** The record of steps taken */ protected final List record = new ArrayList<>(); public TestMachine() { - super(TraceScheduleTest.TOY_BE_64_LANG, null); + super(TraceScheduleTest.TOY_BE_64_LANG); + } + + @Override + protected PcodeArithmetic createArithmetic() { + return null; } @Override diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java index 5aec1429ca..94f1eebaff 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TestThread.java @@ -17,6 +17,7 @@ package ghidra.trace.model.time.schedule; import java.util.List; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.ThreadPcodeExecutorState; import ghidra.pcode.exec.*; @@ -24,6 +25,12 @@ import ghidra.program.model.address.Address; import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.listing.Instruction; +/** + * A mocked out p-code thread + * + *

+ * This records the sequence of steps and Sleigh executions when testing {@link TraceSchedule}. + */ class TestThread implements PcodeThread { protected final String name; protected final TestMachine machine; @@ -43,9 +50,20 @@ class TestThread implements PcodeThread { return machine; } + @Override + public SleighLanguage getLanguage() { + return TraceScheduleTest.TOY_BE_64_LANG; + } + + @Override + public PcodeArithmetic getArithmetic() { + return machine.getArithmetic(); + } + @Override public PcodeExecutor getExecutor() { - return new PcodeExecutor<>(TraceScheduleTest.TOY_BE_64_LANG, machine.getArithmetic(), getState()) { + return new PcodeExecutor<>(TraceScheduleTest.TOY_BE_64_LANG, machine.getArithmetic(), + getState()) { public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary library) { machine.record.add("x:" + name); // TODO: Verify the actual effect diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnProgramTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnProgramTableModel.java index e7e85e4942..3bb112c38c 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnProgramTableModel.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnProgramTableModel.java @@ -16,20 +16,22 @@ package docking.widgets.table; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.framework.plugintool.PluginTool; import ghidra.program.model.address.*; import ghidra.program.model.listing.Program; import ghidra.program.util.ProgramLocation; import ghidra.program.util.ProgramSelection; -public class DefaultEnumeratedColumnProgramTableModel & EnumeratedTableColumn, R> +public class DefaultEnumeratedColumnProgramTableModel & EnumeratedTableColumn, R> extends DefaultEnumeratedColumnTableModel implements EnumeratedColumnProgramTableModel { protected final C selColumn; private Program program; - public DefaultEnumeratedColumnProgramTableModel(String name, Class colType, C selColumn) { - super(name, colType); + public DefaultEnumeratedColumnProgramTableModel(PluginTool tool, String name, Class colType, + C selColumn) { + super(tool, name, colType); if (selColumn != null) { Class valueClass = selColumn.getValueClass(); if (!Address.class.isAssignableFrom(valueClass) && diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java index 02408c4c2f..07ee4b8432 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/DefaultEnumeratedColumnTableModel.java @@ -20,104 +20,104 @@ import java.util.function.Predicate; import docking.widgets.table.ColumnSortState.SortDirection; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.docking.settings.Settings; +import ghidra.framework.plugintool.PluginTool; +import ghidra.framework.plugintool.ServiceProvider; -public class DefaultEnumeratedColumnTableModel & EnumeratedTableColumn, R> - extends AbstractSortedTableModel implements EnumeratedColumnTableModel { +/** + * A table model whose columns are described using an {@link Enum}. + * + *

+ * See the callers to this class' constructor to find example uses. + * + * @param the type of the enum + * @param the type of rows + */ +public class DefaultEnumeratedColumnTableModel & EnumeratedTableColumn, R> + extends GDynamicColumnTableModel implements EnumeratedColumnTableModel { // NOTE: If I need to track indices, addSortListener + /** + * An interface on enums used to describe table columns + * + * @param the type of the enum + * @param the type of rows + */ public static interface EnumeratedTableColumn, R> { + /** + * Get the value class of cells in this column + * + * @return the class + */ public Class getValueClass(); + /** + * Get the value of this column for the given row + * + * @param row the row + * @return the value + */ public Object getValueOf(R row); + /** + * Get the name of this column + * + * @return the name + */ public String getHeader(); + /** + * Get the value of this column for the given row + * + * @param row the row + * @param value the new value + */ default public void setValueOf(R row, Object value) { throw new UnsupportedOperationException("Cell is not editable"); } + /** + * Check if this column can be modified for the given row + * + * @param row the row + * @return true if editable + */ default public boolean isEditable(R row) { return false; } + /** + * Check if this column can be sorted + * + *

+ * TODO: Either this should be implemented as ported to {@link GDynamicColumnTableModel}, or + * removed. + * + * @return true if sortable + */ default public boolean isSortable() { return true; } + /** + * Get the default sort direction for this column + * + * @return the sort direction + */ default public SortDirection defaultSortDirection() { return SortDirection.ASCENDING; } } - public class TableRowIterator implements RowIterator { - protected final ListIterator it = modelData.listIterator(); - protected int index; - - @Override - public boolean hasNext() { - return it.hasNext(); - } - - @Override - public R next() { - index = it.nextIndex(); - return it.next(); - } - - @Override - public boolean hasPrevious() { - return it.hasPrevious(); - } - - @Override - public R previous() { - index = it.previousIndex(); - return it.previous(); - } - - @Override - public int nextIndex() { - return it.nextIndex(); - } - - @Override - public int previousIndex() { - return it.previousIndex(); - } - - @Override - public void remove() { - it.remove(); - fireTableRowsDeleted(index, index); - } - - @Override - public void set(R e) { - it.set(e); - fireTableRowsUpdated(index, index); - } - - @Override - public void notifyUpdated() { - fireTableRowsUpdated(index, index); - } - - @Override - public void add(R e) { - it.add(e); - int nextIndex = it.nextIndex(); - fireTableRowsInserted(nextIndex, nextIndex); - } - } - private final List modelData = new ArrayList<>(); private final String name; private final C[] cols; - public DefaultEnumeratedColumnTableModel(String name, Class colType) { + public DefaultEnumeratedColumnTableModel(PluginTool tool, String name, Class colType) { + super(tool); this.name = name; this.cols = colType.getEnumConstants(); - setupDefaultSortOrder(); + reloadColumns(); // Smell } @Override @@ -130,76 +130,112 @@ public class DefaultEnumeratedColumnTableModel & EnumeratedTab return modelData; } - protected void setupDefaultSortOrder() { - List defaultOrder = defaultSortOrder(); - if (defaultOrder.isEmpty()) { - return; + static class EnumeratedDynamicTableColumn + extends AbstractDynamicTableColumn + implements EditableDynamicTableColumn { + private final EnumeratedTableColumn col; + + public EnumeratedDynamicTableColumn(EnumeratedTableColumn col) { + this.col = col; } - TableSortStateEditor editor = new TableSortStateEditor(); - for (C col : defaultOrder) { - editor.addSortedColumn(col.ordinal(), col.defaultSortDirection()); + + @Override + public String getColumnName() { + return col.getHeader(); + } + + @Override + public Object getValue(R rowObject, Settings settings, Void data, + ServiceProvider serviceProvider) throws IllegalArgumentException { + return col.getValueOf(rowObject); + } + + @Override + @SuppressWarnings("unchecked") + public Class getColumnClass() { + return (Class) col.getValueClass(); + } + + @Override + public boolean isEditable(R row, Settings settings, Void dataSource, + ServiceProvider serviceProvider) { + return col.isEditable(row); + } + + @Override + public void setValueOf(R row, Object value, Settings settings, Void dataSource, + ServiceProvider serviceProvider) { + col.setValueOf(row, value); } - setDefaultTableSortState(editor.createTableSortState()); } + @Override + protected TableColumnDescriptor createTableColumnDescriptor() { + TableColumnDescriptor descriptor = new TableColumnDescriptor<>(); + if (cols != null) { // Smells + List defaultOrder = defaultSortOrder(); + for (C col : cols) { + descriptor.addVisibleColumn( + new EnumeratedDynamicTableColumn(col), + defaultOrder.indexOf(col), // -1 means not found, not sorted + col.defaultSortDirection().isAscending()); + } + } + return descriptor; + } + + @Override + public Void getDataSource() { + return null; + } + + /** + * Get the default sort order of the table + * + * @return the list of columns in order of descending priority + */ public List defaultSortOrder() { return Collections.emptyList(); } - /*@Override - public int getRowCount() { - return modelData.size(); - }*/ - - @Override - public int getColumnCount() { - return cols.length; - } - - /*@Override - public Object getValueAt(int rowIndex, int columnIndex) { - return getColumnValueForRow(modelData.get(rowIndex), columnIndex); - }*/ - @Override public void setValueAt(Object aValue, int rowIndex, int columnIndex) { + DynamicTableColumn column = tableColumns.get(columnIndex); + if (!(column instanceof EditableDynamicTableColumn)) { + return; // TODO: throw? + } + @SuppressWarnings("unchecked") // tableColumns doesn't include DATA_SOURCE + // May cause ClassCastException if given value can't be cast. Good. + EditableDynamicTableColumn editable = + (EditableDynamicTableColumn) column; synchronized (modelData) { + if (rowIndex < 0 || rowIndex >= modelData.size()) { + return; // TODO: throw? + } R row = modelData.get(rowIndex); - C col = cols[columnIndex]; - Class cls = col.getValueClass(); - col.setValueOf(row, cls.cast(aValue)); + editable.setValueOf(row, aValue, columnSettings.get(column), getDataSource(), + serviceProvider); } fireTableCellUpdated(rowIndex, columnIndex); } @Override public boolean isCellEditable(int rowIndex, int columnIndex) { - synchronized (modelData) { - R row = modelData.get(rowIndex); - C col = cols[columnIndex]; - return col.isEditable(row); + DynamicTableColumn column = tableColumns.get(columnIndex); + if (!(column instanceof EditableDynamicTableColumn)) { + return false; + } + @SuppressWarnings("unchecked") // tableColumns doesn't include DATA_SOURCE + EditableDynamicTableColumn editable = + (EditableDynamicTableColumn) column; + synchronized (modelData) { + if (rowIndex < 0 || rowIndex >= modelData.size()) { + return false; + } + R row = modelData.get(rowIndex); + return editable.isEditable(row, columnSettings.get(column), getDataSource(), + serviceProvider); } - } - - @Override - public boolean isSortable(int columnIndex) { - C col = cols[columnIndex]; - return col.isSortable(); - } - - @Override - public Class getColumnClass(int columnIndex) { - return cols[columnIndex].getValueClass(); - } - - @Override - public String getColumnName(int column) { - return cols[column].getHeader(); - } - - @Override - public Object getColumnValueForRow(R t, int columnIndex) { - return cols[columnIndex].getValueOf(t); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/EnumeratedColumnTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/EnumeratedColumnTableModel.java index 44712b0ae7..caf18883b4 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/EnumeratedColumnTableModel.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/EnumeratedColumnTableModel.java @@ -15,12 +15,28 @@ */ package docking.widgets.table; -import java.util.*; +import java.util.Collection; +import java.util.List; import java.util.function.Predicate; +import javax.help.UnsupportedOperationException; + +import ghidra.docking.settings.Settings; +import ghidra.framework.plugintool.ServiceProvider; + public interface EnumeratedColumnTableModel extends RowObjectTableModel { - interface RowIterator extends ListIterator { - void notifyUpdated(); + + public interface EditableDynamicTableColumn + extends DynamicTableColumn { + default public boolean isEditable(ROW_TYPE row, Settings settings, DATA_SOURCE dataSource, + ServiceProvider serviceProvider) { + return false; + } + + default public void setValueOf(ROW_TYPE row, COLUMN_TYPE value, Settings settings, + DATA_SOURCE dataSource, ServiceProvider serviceProvider) { + throw new UnsupportedOperationException("Cell is not editable"); + } } void add(R row); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java index e017949ed6..f9ca2c1a90 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/table/RowWrappedEnumeratedColumnTableModel.java @@ -21,6 +21,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.framework.plugintool.PluginTool; import ghidra.util.Msg; /** @@ -38,9 +39,9 @@ public class RowWrappedEnumeratedColumnTableModel & Enumerated private final Function wrapper; private final Map map = new HashMap<>(); - public RowWrappedEnumeratedColumnTableModel(String name, Class colType, + public RowWrappedEnumeratedColumnTableModel(PluginTool tool, String name, Class colType, Function keyFunc, Function wrapper) { - super(name, colType); + super(tool, name, colType); this.keyFunc = keyFunc; this.wrapper = wrapper; } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/timeline/TimelinePanel.java b/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/timeline/TimelinePanel.java deleted file mode 100644 index 26b3a0a1b8..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/timeline/TimelinePanel.java +++ /dev/null @@ -1,847 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package docking.widgets.timeline; - -import java.awt.*; -import java.awt.event.*; -import java.awt.event.FocusEvent.Cause; -import java.util.*; -import java.util.List; -import java.util.Map.Entry; -import java.util.function.Function; - -import javax.swing.*; -import javax.swing.border.Border; -import javax.swing.event.*; - -import com.google.common.collect.*; - -import docking.widgets.table.RowObjectTableModel; -import ghidra.util.Swing; -import ghidra.util.UIManagerWrapper; -import ghidra.util.datastruct.ListenerSet; - -public class TimelinePanel> extends JPanel { - public static final int INSET_WIDTH = 2; - public static final int SLACK = 1; - - public static , M extends Number & Comparable> Range mapRangeEndpoints( - Range range, Function func) { - // Weeeeeeee!!!!!!! - if (range.hasLowerBound()) { - if (range.hasUpperBound()) { - if (range.lowerBoundType() == BoundType.CLOSED) { - if (range.upperBoundType() == BoundType.CLOSED) { - return Range.closed(func.apply(range.lowerEndpoint()), - func.apply(range.upperEndpoint())); - } - return Range.closedOpen(func.apply(range.lowerEndpoint()), - func.apply(range.upperEndpoint())); - } - if (range.upperBoundType() == BoundType.CLOSED) { - return Range.openClosed(func.apply(range.lowerEndpoint()), - func.apply(range.upperEndpoint())); - } - return Range.open(func.apply(range.lowerEndpoint()), - func.apply(range.upperEndpoint())); - } - if (range.lowerBoundType() == BoundType.CLOSED) { - return Range.atLeast(func.apply(range.lowerEndpoint())); - } - return Range.greaterThan(func.apply(range.lowerEndpoint())); - } - if (range.hasUpperBound()) { - if (range.upperBoundType() == BoundType.CLOSED) { - return Range.atMost(func.apply(range.upperEndpoint())); - } - return Range.lessThan(func.apply(range.upperEndpoint())); - } - return Range.all(); - } - - protected static > Double minWithSlack(Double a, N b) { - if (a == null) { - if (b == null) { - return null; - } - return b.doubleValue() - SLACK; - } - if (b == null) { - return a; - } - return Math.min(a, b.doubleValue() - SLACK); - } - - protected static > Double maxWithSlack(Double a, N b) { - if (a == null) { - if (b == null) { - return null; - } - return b.doubleValue() + SLACK; - } - if (b == null) { - return a; - } - return Math.max(a, b.doubleValue() + SLACK); - } - - // TODO: Consider using a "TimelineCellRenderer" - public interface TimelineInfo> { - Range getRange(T t); - - default String getLabel(T t) { - return t.toString(); - } - - default boolean columnAffectsBounds(int column) { - return true; - } - - default Color getForegroundColor(T t, JComponent in, int track, boolean selected, - boolean hasFocus) { - if (selected) { - return UIManagerWrapper.getColor("Table[Enabled+Selected].textForeground"); - } - return UIManagerWrapper.getColor("Table.textForeground"); - } - - default Color getBackgroundColor(T t, JComponent in, int track, boolean selected, - boolean hasFocus) { - if (selected) { - return UIManagerWrapper.getColor("Table[Enabled+Selected].textBackground"); - } - if (track % 2 == 1) { - return UIManagerWrapper.getColor("Table.alternateRowColor"); - } - return UIManagerWrapper.getColor("Table:\"Table.cellRenderer\".background"); - } - - default JComponent getComponent(T t) { - String text = getLabel(t); - JLabel label = new JLabel(text); - label.setToolTipText(text); - label.setHorizontalAlignment(SwingConstants.CENTER); - return label; - } - } - - protected static class BoundTypeBorder implements Border { - protected final Insets unboundedInsets = new Insets(INSET_WIDTH, 0, INSET_WIDTH, 0); - protected final Insets rightBoundedInsets = - new Insets(INSET_WIDTH, 0, INSET_WIDTH, INSET_WIDTH); - protected final Insets leftBoundedInsets = - new Insets(INSET_WIDTH, INSET_WIDTH, INSET_WIDTH, 0); - protected final Insets boundedInsets = - new Insets(INSET_WIDTH, INSET_WIDTH, INSET_WIDTH, INSET_WIDTH); - protected final Range range; - - public BoundTypeBorder(Range range) { - this.range = range; - } - - @Override - public void paintBorder(Component c, Graphics g, int x1, int y1, int width, int height) { - Graphics2D solid = (Graphics2D) g.create(); - solid.setStroke(new BasicStroke(3)); - solid.setColor(c.getForeground()); - Graphics2D dashed = (Graphics2D) solid.create(); - dashed.setStroke(new BasicStroke(3, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, - 1.0f, new float[] { 3, 3 }, 0)); - - int x2 = x1 + width - 1; - int y2 = y1 + height - 1; - - // Adjust for width - // TODO: Make this configurable - x1++; - y1++; - x2--; - y2--; - - // Top and bottom lines are always drawn - solid.drawLine(x1, y1, x2, y1); - solid.drawLine(x1, y2, x2, y2); - - if (range.hasLowerBound()) { - if (range.lowerBoundType() == BoundType.CLOSED) { - solid.drawLine(x1, y1, x1, y2); - } - else { - dashed.drawLine(x1, y1, x1, y2); - } - } - if (range.hasUpperBound()) { - if (range.upperBoundType() == BoundType.CLOSED) { - solid.drawLine(x2, y1, x2, y2); - } - else { - dashed.drawLine(x2, y1, x2, y2); - } - } - } - - @Override - public Insets getBorderInsets(Component c) { - if (range.hasLowerBound()) { - if (range.hasUpperBound()) { - return boundedInsets; - } - return leftBoundedInsets; - } - if (range.hasUpperBound()) { - return rightBoundedInsets; - } - return unboundedInsets; - } - - @Override - public boolean isBorderOpaque() { - return true; - } - } - - protected static class TimelineTrackLayout> - implements LayoutManager2 { - protected final TimelineTrack track; - protected final Map> components = new HashMap<>(); - - public TimelineTrackLayout(TimelineTrack track) { - this.track = track; - } - - @Override - public void addLayoutComponent(String name, Component comp) { - throw new UnsupportedOperationException(); - } - - @Override - @SuppressWarnings("unchecked") // Used only internally - public void addLayoutComponent(Component comp, Object constraints) { - components.put(comp, (Range) constraints); - } - - @Override - public void removeLayoutComponent(Component comp) { - components.remove(comp); - } - - @Override - public Dimension preferredLayoutSize(Container parent) { - double length = track.viewRange.upperEndpoint() - track.viewRange.lowerEndpoint(); - Dimension result = new Dimension(); - for (Entry> ent : components.entrySet()) { - Range tRange = mapRangeEndpoints(ent.getValue(), Number::doubleValue); - if (!tRange.isConnected(track.viewRange)) { - continue; - } - Dimension size = ent.getKey().getMinimumSize(); - Range subRange = track.viewRange.intersection(tRange); - double subLength = subRange.upperEndpoint() - subRange.lowerEndpoint(); - if (subLength != 0) { - double fraction = subLength / length; - result.width = Math.max(result.width, (int) Math.ceil(size.width / fraction)); - } - result.height = Math.max(result.height, size.height); - } - return result; - } - - @Override - public Dimension minimumLayoutSize(Container parent) { - return new Dimension(0, 0); - } - - @Override - public void layoutContainer(Container parent) { - Dimension pDim = parent.getSize(); - double length = track.viewRange.upperEndpoint() - track.viewRange.lowerEndpoint(); - Rectangle cur = new Rectangle(); - cur.y = 0; - cur.height = pDim.height; - for (Entry> ent : components.entrySet()) { - Range tRange = mapRangeEndpoints(ent.getValue(), Number::doubleValue); - if (!tRange.isConnected(track.viewRange)) { - ent.getKey().setBounds(-10, -10, 1, 1); // Nowhere - continue; - } - Range range = track.viewRange.intersection(tRange); - double subLength = range.upperEndpoint() - range.lowerEndpoint(); - double subLeft = range.lowerEndpoint() - track.viewRange.lowerEndpoint(); - cur.x = (int) (subLeft / length * pDim.width); - cur.width = Math.max(INSET_WIDTH * 3, (int) (subLength / length * pDim.width)); - ent.getKey().setBounds(cur); - } - } - - @Override - public Dimension maximumLayoutSize(Container target) { - return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); - } - - @Override - public float getLayoutAlignmentX(Container target) { - return 0.5f; - } - - @Override - public float getLayoutAlignmentY(Container target) { - return 0.5f; - } - - @Override - public void invalidateLayout(Container target) { - // No cache to clear - } - } - - protected static class TimelineTrack> extends JPanel { - protected final RangeMap objects = TreeRangeMap.create(); - protected final BiMap componentMap = HashBiMap.create(); - protected final TimelineInfo info; - protected final MouseListener mouseListener; - protected final FocusListener focusListener; - protected boolean isCompressed; - - protected Range viewRange = Range.closed(-1.0, 1.0); - - public TimelineTrack(TimelineInfo info, MouseListener mouseListener, - FocusListener focusListener, boolean isCompressed) { - this.info = info; - this.mouseListener = mouseListener; - this.focusListener = focusListener; - this.isCompressed = isCompressed; - setLayout(new TimelineTrackLayout<>(this)); - setFocusable(true); - } - - @Override - public boolean isOpaque() { - return super.isOpaque(); - } - - public void setViewRange(Range viewRange) { - this.viewRange = viewRange; - invalidate(); - } - - public boolean fits(Range range) { - if (isCompressed) { - return objects.subRangeMap(range).asMapOfRanges().isEmpty(); - } - return false; - } - - public void add(Range range, T t) { - objects.put(range, t); - JComponent comp = info.getComponent(t); - comp.setOpaque(true); - comp.setFocusable(true); - comp.addMouseListener(mouseListener); - comp.addFocusListener(focusListener); - add(comp, range); - componentMap.put(t, comp); - } - - public void remove(T t) { - Range found = null; - for (Entry, T> ent : objects.asMapOfRanges().entrySet()) { - if (ent.getValue().equals(t)) { - found = ent.getKey(); - break; // I'm assert each value is unique - } - } - if (found != null) { - objects.remove(found); - Component comp = componentMap.remove(t); - remove(comp); - } - } - - public void removeAll(Collection c) { - RangeSet found = TreeRangeSet.create(); - for (Entry, T> ent : objects.asMapOfRanges().entrySet()) { - if (c.contains(ent.getValue())) { - found.add(ent.getKey()); - } - } - for (Range range : found.asRanges()) { - objects.remove(range); - } - } - - public boolean isEmpty() { - return objects.asMapOfRanges().isEmpty(); - } - } - - protected class ItemTracker { - private final List trackedItems = new ArrayList<>(); - - public List itemsInserted(int firstIndex, int lastIndex) { - synchronized (tableModel) { - List inserted = - new ArrayList<>(tableModel.getModelData().subList(firstIndex, lastIndex + 1)); - trackedItems.addAll(firstIndex, inserted); - assert Objects.equals(tableModel.getModelData(), trackedItems); - return inserted; - } - } - - public List itemsUpdated(int firstIndex, int lastIndex) { - synchronized (tableModel) { - List updated = new ArrayList<>(lastIndex - firstIndex + 1); - for (int i = firstIndex; i <= lastIndex; i++) { - T t = tableModel.getModelData().get(i); - updated.add(t); - trackedItems.set(i, t); - } - assert Objects.equals(tableModel.getModelData(), trackedItems); - return updated; - } - } - - public List itemsDeleted(int firstIndex, int lastIndex) { - synchronized (tableModel) { - List sub = trackedItems.subList(firstIndex, lastIndex + 1); - List deleted = new ArrayList<>(sub); - sub.clear(); - assert Objects.equals(tableModel.getModelData(), trackedItems); - return deleted; - } - } - - public List itemsRefreshed() { - synchronized (tableModel) { - trackedItems.clear(); - trackedItems.addAll(tableModel.getModelData()); - return trackedItems; - } - } - - public void clear() { - trackedItems.clear(); - } - - public List items() { - return trackedItems; - } - } - - protected class CellMouseListener extends MouseAdapter { - - @Override - public void mouseClicked(MouseEvent e) { - Component cell = e.getComponent(); - cell.requestFocus(Cause.MOUSE_EVENT); - @SuppressWarnings("unchecked") - TimelineTrack track = (TimelineTrack) cell.getParent(); - T t = track.componentMap.inverse().get(cell); - assert t != null; - - int index = rows.items().indexOf(t); // Ew. Why does filtered model not use view - if (e.isControlDown()) { - if (selectionModel.isSelectedIndex(index)) { - selectionModel.removeSelectionInterval(index, index); - } - else { - selectionModel.addSelectionInterval(index, index); - } - } - else { - selectionModel.setSelectionInterval(index, index); - } - timelineListeners.fire.itemActivated(index); - } - } - - protected class CellFocusListener extends FocusAdapter { - @Override - public void focusGained(FocusEvent e) { - focusGainedOrLost(); - } - - @Override - public void focusLost(FocusEvent e) { - focusGainedOrLost(); - } - } - - protected RowObjectTableModel tableModel; - protected ListSelectionModel selectionModel; - protected TimelineInfo info; - protected Range viewRange = Range.closed(-1.0, 1.0); - protected double maxAtLeast; - private boolean isCompressed = true; - - protected final TableModelListener tableModelListener = this::tableChanged; - protected final ListSelectionListener selectionModelListener = this::selectionChanged; - protected final ItemTracker rows = new ItemTracker(); - protected final List> tracks = new ArrayList<>(); - protected final Map> trackMap = new HashMap<>(); - protected final ListenerSet timelineListeners = - new ListenerSet<>(TimelineListener.class); - protected final MouseListener mouseListener = new CellMouseListener(); - protected final FocusListener focusListener = new CellFocusListener(); - - public TimelinePanel() { - setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); - setFocusable(true); - } - - public TimelinePanel(RowObjectTableModel model, TimelineInfo info) { - this(); - setTableModel(model, info); - setSelectionModel(new DefaultListSelectionModel()); - } - - private synchronized void focusGainedOrLost() { - recolor(); // TODO: Way too draconian - } - - public void addTimelineListener(TimelineListener listener) { - timelineListeners.add(listener); - } - - public void removeTimelineListener(TimelineListener listener) { - timelineListeners.remove(listener); - } - - protected Range computeViewRange() { - // TODO: Optimize when adding if performance is an issue - Double min = null; - double max = maxAtLeast + SLACK; - // Don't use Range's union/span methods, since I need to ignore unbounded ends - for (TimelineTrack track : tracks) { - for (Range range : track.objects.asMapOfRanges().keySet()) { - N lower = range.hasLowerBound() ? range.lowerEndpoint() : null; - N upper = range.hasUpperBound() ? range.upperEndpoint() : null; - min = minWithSlack(min, lower); - min = minWithSlack(min, upper); - max = maxWithSlack(max, lower); - max = maxWithSlack(max, upper); - } - } - if (min == null) { - min = 0.0; - } - return Range.closed(min, max); - } - - protected synchronized void reSortTracks() { - // TODO: Can I be more selective about which tracks are involved? - BiMap, Integer> minIndices = HashBiMap.create(tracks.size()); - List items = rows.items(); - for (int i = 0; i < items.size(); i++) { - final int fi = i; - minIndices.compute(trackMap.get(items.get(i)), - (t, j) -> j == null ? fi : Math.min(fi, j)); - } - tracks.sort((t1, t2) -> Integer.compare(minIndices.get(t1), minIndices.get(t2))); - this.removeAll(); - for (TimelineTrack track : tracks) { - add(track); - } - } - - protected synchronized void assignTrack(T t) { - Range range = info.getRange(t); - for (TimelineTrack track : tracks) { - if (track.fits(range)) { - track.add(range, t); - trackMap.put(t, track); - return; - } - } - TimelineTrack track = - new TimelineTrack<>(info, mouseListener, focusListener, isCompressed); - add(track); - tracks.add(track); - track.add(range, t); - trackMap.put(t, track); - } - - protected synchronized void assignTracks(Collection col) { - for (T t : col) { - assert !trackMap.containsKey(t); - assignTrack(t); - } - } - - protected synchronized void adjustTrack(T t) { - Range range = info.getRange(t); - TimelineTrack track = trackMap.get(t); - track.remove(t); - if (track.fits(range)) { - track.add(range, t); - return; - } - assignTrack(t); - } - - protected synchronized Component getComponent(T key) { - return trackMap.get(key).componentMap.get(key); - } - - protected synchronized void reAssignTracks() { - for (T t : rows.items()) { // Prefer to move earlier rows to the top tracks - TimelineTrack fromTrack = trackMap.get(t); - for (TimelineTrack toTrack : tracks) { - Range range = info.getRange(t); - if (toTrack.fits(range)) { - fromTrack.remove(t); - toTrack.add(range, t); - trackMap.put(t, toTrack); - break; - } - if (fromTrack == toTrack) { - break; // Just leave in current track - } - } - } - for (Iterator> it = tracks.iterator(); it.hasNext();) { - TimelineTrack track = it.next(); - if (track.isEmpty()) { - it.remove(); - remove(track); - } - } - } - - protected synchronized void adjustTracks(Collection col) { - for (T t : col) { - adjustTrack(t); - } - reAssignTracks(); // TODO: Could become inefficient.... - } - - protected synchronized void cleanTracks(Collection col) { - for (TimelineTrack track : tracks) { - track.removeAll(col); - } - trackMap.keySet().removeAll(col); - reAssignTracks(); - } - - private synchronized void tableChanged(TableModelEvent e) { - switch (e.getType()) { - case TableModelEvent.INSERT: - List itemsInserted = rows.itemsInserted(e.getFirstRow(), e.getLastRow()); - assignTracks(itemsInserted); - reSortTracks(); - fitView(); - recolor(); - break; - case TableModelEvent.UPDATE: - if (e.getLastRow() >= tableModel.getRowCount()) { - reload(); - fitView(); - recolor(); - } - else { - int column = e.getColumn(); - if (info.columnAffectsBounds(column)) { - List itemsUpdated = rows.itemsUpdated(e.getFirstRow(), e.getLastRow()); - adjustTracks(itemsUpdated); - reSortTracks(); - fitView(); - recolor(); - } - } - break; - case TableModelEvent.DELETE: - List itemsDeleted = rows.itemsDeleted(e.getFirstRow(), e.getLastRow()); - cleanTracks(itemsDeleted); - reSortTracks(); - fitView(); - recolor(); - break; - } - } - - protected void removeOldTableModelListeners() { - if (tableModel == null) { - return; - } - tableModel.removeTableModelListener(tableModelListener); - } - - protected void addNewTableModelListeners() { - if (tableModel == null) { - return; - } - tableModel.addTableModelListener(tableModelListener); - } - - public synchronized RowObjectTableModel getTableModel() { - return tableModel; - } - - public synchronized void setTableModel(RowObjectTableModel model, TimelineInfo info) { - if (this.tableModel == model) { - return; - } - removeOldTableModelListeners(); - clear(); - - this.tableModel = model; - this.info = info; - addNewTableModelListeners(); - - reload(); - fitView(); - } - - protected void selectionChanged(ListSelectionEvent e) { - recolor(); - } - - protected void removeOldSelectionModelListeners() { - if (selectionModel == null) { - return; - } - selectionModel.removeListSelectionListener(selectionModelListener); - } - - protected void addNewSelectionModelListeners() { - selectionModel.addListSelectionListener(selectionModelListener); - } - - public synchronized void setSelectionModel(ListSelectionModel selectionModel) { - if (this.selectionModel == selectionModel) { - return; - } - removeOldSelectionModelListeners(); - - this.selectionModel = - selectionModel == null ? new DefaultListSelectionModel() : selectionModel; - addNewSelectionModelListeners(); - - recolor(); - } - - protected synchronized void clear() { - this.rows.clear(); - this.tracks.clear(); - this.trackMap.clear(); - this.removeAll(); - } - - protected synchronized void reload() { - clear(); - if (tableModel == null) { - return; - } - assignTracks(rows.itemsRefreshed()); - recolor(); - } - - protected synchronized void recolor() { - //dumpkeys(Border.class, Border::toString); - List items = rows.items(); - for (int i = 0; i < items.size(); i++) { - T t = items.get(i); - boolean selected = selectionModel.isSelectedIndex(i); - TimelineTrack track = trackMap.get(t); - JComponent comp = track.componentMap.get(t); - boolean hasFocus = comp.hasFocus(); - // TODO: I'd rather not use indexOf, but I suppose I shouldn't expect many tracks? - Color bg = info.getBackgroundColor(t, comp, tracks.indexOf(track), selected, hasFocus); - Color fg = info.getForegroundColor(t, comp, tracks.indexOf(track), selected, hasFocus); - comp.setBackground(bg); - comp.setForeground(fg); - BoundTypeBorder rangeBorder = new BoundTypeBorder(info.getRange(t)); - Border uiBorder = UIManagerWrapper.getBorder( - hasFocus ? "Table.focusCellHighlightBorder" : "Table.cellNoFocusBorder"); - Border border = BorderFactory.createCompoundBorder(rangeBorder, uiBorder); - comp.setBorder(border); - } - repaint(); - } - - protected void fitView() { - Range newViewRange = computeViewRange(); - if (this.viewRange.equals(newViewRange)) { - validate(); - return; - } - this.viewRange = newViewRange; - for (TimelineTrack track : tracks) { - track.setViewRange(newViewRange); - } - validate(); - timelineListeners.fire.viewRangeChanged(newViewRange); - } - - public Range getViewRange() { - return viewRange; - } - - protected void dumpkeys(Class cls, Function fmt) { // For debugging and experimentation - TreeMap sorted = new TreeMap<>(); - UIManager.getDefaults() - .entrySet() - .stream() - .filter(ent -> cls.isInstance(ent.getValue())) - .forEach(ent -> sorted.put(ent.getKey(), cls.cast(ent.getValue()))); - for (Entry ent : sorted.entrySet()) { - System.out.println(String.format("%s=%s", ent.getKey(), fmt.apply(ent.getValue()))); - } - } - - public void setMaxAtLeast(double maxAtLeast) { - if (this.maxAtLeast == maxAtLeast) { - return; - } - this.maxAtLeast = maxAtLeast; - if (!viewRange.contains(maxAtLeast + SLACK)) { - Swing.runIfSwingOrRunLater(() -> fitView()); - } - } - - public double getMaxAtLeast() { - return maxAtLeast; - } - - public boolean isCompressed() { - return isCompressed; - } - - public void setCompressed(boolean isCompressed) { - this.isCompressed = isCompressed; - } - - /** - * Get the cell bounds, relative to the timeline, of the given item - * - * @param t the item - * @return the rectangle, or {@code null} if the given item is not present - */ - public synchronized Rectangle getCellBounds(T t) { - TimelineTrack track = trackMap.get(t); - if (track == null) { - return null; - } - JComponent comp = track.componentMap.get(t); - if (comp == null) { - return null; - } - Rectangle bounds = comp.getBounds(); - Point tl = track.getLocation(); - bounds.x += tl.x; - bounds.y += tl.y; - return bounds; - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java index 648797ea27..650445aadf 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java @@ -19,6 +19,7 @@ import java.util.*; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.lang.Language; import ghidra.util.classfinder.ClassSearcher; @@ -27,9 +28,36 @@ import ghidra.util.classfinder.ClassSearcher; * An abstract implementation of {@link PcodeMachine} suitable as a base for most implementations * *

- * For a complete example of a p-code emulator, see {@link PcodeEmulator}. + * A note regarding terminology: A p-code "machine" refers to any p-code-based machine simulator, + * whether or not it operates on abstract or concrete values. The term "emulator" is reserved for + * machines whose values always include a concrete piece. That piece doesn't necessarily have to be + * a (derivative of) {@link BytesPcodeExecutorStatePiece}, but it usually is. To be called an + * "emulator" implies that {@link PcodeArithmetic#toConcrete(Object, Purpose)} never throws + * {@link ConcretionError} for any value in its state. + * + *

+ * For a complete example of a p-code emulator, see {@link PcodeEmulator}. For an alternative + * implementation incorporating an abstract piece, see the Taint Analyzer. */ public abstract class AbstractPcodeMachine implements PcodeMachine { + + /** + * Check and cast the language to Sleigh + * + *

+ * Sleigh is currently the only realization, but this should give a decent error should that + * ever change. + * + * @param language the language + * @return the same language, cast to Sleigh + */ + protected static SleighLanguage assertSleigh(Language language) { + if (!(language instanceof SleighLanguage)) { + throw new IllegalArgumentException("Emulation requires a sleigh language"); + } + return (SleighLanguage) language; + } + protected final SleighLanguage language; protected final PcodeArithmetic arithmetic; protected final PcodeUseropLibrary library; @@ -48,12 +76,11 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { * Construct a p-code machine with the given language and arithmetic * * @param language the processor language to be emulated - * @param arithmetic the definition of arithmetic p-code ops to be used in emulation */ - public AbstractPcodeMachine(SleighLanguage language, PcodeArithmetic arithmetic) { - this.language = language; - this.arithmetic = arithmetic; + public AbstractPcodeMachine(Language language) { + this.language = assertSleigh(language); + this.arithmetic = createArithmetic(); this.library = createUseropLibrary(); this.stubLibrary = createThreadStubLibrary().compose(library); @@ -65,6 +92,13 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { this.initializer = getPluggableInitializer(language); } + /** + * A factory method to create the arithmetic used by this machine + * + * @return the arithmetic + */ + protected abstract PcodeArithmetic createArithmetic(); + /** * A factory method to create the userop library shared by all threads in this machine * diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/BytesPcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/BytesPcodeThread.java index 7950fe2494..0a62d23025 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/BytesPcodeThread.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/BytesPcodeThread.java @@ -15,8 +15,6 @@ */ package ghidra.pcode.emu; -import ghidra.program.model.address.AddressSpace; - /** * A simple p-code thread that operates on concrete bytes * @@ -24,7 +22,7 @@ import ghidra.program.model.address.AddressSpace; * For a complete example of a p-code emulator, see {@link PcodeEmulator}. This is the default * thread for that emulator. */ -public class BytesPcodeThread extends AbstractModifiedPcodeThread { +public class BytesPcodeThread extends ModifiedPcodeThread { /** * Construct a new thread * @@ -35,17 +33,4 @@ public class BytesPcodeThread extends AbstractModifiedPcodeThread { public BytesPcodeThread(String name, AbstractPcodeMachine machine) { super(name, machine); } - - @Override - protected int getBytesChunk(byte[] res, AddressSpace spc, long off, int size, - boolean stopOnUnintialized) { - byte[] var = state.getVar(spc, off, size, true); - System.arraycopy(var, 0, res, 0, var.length); - return var.length; - } - - @Override - protected void setBytesChunk(byte[] val, AddressSpace spc, long off, int size) { - state.setVar(spc, off, size, true, val); - } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java index 91689afaf6..2fbacf3a17 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/DefaultPcodeThread.java @@ -21,6 +21,7 @@ import java.util.*; import ghidra.app.emulator.Emulator; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.lang.*; import ghidra.program.model.listing.Instruction; @@ -32,7 +33,7 @@ import ghidra.util.Msg; * The default implementation of {@link PcodeThread} suitable for most applications * *

- * When emulating on concrete state, consider using {@link AbstractModifiedPcodeThread}, so that + * When emulating on concrete state, consider using {@link ModifiedPcodeThread}, so that * state modifiers from the older {@link Emulator} are incorporated. In either case, it may be * worthwhile to examine existing state modifiers to ensure they are appropriately represented in * any abstract state. It may be necessary to port them. @@ -129,8 +130,9 @@ public class DefaultPcodeThread implements PcodeThread { * This executor checks for thread suspension and updates the program counter register upon * execution of (external) branches. */ - public class PcodeThreadExecutor extends PcodeExecutor { + public static class PcodeThreadExecutor extends PcodeExecutor { volatile boolean suspended = false; + protected final DefaultPcodeThread thread; /** * Construct the executor @@ -140,9 +142,16 @@ public class DefaultPcodeThread implements PcodeThread { * @param arithmetic the arithmetic of the containing machine * @param state the composite state assigned to the thread */ - public PcodeThreadExecutor(SleighLanguage language, PcodeArithmetic arithmetic, - PcodeExecutorStatePiece state) { - super(language, arithmetic, state); + public PcodeThreadExecutor(DefaultPcodeThread thread) { + super(thread.language, thread.arithmetic, thread.state); + this.thread = thread; + } + + @Override + public void executeSleighLine(String line) { + PcodeProgram program = SleighProgramCompiler.compileProgram(language, "line", + List.of(line + ";"), thread.library); + execute(program, thread.library); } @Override @@ -155,11 +164,24 @@ public class DefaultPcodeThread implements PcodeThread { @Override protected void branchToAddress(Address target) { - overrideCounter(target); + thread.overrideCounter(target); } - public Instruction getInstruction() { - return instruction; + @Override + protected void onMissingUseropDef(PcodeOp op, PcodeFrame frame, String opName, + PcodeUseropLibrary library) { + if (!thread.onMissingUseropDef(op, opName)) { + super.onMissingUseropDef(op, frame, opName, library); + } + } + + /** + * Get the thread owning this executor + * + * @return the thread + */ + public DefaultPcodeThread getThread() { + return thread; } } @@ -171,7 +193,7 @@ public class DefaultPcodeThread implements PcodeThread { protected final InstructionDecoder decoder; protected final PcodeUseropLibrary library; - protected final PcodeThreadExecutor executor; + protected final PcodeThreadExecutor executor; protected final Register pc; protected final Register contextreg; @@ -245,8 +267,8 @@ public class DefaultPcodeThread implements PcodeThread { * * @return the executor */ - protected PcodeThreadExecutor createExecutor() { - return new PcodeThreadExecutor(language, arithmetic, state); + protected PcodeThreadExecutor createExecutor() { + return new PcodeThreadExecutor<>(this); } @Override @@ -317,12 +339,12 @@ public class DefaultPcodeThread implements PcodeThread { @Override public void reInitialize() { - long offset = arithmetic.toConcrete(state.getVar(pc)).longValue(); + long offset = arithmetic.toLong(state.getVar(pc), Purpose.BRANCH); setCounter(language.getDefaultSpace().getAddress(offset, true)); if (contextreg != Register.NO_CONTEXT) { try { - BigInteger ctx = arithmetic.toConcrete(state.getVar(contextreg), true); + BigInteger ctx = arithmetic.toBigInteger(state.getVar(contextreg), Purpose.CONTEXT); assignContext(new RegisterValue(contextreg, ctx)); } catch (AccessPcodeExecutionException e) { @@ -442,25 +464,34 @@ public class DefaultPcodeThread implements PcodeThread { } /** - * An extension point for hooking instruction execution before the fact + * Extension point: Extra behavior before executing an instruction * *

* This is currently used for incorporating state modifiers from the older {@link Emulator} * framework. There is likely utility here when porting those to this framework. */ protected void preExecuteInstruction() { - // Extension point } /** - * An extension point for hooking instruction execution after the fact + * Extension point: Extra behavior after executing an instruction * *

* This is currently used for incorporating state modifiers from the older {@link Emulator} * framework. There is likely utility here when porting those to this framework. */ protected void postExecuteInstruction() { - // Extension point + } + + /** + * Extension point: Behavior when a p-code userop definition is not found + * + * @param op the op + * @param opName the name of the p-code userop + * @return true if handle, false if still undefined + */ + protected boolean onMissingUseropDef(PcodeOp op, String opName) { + return false; } @Override @@ -514,6 +545,16 @@ public class DefaultPcodeThread implements PcodeThread { executor.suspended = suspended; } + @Override + public SleighLanguage getLanguage() { + return language; + } + + @Override + public PcodeArithmetic getArithmetic() { + return arithmetic; + } + @Override public PcodeExecutor getExecutor() { return executor; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractModifiedPcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java similarity index 81% rename from Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractModifiedPcodeThread.java rename to Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java index 1b18322b8d..be8d0d2e07 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractModifiedPcodeThread.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java @@ -20,7 +20,8 @@ import java.lang.reflect.Constructor; import ghidra.app.emulator.Emulator; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emulate.*; -import ghidra.pcode.exec.*; +import ghidra.pcode.exec.ConcretionError; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.memstate.MemoryState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; @@ -29,7 +30,13 @@ import ghidra.program.model.pcode.PcodeOp; import ghidra.util.Msg; /** - * A p-code thread which incorporates per-architecture state modifiers on concrete bytes + * A p-code thread which incorporates per-architecture state modifiers + * + *

+ * All machines that include a concrete state piece, i.e., all emulators, should use threads derived + * from this class. This implementation assumes that the modified state can be concretized. This + * doesn't necessarily require the machine to be a concrete emulator, but an abstract machine must + * avoid or handle {@link ConcretionError}s arising from state modifiers. * *

* For a complete example of a p-code emulator, see {@link PcodeEmulator}. @@ -39,7 +46,7 @@ import ghidra.util.Msg; * incorporated into threads extended from this abstract class, so that they do not yet need to be * ported to this emulator. */ -public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread { +public class ModifiedPcodeThread extends DefaultPcodeThread { /** * Glue for incorporating state modifiers @@ -106,33 +113,6 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< } } - /** - * Glue for incorporating state modifiers - * - *

- * This allows the modifiers to provider userop definitions. - */ - protected class GluePcodeThreadExecutor extends PcodeThreadExecutor { - public GluePcodeThreadExecutor(SleighLanguage language, PcodeArithmetic arithmetic, - PcodeExecutorStatePiece state) { - super(language, arithmetic, state); - } - - @Override - public void executeCallother(PcodeOp op, PcodeFrame frame, - PcodeUseropLibrary library) { - // Prefer one in the library. Fall-back to state modifier's impl - try { - super.executeCallother(op, frame, library); - } - catch (SleighLinkException e) { - if (modifier == null || !modifier.executeCallOther(op)) { - throw e; - } - } - } - } - // Part of the glue that makes existing state modifiers work in new emulation framework protected final EmulateInstructionStateModifier modifier; protected final Emulate emulate; @@ -146,7 +126,7 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< * @param name the name of the new thread * @param machine the machine to which the new thread belongs */ - public AbstractModifiedPcodeThread(String name, AbstractPcodeMachine machine) { + public ModifiedPcodeThread(String name, AbstractPcodeMachine machine) { super(name, machine); /** @@ -190,25 +170,28 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< } } - @Override - protected PcodeThreadExecutor createExecutor() { - return new GluePcodeThreadExecutor(language, arithmetic, state); - } - /** * Called by a state modifier to read concrete bytes from the thread's state * * @see {@link MemoryState#getChunk(byte[], AddressSpace, long, int, boolean)} */ - protected abstract int getBytesChunk(byte[] res, AddressSpace spc, long off, int size, - boolean stopOnUnintialized); + protected int getBytesChunk(byte[] res, AddressSpace spc, long off, int size, + boolean stopOnUnintialized) { + T t = state.getVar(spc, off, size, true); + byte[] val = arithmetic.toConcrete(t, Purpose.OTHER); + System.arraycopy(val, 0, res, 0, val.length); + return val.length; + } /** * Called by a state modifier to write concrete bytes to the thread's state * * @see {@link MemoryState#setChunk(byte[], AddressSpace, long, int)} */ - protected abstract void setBytesChunk(byte[] val, AddressSpace spc, long off, int size); + protected void setBytesChunk(byte[] val, AddressSpace spc, long off, int size) { + T t = arithmetic.fromConst(val); + state.setVar(spc, off, size, true, t); + } @Override protected void preExecuteInstruction() { @@ -225,4 +208,12 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< frame.getBranched(), getCounter()); } } + + @Override + protected boolean onMissingUseropDef(PcodeOp op, String opName) { + if (modifier != null) { + return modifier.executeCallOther(op); + } + return super.onMissingUseropDef(op, opName); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeEmulator.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeEmulator.java index 3842e7e7e1..b0011f5493 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeEmulator.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeEmulator.java @@ -17,10 +17,11 @@ package ghidra.pcode.emu; import java.util.List; -import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; import ghidra.pcode.emu.sys.EmuSyscallLibrary; import ghidra.pcode.exec.*; import ghidra.program.model.address.Address; +import ghidra.program.model.lang.Language; /** * A p-code machine which executes on concrete bytes and incorporates per-architecture state @@ -36,8 +37,8 @@ import ghidra.program.model.address.Address; * Every class should be extensible and have overridable factory methods so that those extensions * can be incorporated into even more capable emulators. Furthermore, many components, e.g., * {@link PcodeExecutorState} were designed with composition in mind. Referring to examples, it is - * generally pretty easy to extend the emulator via composition. Search for references to - * {@link PairedPcodeExecutorState} to find such examples. + * straightforward to extend the emulator via composition. Consider using {@link AuxPcodeEmulator} + * or one of its derivatives to create a concrete-plus-auxiliary style emulator. * *

  * emulator      : PcodeMachine
@@ -69,9 +70,8 @@ import ghidra.program.model.address.Address;
  * components. For state, the composition directs memory accesses to the machine's state and
  * register accesses to the thread's state. (Accesses to the "unique" space are also directed to the
  * thread's state.) This properly emulates the thread semantics of most platforms. For the userop
- * library, composition is achieved simply via
- * {@link PcodeUseropLibrary#compose(PcodeUseropLibrary)}. Thus, each invocation is directed to the
- * library that exports the invoked userop.
+ * library, composition is achieved via {@link PcodeUseropLibrary#compose(PcodeUseropLibrary)}.
+ * Thus, each invocation is directed to the library that exports the invoked userop.
  * 
  * 

* Each thread creates an {@link InstructionDecoder} and a {@link PcodeExecutor}, providing the @@ -83,21 +83,21 @@ import ghidra.program.model.address.Address; * follows: 1) decode the current instruction, 2) generate that instruction's p-code, 3) feed the * code to the executor, 4) resolve the outcome and advance the program counter, then 5) repeat. So * long as the arithmetic and state objects agree in type, a p-code machine can be readily - * implemented to manipulate values of that type. Both arithmetic and state are readily composed - * using {@link PairedPcodeArithmetic} and {@link PairedPcodeExecutorState} or - * {@link PairedPcodeExecutorStatePiece}. + * implemented to manipulate values of that type. * *

* This concrete emulator chooses a {@link BytesPcodeArithmetic} based on the endianness of the * target language. Its threads are {@link BytesPcodeThread}. The shared and thread-local states are - * all {@link BytesPcodeExecutorState}. That state class can be extended to read through to some - * other backing object. For example, the memory state could read through to an imported program - * image, which allows the emulator's memory to be loaded lazily. The default userop library is - * empty. For many use cases, it will be necessary to override {@link #createUseropLibrary()} if - * only to implement the language-defined userops. If needed, simulation of the host operating - * system is typically achieved by implementing the {@code syscall} userop. The fidelity of that - * simulation depends on the use case. See {@link EmuSyscallLibrary} and its implementations to see - * what simulations are available "out of the box." + * all {@link BytesPcodeExecutorState}. That pieces of that state can be extended to read through to + * some other backing object. For example, the memory state could read through to an imported + * program image, which allows the emulator's memory to be loaded lazily. + * + *

+ * The default userop library is empty. For many use cases, it will be necessary to override + * {@link #createUseropLibrary()} if only to implement the language-defined userops. If needed, + * simulation of the host operating system is typically achieved by implementing the {@code syscall} + * userop. The fidelity of that simulation depends on the use case. See {@link EmuSyscallLibrary} + * and its implementations to see what simulators are available "out of the box." * *

* Alternatively, if the target program never invokes system calls directly, but rather via @@ -117,8 +117,13 @@ public class PcodeEmulator extends AbstractPcodeMachine { * * @param language the language of the target processor */ - public PcodeEmulator(SleighLanguage language) { - super(language, BytesPcodeArithmetic.forLanguage(language)); + public PcodeEmulator(Language language) { + super(language); + } + + @Override + protected PcodeArithmetic createArithmetic() { + return BytesPcodeArithmetic.forLanguage(language); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeThread.java index ba8481576c..7b5f7d1871 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeThread.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeThread.java @@ -47,7 +47,7 @@ public interface PcodeThread { PcodeMachine getMachine(); /** - * Set the emulator's counter without writing to its machine state + * Set the thread's program counter without writing to its executor state * * @see #overrideCounter(Address) * @param counter the new target address @@ -62,7 +62,7 @@ public interface PcodeThread { Address getCounter(); /** - * Set the emulator's counter and write the PC of its machine state + * Set the thread's program counter and write the pc register of its executor state * * @see #setCounter(Address) * @param counter the new target address @@ -70,7 +70,7 @@ public interface PcodeThread { void overrideCounter(Address counter); /** - * Adjust the emulator's decoding context without writing to its machine state + * Adjust the thread's decoding context without writing to its executor state * *

* As in {@link RegisterValue#assign(Register, RegisterValue)}, only those bits having a value @@ -82,14 +82,14 @@ public interface PcodeThread { void assignContext(RegisterValue context); /** - * Get the emulator's decoding context + * Get the thread's decoding context * * @return the context */ RegisterValue getContext(); /** - * Adjust the emulator's parsing context and write the contextreg of its machine state + * Adjust the thread's decoding context and write the contextreg of its executor state * * @see #assignContext(RegisterValue) * @param context the new context @@ -100,12 +100,8 @@ public interface PcodeThread { * Set the context at the current counter to the default given by the language * *

- * This also writes the context to the machine's state. For languages without context, this call + * This also writes the context to the thread's state. For languages without context, this call * does nothing. - * - *

- * TODO: Seems to me, since this method must be called upon creating any emulator thread, that's - * evidence the trace's context manager is not providing correct defaults. */ void overrideContextWithDefault(); @@ -119,9 +115,9 @@ public interface PcodeThread { * *

* Note because of the way Ghidra and Sleigh handle delay slots, the execution of an instruction - * with delay slots cannot be separated from the following instructions filling those slots. It - * and its slotted instructions are executed in a single "step." However, stepping the - * individual p-code ops is still possible using {@link #stepPcodeOp(PcodeFrame)}. + * with delay slots cannot be separated from the instructions filling those slots. It and its + * slotted instructions are executed in a single "step." However, stepping the individual p-code + * ops is still possible using {@link #stepPcodeOp()}. */ void stepInstruction(); @@ -279,37 +275,33 @@ public interface PcodeThread { * *

* When {@link #run()} is invoked by a dedicated thread, suspending the pcode thread is the most - * reliable way to halt execution. Note the emulator will halt mid instruction. If this is not + * reliable way to halt execution. Note the emulator may halt mid instruction. If this is not * desired, then upon catching the exception, un-suspend the p-code thread and call * {@link #finishInstruction()} or {@link #dropInstruction()}. */ void setSuspended(boolean suspended); /** - * Get the thread's SLEIGH language (processor model) + * Get the thread's Sleigh language (processor model) * * @return the language */ - default SleighLanguage getLanguage() { - return getExecutor().getLanguage(); - } + SleighLanguage getLanguage(); /** * Get the thread's p-code arithmetic * * @return the arithmetic */ - default PcodeArithmetic getArithmetic() { - return getExecutor().getArithmetic(); - } + PcodeArithmetic getArithmetic(); /** * Get the thread's p-code executor * *

- * This can be used to execute inject p-code execution, e.g., as part of implementing a userop, - * or as part of testing, outside the emulator's usual control flow. Any new frame generated by - * the executor is ignored by the emulator. It retains the instruction frame, if any. Note that + * This can be used to execute injected p-code, e.g., as part of implementing a userop, or as + * part of testing, outside the thread's usual control flow. Any new frame generated by the + * executor is ignored by the thread. It retains the instruction frame, if any. Note that * suspension is implemented by the executor, so if this p-code thread is suspended, the * executor cannot execute any code. * @@ -318,8 +310,7 @@ public interface PcodeThread { PcodeExecutor getExecutor(); /** - * Get the complete userop library for this thread, including userops for controlling this - * thread + * Get the complete userop library for this thread * * @return the library */ @@ -336,7 +327,7 @@ public interface PcodeThread { ThreadPcodeExecutorState getState(); /** - * Override the p-code at the given address with the given SLEIGH source for only this thread + * Override the p-code at the given address with the given Sleigh source for only this thread * *

* This works the same {@link PcodeMachine#inject(Address, List)} but on a per-thread basis. @@ -353,7 +344,7 @@ public interface PcodeThread { * Remove the per-thread inject, if present, at the given address * *

- * This has no affect on machine-level injects. If there is one present, it will still override + * This has no effect on machine-level injects. If there is one present, it will still override * this thread's p-code if execution reaches the address. * * @param address the address to clear diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/SleighInstructionDecoder.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/SleighInstructionDecoder.java index c7f4b42ad2..17ea546ef3 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/SleighInstructionDecoder.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/SleighInstructionDecoder.java @@ -16,6 +16,7 @@ package ghidra.pcode.emu; import ghidra.pcode.emulate.InstructionDecodeException; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.disassemble.Disassembler; import ghidra.program.disassemble.DisassemblerMessageListener; @@ -72,7 +73,8 @@ public class SleighInstructionDecoder implements InstructionDecoder { public Instruction decodeInstruction(Address address, RegisterValue context) { lastMsg = DEFAULT_ERROR; // Always re-parse block in case bytes change - block = disassembler.pseudoDisassembleBlock(state.getConcreteBuffer(address), context, 1); + block = disassembler.pseudoDisassembleBlock( + state.getConcreteBuffer(address, Purpose.DECODE), context, 1); instruction = block == null ? null : block.getInstructionAt(address); if (instruction == null) { throw new InstructionDecodeException(lastMsg, address); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java index 58fac6cd77..aaab335aee 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/ThreadPcodeExecutorState.java @@ -15,20 +15,25 @@ */ package ghidra.pcode.emu; +import java.util.Objects; + +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutorState; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.mem.MemBuffer; /** - * A p-code executor state that multiplexes shared and thread-local states for use in a - * multi-threaded emulator + * A p-code executor state that multiplexes shared and thread-local states for use in a machine that + * models multi-threading * * @param the type of values stored in the states */ public class ThreadPcodeExecutorState implements PcodeExecutorState { protected final PcodeExecutorState sharedState; protected final PcodeExecutorState localState; + protected final PcodeArithmetic arithmetic; /** * Create a multiplexed state @@ -39,8 +44,15 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { */ public ThreadPcodeExecutorState(PcodeExecutorState sharedState, PcodeExecutorState localState) { + assert Objects.equals(sharedState.getArithmetic(), localState.getArithmetic()); this.sharedState = sharedState; this.localState = localState; + this.arithmetic = sharedState.getArithmetic(); + } + + @Override + public PcodeArithmetic getArithmetic() { + return arithmetic; } /** @@ -54,40 +66,26 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { } @Override - public T longToOffset(AddressSpace space, long l) { + public void setVar(AddressSpace space, T offset, int size, boolean quantize, T val) { if (isThreadLocalSpace(space)) { - return localState.longToOffset(space, l); - } - else { - return sharedState.longToOffset(space, l); + localState.setVar(space, offset, size, quantize, val); + return; } + sharedState.setVar(space, offset, size, quantize, val); } @Override - public void setVar(AddressSpace space, T offset, int size, boolean truncateAddressableUnit, - T val) { + public T getVar(AddressSpace space, T offset, int size, boolean quantize) { if (isThreadLocalSpace(space)) { - localState.setVar(space, offset, size, truncateAddressableUnit, val); - } - else { - sharedState.setVar(space, offset, size, truncateAddressableUnit, val); + return localState.getVar(space, offset, size, quantize); } + return sharedState.getVar(space, offset, size, quantize); } @Override - public T getVar(AddressSpace space, T offset, int size, boolean truncateAddressableUnit) { - if (isThreadLocalSpace(space)) { - return localState.getVar(space, offset, size, truncateAddressableUnit); - } - else { - return sharedState.getVar(space, offset, size, truncateAddressableUnit); - } - } - - @Override - public MemBuffer getConcreteBuffer(Address address) { + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { assert !isThreadLocalSpace(address.getAddressSpace()); - return sharedState.getConcreteBuffer(address); + return sharedState.getConcreteBuffer(address, purpose); } /** diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java new file mode 100644 index 0000000000..1c962ccfe5 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxEmulatorPartsFactory.java @@ -0,0 +1,143 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.auxiliary; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.*; +import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; +import ghidra.pcode.exec.*; +import ghidra.program.model.lang.Language; + +/** + * An auxiliary emulator parts factory for stand-alone emulation + * + *

+ * This can manufacture all the parts needed for a stand-alone emulator with concrete and some + * implementation-defined auxiliary state. More capable emulators may also use many of these parts. + * Usually, the additional capabilities deal with how state is loaded and stored or otherwise made + * available to the user. The pattern of use for a stand-alone emulator is usually in a script: + * Create an emulator, initialize its state, write instructions to its memory, create and initialize + * a thread, point its counter at the instructions, instrument, step/run, inspect, and finally + * terminate. + * + *

+ * This "parts factory" pattern aims to flatten the extension points of the + * {@link AbstractPcodeMachine} and its components into a single class. Its use is not required, but + * may make things easier. It also encapsulates some "special knowledge," that might not otherwise + * be obvious to a developer, e.g., it creates the concrete state pieces, so the developer need not + * guess (or keep up to date) the concrete state piece classes to instantiate. + * + *

+ * The factory itself should be a singleton object. See the Taint Analyzer for a complete solution + * using this interface. + * + * @param the type of auxiliary values + */ +public interface AuxEmulatorPartsFactory { + /** + * Get the arithmetic for the emulator given a target langauge + * + * @param language the language + * @return the arithmetic + */ + PcodeArithmetic getArithmetic(Language language); + + /** + * Create the userop library for the emulator (used by all threads) + * + * @param emulator the emulator + * @return the userop library + */ + PcodeUseropLibrary> createSharedUseropLibrary(AuxPcodeEmulator emulator); + + /** + * Create a stub userop library for the emulator's threads + * + * @param emulator the emulator + * @return the library of stubs + */ + PcodeUseropLibrary> createLocalUseropStub(AuxPcodeEmulator emulator); + + /** + * Create a userop library for a given thread + * + * @param emulator the emulator + * @param thread the thread + * @return the userop library + */ + PcodeUseropLibrary> createLocalUseropLibrary(AuxPcodeEmulator emulator, + PcodeThread> thread); + + /** + * Create an executor for the given thread + * + *

+ * This allows the implementor to override or intercept the logic for individual p-code + * operations that would not otherwise be possible in the arithmetic, e.g., to print diagnostics + * on a conditional branch. + * + * @param emulator the emulator + * @param thread the thread + * @return the executor + */ + default PcodeThreadExecutor> createExecutor( + AuxPcodeEmulator emulator, DefaultPcodeThread> thread) { + return new PcodeThreadExecutor<>(thread); + } + + /** + * Create a thread with the given name + * + * @param emulator the emulator + * @param name the thread's name + * @return the thread + */ + default PcodeThread> createThread(AuxPcodeEmulator emulator, String name) { + return new AuxPcodeThread<>(name, emulator); + } + + /** + * Create the shared (memory) state of a new stand-alone emulator + * + *

+ * This is usually composed of pieces using {@link PairedPcodeExecutorStatePiece}, but it does + * not have to be. It must incorporate the concrete piece provided. It should be self contained + * and relatively fast. + * + * @param emulator the emulator + * @param concrete the concrete piece + * @return the composed state + */ + PcodeExecutorState> createSharedState(AuxPcodeEmulator emulator, + BytesPcodeExecutorStatePiece concrete); + + /** + * Create the local (register) state of a new stand-alone emulator + * + *

+ * This is usually composed of pieces using {@link PairedPcodeExecutorStatePiece}, but it does + * not have to be. It must incorporate the concrete piece provided. It should be self contained + * and relatively fast. + * + * @param emulator the emulator + * @param thread the thread + * @param concrete the concrete piece + * @return the composed state + */ + PcodeExecutorState> createLocalState(AuxPcodeEmulator emulator, + PcodeThread> thread, BytesPcodeExecutorStatePiece concrete); +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java new file mode 100644 index 0000000000..07f31b67a4 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java @@ -0,0 +1,90 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.auxiliary; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.AbstractPcodeMachine; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.*; +import ghidra.program.model.lang.Language; + +/** + * A stand-alone emulator whose parts are manufactured by a {@link AuxEmulatorPartsFactory} + * + *

+ * See the parts factory interface: {@link AuxEmulatorPartsFactory}. Also see the Taint Analyzer for + * a complete solution based on this class. + * + * @param the type of auxiliary values + */ +public abstract class AuxPcodeEmulator extends AbstractPcodeMachine> { + /** + * Create a new emulator + * + * @param language the language (processor model) + */ + public AuxPcodeEmulator(Language language) { + super(language); + } + + /** + * Get the factory that manufactures parts for this emulator + * + * @implNote This should just return a singleton, since it is called repeatedly (without + * caching) during emulator and thread construction. If, for some reason, a singleton + * is not suitable, then this should instantiate it just once and cache the factory + * itself. If cached, it should be done in a thread-safe manner. + * + * @return the factory + */ + protected abstract AuxEmulatorPartsFactory getPartsFactory(); + + @Override + protected PcodeArithmetic> createArithmetic() { + return new PairedPcodeArithmetic<>( + BytesPcodeArithmetic.forLanguage(language), + getPartsFactory().getArithmetic(language)); + } + + @Override + protected PcodeUseropLibrary> createUseropLibrary() { + return getPartsFactory().createSharedUseropLibrary(this); + } + + @Override + protected PcodeUseropLibrary> createThreadStubLibrary() { + return getPartsFactory().createLocalUseropStub(this); + } + + @Override + protected PcodeExecutorState> createSharedState() { + return getPartsFactory().createSharedState(this, + new BytesPcodeExecutorStatePiece(language)); + } + + @Override + protected PcodeExecutorState> createLocalState( + PcodeThread> thread) { + return getPartsFactory().createLocalState(this, thread, + new BytesPcodeExecutorStatePiece(language)); + } + + @Override + protected PcodeThread> createThread(String name) { + return getPartsFactory().createThread(this, name); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeThread.java new file mode 100644 index 0000000000..0b63ab69be --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeThread.java @@ -0,0 +1,57 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.auxiliary; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.ModifiedPcodeThread; +import ghidra.pcode.exec.PcodeUseropLibrary; + +/** + * The default thread for {@link AuxPcodeEmulator} + * + *

+ * Generally, extending this class should not be necessary, as it already defers to the emulator's + * parts factory + * + * @param the type of auxiliary values + */ +public class AuxPcodeThread extends ModifiedPcodeThread> { + + public AuxPcodeThread(String name, AuxPcodeEmulator emulator) { + super(name, emulator); + } + + @Override + public AuxPcodeEmulator getMachine() { + return (AuxPcodeEmulator) super.getMachine(); + } + + protected AuxEmulatorPartsFactory getPartsFactory() { + return getMachine().getPartsFactory(); + } + + @Override + protected PcodeUseropLibrary> createUseropLibrary() { + return super.createUseropLibrary().compose( + getPartsFactory().createLocalUseropLibrary(getMachine(), this)); + } + + @Override + protected PcodeThreadExecutor> createExecutor() { + return getPartsFactory().createExecutor(getMachine(), this); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java index 1287eb9af4..4107f7f041 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java @@ -24,6 +24,7 @@ import ghidra.framework.Application; import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.unix.EmuUnixFileSystem; import ghidra.pcode.emu.unix.EmuUnixUser; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeExecutor; import ghidra.pcode.exec.PcodeExecutorStatePiece; import ghidra.program.model.data.DataTypeManager; @@ -91,7 +92,7 @@ public class EmuLinuxAmd64SyscallUseropLibrary extends AbstractEmuLinuxSyscal @Override public long readSyscallNumber(PcodeExecutorStatePiece state) { - return machine.getArithmetic().toConcrete(state.getVar(regRAX)).longValue(); + return machine.getArithmetic().toLong(state.getVar(regRAX), Purpose.OTHER); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java index c27f03c0c7..d498468910 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java @@ -21,15 +21,17 @@ import java.util.List; import generic.jar.ResourceFile; import ghidra.framework.Application; -import ghidra.pcode.emu.DefaultPcodeThread; +import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.unix.EmuUnixFileSystem; import ghidra.pcode.emu.unix.EmuUnixUser; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.FileDataTypeManager; import ghidra.program.model.lang.Register; import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.PcodeOp; /** * A system call library simulating Linux for x86 (32-bit) @@ -91,7 +93,7 @@ public class EmuLinuxX86SyscallUseropLibrary extends AbstractEmuLinuxSyscallU @Override public long readSyscallNumber(PcodeExecutorStatePiece state) { - return machine.getArithmetic().toConcrete(state.getVar(regEAX)).longValue(); + return machine.getArithmetic().toLong(state.getVar(regEAX), Purpose.OTHER); } @Override @@ -102,26 +104,32 @@ public class EmuLinuxX86SyscallUseropLibrary extends AbstractEmuLinuxSyscallU return true; } + /** + * Implement this to detect and interpret the {@code INT 0x80} instruction as the syscall + * convention + * + * @param executor to receive the executor + * @param library to receive the userop library, presumably replete with syscalls + * @param number the interrupt number + * @return the address of the fall-through, to hack the {@link PcodeOp#CALLIND} + */ @PcodeUserop public T swi(@OpExecutor PcodeExecutor executor, @OpLibrary PcodeUseropLibrary library, T number) { PcodeArithmetic arithmetic = executor.getArithmetic(); - long intNo = arithmetic.toConcrete(number).longValue(); + long intNo = arithmetic.toLong(number, Purpose.OTHER); if (intNo == 0x80) { // A CALLIND follows to the return of swi().... OK. // We'll just make that "fall through" instead T next = executor.getState().getVar(regEIP); - DefaultPcodeThread.PcodeThreadExecutor te = - (DefaultPcodeThread.PcodeThreadExecutor) executor; + PcodeThreadExecutor te = (PcodeThreadExecutor) executor; int pcSize = regEIP.getNumBytes(); - int iLen = te.getInstruction().getLength(); - next = arithmetic.binaryOp(PcodeArithmetic.INT_ADD, pcSize, pcSize, next, pcSize, + int iLen = te.getThread().getInstruction().getLength(); + next = arithmetic.binaryOp(PcodeOp.INT_ADD, pcSize, pcSize, next, pcSize, arithmetic.fromConst(iLen, pcSize)); syscall(executor, library); return next; } - else { - throw new PcodeExecutionException("Unknown interrupt: 0x" + Long.toString(intNo, 16)); - } + throw new PcodeExecutionException("Unknown interrupt: 0x" + Long.toString(intNo, 16)); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/BytesEmuFileContents.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/BytesEmuFileContents.java new file mode 100644 index 0000000000..3d8b891227 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/BytesEmuFileContents.java @@ -0,0 +1,65 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.sys; + +/** + * A concrete in-memory bytes store for simulated file contents + * + *

+ * Note that currently, the total contents cannot exceed a Java array, so the file must remain less + * than 2GB in size. + */ +public class BytesEmuFileContents implements EmuFileContents { + protected static final int INIT_CONTENT_SIZE = 1024; + + protected byte[] content = new byte[INIT_CONTENT_SIZE]; + + @Override + public synchronized long read(long offset, byte[] buf, long fileSize) { + // We're using an in-memory array, so limited to int offsets + if (offset > Integer.MAX_VALUE) { + throw new EmuIOException("Offset is past end of file"); + } + long len = Math.min(buf.length, fileSize - offset); + if (len < 0) { + throw new EmuIOException("Offset is past end of file"); + } + System.arraycopy(content, (int) offset, buf, 0, (int) len); + return len; + } + + @Override + public synchronized long write(long offset, byte[] buf, long curSize) { + long newSize = offset + buf.length; + if (newSize > Integer.MAX_VALUE || newSize < 0) { + throw new EmuIOException("File size cannot exceed " + Integer.MAX_VALUE + " bytes"); + } + if (newSize > content.length) { + byte[] grown = new byte[content.length * 2]; + System.arraycopy(content, 0, grown, 0, (int) curSize); + content = grown; + } + System.arraycopy(buf, 0, content, (int) offset, buf.length); + return buf.length; + } + + @Override + public synchronized void truncate() { + if (content.length > INIT_CONTENT_SIZE) { + content = new byte[INIT_CONTENT_SIZE]; + } + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuFileContents.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuFileContents.java new file mode 100644 index 0000000000..cf2e5be28a --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuFileContents.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.pcode.emu.sys; + +/** + * The content store to back a simulated file + * + *

+ * TODO: Could/should this just be the same interface as an execute state? If so, we'd need to + * formalize the store interface and require one for each address space in the state. Sharing that + * interface may not be a good idea.... I think implementors can use a common realization if that + * suits them. + * + *

+ * TODO: Actually, a better idea might be to introduce an address factory with custom spaces into + * the emulator. Then a library/file could just create an address space and use the state to store + * and retrieve the file contents. Better yet, when written down, those contents and markings could + * appear in the user's trace. + * + * @param the type of values in the file + */ +public interface EmuFileContents { + /** + * Copy values from the file into the given buffer + * + * @param offset the offset in the file to read + * @param buf the destination buffer, whose size must be known + * @param fileSize the size of the file + * @return the number of bytes (not necessarily concrete) read + */ + long read(long offset, T buf, long fileSize); + + /** + * Write values from the given buffer into the file + * + * @param offset the offset in the file to write + * @param buf the source buffer, whose size must be known + * @param curSize the current size of the file + * @return the number of bytes (not necessarily concrete) written + */ + long write(long offset, T buf, long curSize); + + /** + * Erase the contents + * + *

+ * Note that the file's size will be set to 0, so actual erasure of the contents may not be + * necessary, but if the contents are expensive to store, they ought to be disposed. + */ + void truncate(); +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java index 4a59a90661..d4bde08d83 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java @@ -18,15 +18,28 @@ package ghidra.pcode.emu.sys; import java.math.BigInteger; import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; /** * A simulated process (or thread group) has exited + * + *

+ * The simulator should catch this exception and terminate accordingly. Continuing execution of the + * emulator beyond this exception will cause undefined behavior. */ public class EmuProcessExitedException extends EmuSystemException { + /** + * Attempt to concretize a value and convert it to hex + * + * @param the type of the status + * @param arithmetic the arithmetic to operate on the value + * @param status the status value + * @return the hex string, or the error message + */ public static String tryConcereteToString(PcodeArithmetic arithmetic, T status) { try { - BigInteger value = arithmetic.toConcrete(status); + BigInteger value = arithmetic.toBigInteger(status, Purpose.INSPECT); return value.toString(); } catch (Exception e) { diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java index 25c9b47cca..4cbedc8edc 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java @@ -24,7 +24,6 @@ import generic.jar.ResourceFile; import ghidra.framework.Application; import ghidra.pcode.exec.*; import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.*; -import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.listing.Function; @@ -42,11 +41,11 @@ import ghidra.program.model.symbol.*; * userop is automatically included in the userop library. The simplest means of implementing a * syscall library is probably via {@link AnnotatedEmuSyscallUseropLibrary}. It implements this * interface and extends {@link AnnotatedPcodeUseropLibrary}. In addition, it provides its own - * annotation system for exporting Java methods as system calls. + * annotation system for exporting userops as system calls. * * @param the type of data processed by the system calls, typically {@code byte[]} */ -public interface EmuSyscallLibrary { +public interface EmuSyscallLibrary extends PcodeUseropLibrary { String SYSCALL_SPACE_NAME = "syscall"; String SYSCALL_CONVENTION_NAME = "syscall"; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/PairedEmuFileContents.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/PairedEmuFileContents.java new file mode 100644 index 0000000000..ed4ec315f7 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/PairedEmuFileContents.java @@ -0,0 +1,62 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.sys; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.exec.PairedPcodeExecutorStatePiece; + +/** + * The analog of {@link PairedPcodeExecutorStatePiece} for simulated file contents + * + * @param the type of values for the left + * @param the type of values for the right + */ +public class PairedEmuFileContents implements EmuFileContents> { + protected final EmuFileContents left; + protected final EmuFileContents right; + + /** + * Create a paired file contents + * + * @param left the left contents + * @param right the right contents + */ + public PairedEmuFileContents(EmuFileContents left, EmuFileContents right) { + this.left = left; + this.right = right; + } + + @Override + public long read(long offset, Pair buf, long fileSize) { + long result = left.read(offset, buf.getLeft(), fileSize); + right.read(offset, buf.getRight(), fileSize); + return result; + } + + @Override + public long write(long offset, Pair buf, long curSize) { + long result = left.write(offset, buf.getLeft(), curSize); + right.write(offset, buf.getRight(), curSize); + return result; + } + + @Override + public void truncate() { + left.truncate(); + right.truncate(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java index cd55065e9e..55a518e35e 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java @@ -15,6 +15,11 @@ */ package ghidra.pcode.emu.unix; +import ghidra.pcode.emu.sys.EmuFileContents; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.util.MathUtilities; + /** * An abstract file contained in an emulated file system * @@ -25,8 +30,10 @@ package ghidra.pcode.emu.unix; * @param the type of values stored in the file */ public abstract class AbstractEmuUnixFile implements EmuUnixFile { + protected final String pathname; - protected final EmuUnixFileStat stat = createStat(); + protected final EmuUnixFileStat stat; + protected EmuFileContents contents; /** * Construct a new file @@ -41,7 +48,9 @@ public abstract class AbstractEmuUnixFile implements EmuUnixFile { */ public AbstractEmuUnixFile(String pathname, int mode) { this.pathname = pathname; - stat.st_mode = mode; + this.stat = createStat(); + this.stat.st_mode = mode; + this.contents = createDefaultContents(); } /** @@ -53,6 +62,13 @@ public abstract class AbstractEmuUnixFile implements EmuUnixFile { return new EmuUnixFileStat(); } + /** + * A factory method for the file's default contents + * + * @return the contents + */ + protected abstract EmuFileContents createDefaultContents(); + @Override public String getPathname() { return pathname; @@ -62,4 +78,25 @@ public abstract class AbstractEmuUnixFile implements EmuUnixFile { public EmuUnixFileStat getStat() { return stat; } + + @Override + public T read(PcodeArithmetic arithmetic, T offset, T buf) { + long off = arithmetic.toLong(offset, Purpose.OTHER); + long len = contents.read(off, buf, stat.st_size); + return arithmetic.fromConst(len, (int) arithmetic.sizeOf(offset)); + } + + @Override + public T write(PcodeArithmetic arithmetic, T offset, T buf) { + long off = arithmetic.toLong(offset, Purpose.OTHER); + long len = contents.write(off, buf, stat.st_size); + stat.st_size = MathUtilities.unsignedMax(stat.st_size, off + len); + return arithmetic.fromConst(len, (int) arithmetic.sizeOf(offset)); + } + + @Override + public synchronized void truncate() { + stat.st_size = 0; + contents.truncate(); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java index c0a9eeb8a8..62fd7ccbab 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java @@ -23,6 +23,7 @@ import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary; import ghidra.pcode.emu.sys.EmuProcessExitedException; import ghidra.pcode.emu.unix.EmuUnixFileSystem.OpenFlag; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.StringDataInstance; import ghidra.program.model.data.StringDataType; @@ -33,6 +34,10 @@ import ghidra.program.model.mem.MemBuffer; * An abstract library of UNIX system calls, suitable for use with any processor * *

+ * See the UNIX manual pages for more information about each specific system call, error numbers, + * etc. + * + *

* TODO: The rest of the system calls common to UNIX. * * @param the type of values processed by the library @@ -41,10 +46,7 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary extends AnnotatedEmuSyscallUseropLibrary { /** - * The errno values as defined by the simulator - * - *

- * See a UNIX manual for their exact meaning + * The errno values as defined by the OS simulator */ public enum Errno { EBADF; @@ -87,6 +89,11 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary this.intSize = program.getCompilerSpec().getDataOrganization().getIntegerSize(); } + /** + * Get the first available file descriptor + * + * @return the lowest available descriptor + */ protected int lowestFd() { Integer lowest = closedFds.pollFirst(); if (lowest != null) { @@ -95,6 +102,15 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary return descriptors.size(); } + /** + * Claim the lowest available file descriptor number for the given descriptor object + * + *

+ * The descriptor will be added to the descriptor table for the claimed number + * + * @param desc the descriptor object + * @return the descriptor number + */ protected int claimFd(EmuUnixFileDescriptor desc) { synchronized (descriptors) { int fd = lowestFd(); @@ -103,6 +119,13 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary } } + /** + * Get the file descriptor object for the given file descriptor number + * + * @param fd the descriptor number + * @return the descriptor object + * @throws EmuUnixException with {@link Errno#EBADF} if the file descriptor is invalid + */ protected EmuUnixFileDescriptor findFd(int fd) { synchronized (descriptors) { EmuUnixFileDescriptor desc = descriptors.get(fd); @@ -113,6 +136,13 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary } } + /** + * Release/invalidate the given file descriptor number + * + * @param fd the file descriptor number + * @return the removed descriptor object + * @throws EmuUnixException with {@link Errno#EBADF} if the file descriptor is invalid + */ protected EmuUnixFileDescriptor releaseFd(int fd) { synchronized (descriptors) { if (descriptors.size() + closedFds.size() - 1 == fd) { @@ -127,6 +157,9 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary } } + /** + * Plug our Sleigh-defined syscalls in + */ @Override protected StructuredPart newStructuredPart() { return new UnixStructuredPart(); @@ -172,6 +205,13 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary } } + /** + * Place the errno into the machine as expected by the simulated platform's ABI + * + * @param executor the executor for the thread running this system call + * @param errno the error number + * @return true if the errno was successfully placed + */ protected abstract boolean returnErrno(PcodeExecutor executor, int errno); @Override @@ -186,54 +226,91 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary return false; } + /** + * The UNIX {@code exit} system call + * + *

+ * This just throws an exception, which the overall simulator or script should catch. + * + * @param status the status code + * @return never + * @throws EmuProcessExitedException always + */ @PcodeUserop @EmuSyscall("exit") public T unix_exit(T status) { throw new EmuProcessExitedException(machine.getArithmetic(), status); } + /** + * The UNIX {@code read} system call + * + * @param state to receive the thread's state + * @param fd the file descriptor + * @param bufPtr the pointer to the buffer to receive the data + * @param count the number of bytes to read + * @return the number of bytes successfully read + */ @PcodeUserop @EmuSyscall("read") - public T unix_read(@OpState PcodeExecutorStatePiece state, T fd, T bufPtr, T count) { + public T unix_read(@OpState PcodeExecutorState state, T fd, T bufPtr, T count) { PcodeArithmetic arithmetic = machine.getArithmetic(); - int ifd = arithmetic.toConcrete(fd).intValue(); + int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER); EmuUnixFileDescriptor desc = findFd(ifd); AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); - int size = arithmetic.toConcrete(count).intValue(); // TODO: Not idea to require concrete size + // TODO: Not ideal to require concrete size, but gets unwieldy to leave it abstract + int size = (int) arithmetic.toLong(count, Purpose.OTHER); T buf = arithmetic.fromConst(0, size); T result = desc.read(buf); - int iresult = arithmetic.toConcrete(result).intValue(); + int iresult = (int) arithmetic.toLong(result, Purpose.OTHER); state.setVar(space, bufPtr, iresult, true, buf); return result; } + /** + * The UNIX {@code write} system call + * + * @param state to receive the thread's state + * @param fd the file descriptor + * @param bufPtr the pointer to the buffer of data to write + * @param count the number of bytes to write + * @return the number of bytes successfully written + */ @PcodeUserop @EmuSyscall("write") - public T unix_write(@OpState PcodeExecutorStatePiece state, T fd, T bufPtr, T count) { + public T unix_write(@OpState PcodeExecutorState state, T fd, T bufPtr, T count) { PcodeArithmetic arithmetic = machine.getArithmetic(); - int ifd = arithmetic.toConcrete(fd).intValue(); + int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER); EmuUnixFileDescriptor desc = findFd(ifd); AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); // TODO: Not ideal to require concrete size. What are the alternatives, though? // TODO: size should actually be long (size_t) - int size = arithmetic.toConcrete(count).intValue(); + int size = (int) arithmetic.toLong(count, Purpose.OTHER); T buf = state.getVar(space, bufPtr, size, true); // TODO: Write back into state? "write" shouldn't touch the buffer.... return desc.write(buf); } + /** + * The UNIX {@code open} system call + * + * @param state to receive the thread's state + * @param pathnamePtr the file's path (pointer to character string) + * @param flags the flags + * @param mode the mode + * @return the file descriptor + */ @PcodeUserop @EmuSyscall("open") - public T unix_open(@OpState PcodeExecutorStatePiece state, T pathnamePtr, T flags, - T mode) { + public T unix_open(@OpState PcodeExecutorState state, T pathnamePtr, T flags, T mode) { PcodeArithmetic arithmetic = machine.getArithmetic(); - int iflags = arithmetic.toConcrete(flags).intValue(); - int imode = arithmetic.toConcrete(mode).intValue(); - long pathnameOff = arithmetic.toConcrete(pathnamePtr).longValue(); + int iflags = (int) arithmetic.toLong(flags, Purpose.OTHER); + int imode = (int) arithmetic.toLong(mode, Purpose.OTHER); + long pathnameOff = arithmetic.toLong(pathnamePtr, Purpose.OTHER); AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); SettingsImpl settings = new SettingsImpl(); - MemBuffer buffer = state.getConcreteBuffer(space.getAddress(pathnameOff)); + MemBuffer buffer = state.getConcreteBuffer(space.getAddress(pathnameOff), Purpose.OTHER); StringDataInstance sdi = new StringDataInstance(StringDataType.dataType, settings, buffer, -1); sdi = new StringDataInstance(StringDataType.dataType, settings, buffer, @@ -245,26 +322,47 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary return arithmetic.fromConst(ifd, intSize); } + /** + * The UNIX {@code close} system call + * + * @param fd the file descriptor + * @return 0 for success + */ @PcodeUserop @EmuSyscall("close") public T unix_close(T fd) { PcodeArithmetic arithmetic = machine.getArithmetic(); - int ifd = arithmetic.toConcrete(fd).intValue(); + int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER); // TODO: Some fs.close or file.close, when all handles have released it? EmuUnixFileDescriptor desc = releaseFd(ifd); desc.close(); return arithmetic.fromConst(0, intSize); } + /** + * The UNIX {@code group_exit} system call + * + *

+ * This just throws an exception, which the overall simulator or script should catch. + * + * @param status the status code + * @return never + * @throws EmuProcessExitedException always + */ @PcodeUserop @EmuSyscall("group_exit") public void unix_group_exit(T status) { throw new EmuProcessExitedException(machine.getArithmetic(), status); } + /** + * System calls defined using Structured Sleigh + */ protected class UnixStructuredPart extends StructuredPart { + /** "Extern" declaration of {@code unix_read} */ final UseropDecl unix_read = userop(type("size_t"), "unix_read", types("int", "void *", "size_t")); + /** "Extern" declaration of {@code unix_write} */ final UseropDecl unix_write = userop(type("size_t"), "unix_write", types("int", "void *", "size_t"));; @@ -273,8 +371,8 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary * *

* This is essentially a macro by virtue of the host (Java) language. Note that - * {@link #_result(RVal)} from here will cause the whole userop to return, not just this - * inlined portion. + * {@link #_result(RVal)} from here will cause the whole userop to return, not just from + * {@link #gatherScatterIovec(Var, Var, Var, UseropDecl)}. */ protected void gatherScatterIovec(Var in_fd, Var in_iovec, Var in_iovcnt, UseropDecl subOp) { @@ -293,6 +391,13 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary _result(tmp_total); } + /** + * The UNIX {@code readv} system call + * + * @param in_fd the file descriptor + * @param in_iovec pointer to the vector of buffers + * @param in_iovcnt the number of buffers + */ @StructuredUserop(type = "size_t") @EmuSyscall("readv") public void unix_readv(@Param(type = "int", name = "in_fd") Var in_fd, @@ -301,6 +406,13 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary gatherScatterIovec(in_fd, in_iovec, in_iovcnt, unix_read); } + /** + * The UNIX {@code writev} system call + * + * @param in_fd the file descriptor + * @param in_iovec pointer to the vector of buffers + * @param in_iovcnt the number of buffers + */ @StructuredUserop(type = "size_t") @EmuSyscall("writev") public void unix_writev(@Param(type = "int", name = "in_fd") Var in_fd, diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/BytesEmuUnixFileSystem.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/BytesEmuUnixFileSystem.java index f401c76ad6..da9b1cd44c 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/BytesEmuUnixFileSystem.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/BytesEmuUnixFileSystem.java @@ -15,11 +15,8 @@ */ package ghidra.pcode.emu.unix; -import ghidra.pcode.emu.sys.EmuIOException; -import ghidra.pcode.exec.BytesPcodeArithmetic; -import ghidra.pcode.exec.PcodeArithmetic; -import ghidra.program.model.lang.Language; -import ghidra.util.MathUtilities; +import ghidra.pcode.emu.sys.BytesEmuFileContents; +import ghidra.pcode.emu.sys.EmuFileContents; /** * A concrete in-memory file system simulator suitable for UNIX programs @@ -30,52 +27,13 @@ public class BytesEmuUnixFileSystem extends AbstractEmuUnixFileSystem { * A concrete in-memory file suitable for UNIX programs */ protected static class BytesEmuUnixFile extends AbstractEmuUnixFile { - protected static final int INIT_CONTENT_SIZE = 1024; - - protected byte[] content = new byte[INIT_CONTENT_SIZE]; - - /** - * Construct a new file - * - * @see BytesEmuUnixFileSystem#newFile(String) - * @param pathname the original pathname of the file - */ public BytesEmuUnixFile(String pathname, int mode) { super(pathname, mode); } @Override - public synchronized byte[] read(PcodeArithmetic arithmetic, byte[] offset, - byte[] buf) { - // NOTE: UNIX takes long offsets, but since we're backing with arrays, we use int - int off = arithmetic.toConcrete(offset).intValue(); - int len = Math.min(buf.length, (int) stat.st_size - off); - if (len < 0) { - throw new EmuIOException("Offset is past end of file"); - } - System.arraycopy(content, off, buf, 0, len); - return arithmetic.fromConst(len, offset.length); - } - - @Override - public synchronized byte[] write(PcodeArithmetic arithmetic, byte[] offset, - byte[] buf) { - int off = arithmetic.toConcrete(offset).intValue(); - if (off + buf.length > content.length) { - byte[] grown = new byte[content.length * 2]; - System.arraycopy(content, 0, grown, 0, (int) stat.st_size); - content = grown; - } - System.arraycopy(buf, 0, content, off, buf.length); - // TODO: Uhh, arrays can't get larger than INT_MAX anyway - stat.st_size = MathUtilities.unsignedMax(stat.st_size, off + buf.length); - return arithmetic.fromConst(buf.length, offset.length); - } - - @Override - public synchronized void truncate() { - stat.st_size = 0; - // TODO: Zero content? + protected EmuFileContents createDefaultContents() { + return new BytesEmuFileContents(); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java index 6b07615aeb..c84778cdf2 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java @@ -21,7 +21,6 @@ import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.sys.EmuIOException; import ghidra.pcode.emu.unix.EmuUnixFileSystem.OpenFlag; import ghidra.pcode.exec.PcodeArithmetic; -import ghidra.pcode.opbehavior.*; import ghidra.program.model.lang.CompilerSpec; import ghidra.program.model.pcode.PcodeOp; @@ -62,6 +61,15 @@ public class DefaultEmuUnixFileHandle implements EmuUnixFileDescriptor { this.offset = arithmetic.fromConst(0, offsetBytes); } + /** + * Get the file opened to this handle + * + * @return the file + */ + public EmuUnixFile getFile() { + return file; + } + /** * Check if the file is readable, throwing {@link EmuIOException} if not */ @@ -86,9 +94,9 @@ public class DefaultEmuUnixFileHandle implements EmuUnixFileDescriptor { * @param len the number of bytes to advance */ protected void advanceOffset(T len) { - int sizeofLen = arithmetic.toConcrete(arithmetic.sizeOf(len)).intValue(); - offset = arithmetic.binaryOp(PcodeArithmetic.INT_ADD, offsetBytes, offsetBytes, offset, - sizeofLen, len); + int sizeofLen = (int) arithmetic.sizeOf(len); + offset = + arithmetic.binaryOp(PcodeOp.INT_ADD, offsetBytes, offsetBytes, offset, sizeofLen, len); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java similarity index 57% rename from Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorState.java rename to Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java index d64cef8cbd..8ec2854d45 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorStatePiece.java @@ -16,31 +16,29 @@ package ghidra.pcode.exec; import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import ghidra.pcode.utils.Utils; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; import ghidra.program.model.mem.*; +import ghidra.program.model.pcode.PcodeOp; import ghidra.util.Msg; /** - * An abstract p-code executor state for storing bytes, retrieved and set as arrays. + * An abstract p-code executor state piece for storing and retrieving bytes as arrays * - * @param if this state is a cache, the type of object backing each address space - * @param the type of an execute state space, internally associated with an address space + * @param the type of an executor state space, internally associated with an address space */ -public abstract class AbstractBytesPcodeExecutorState> - extends AbstractLongOffsetPcodeExecutorState { +public abstract class AbstractBytesPcodeExecutorStatePiece> + extends AbstractLongOffsetPcodeExecutorStatePiece { /** * A memory buffer bound to a given space in this state */ protected class StateMemBuffer implements MemBufferAdapter { protected final Address address; - protected final BytesPcodeExecutorStateSpace source; + protected final BytesPcodeExecutorStateSpace source; /** * Construct a buffer bound to the given space, at the given address @@ -48,7 +46,7 @@ public abstract class AbstractBytesPcodeExecutorState source) { + public StateMemBuffer(Address address, BytesPcodeExecutorStateSpace source) { this.address = address; this.source = source; } @@ -76,59 +74,44 @@ public abstract class AbstractBytesPcodeExecutorState spaces = new HashMap<>(); - - protected final Language language; + protected final AbstractSpaceMap spaceMap = newSpaceMap(); /** * Construct a state for the given language * - * @param language the langauge (used for its memory model) + * @param language the language, used for its memory model and arithmetic */ - public AbstractBytesPcodeExecutorState(Language language) { - super(language, BytesPcodeArithmetic.forLanguage(language)); - this.language = language; - } - - @Override - protected long offsetToLong(byte[] offset) { - return Utils.bytesToLong(offset, offset.length, language.isBigEndian()); - } - - @Override - public byte[] longToOffset(AddressSpace space, long l) { - return arithmetic.fromConst(l, space.getPointerSize()); + public AbstractBytesPcodeExecutorStatePiece(Language language) { + this(language, BytesPcodeArithmetic.forLanguage(language)); } /** - * If this state is a cache, get the object backing the given address space + * Construct a state for the given language * - * @param space the space - * @return the backing object + * @param language the language, used for its memory model + * @param arithmetic the arithmetic */ - protected B getBacking(AddressSpace space) { - return null; + public AbstractBytesPcodeExecutorStatePiece(Language language, + PcodeArithmetic arithmetic) { + super(language, arithmetic, arithmetic); } /** - * Construct a new space internally associated with the given address space, having the given - * backing + * A factory method for this state's space map. * *

- * As the name implies, this often simply wraps {@code S}'s constructor + * Because most of the special logic for extensions is placed in the "state space," i.e., an + * object assigned to a particular address space in the state's language, this factory method + * must provide the map to create and maintain those spaces. That map will in turn be the + * factory of the spaces themselves, allowing extensions to provide additional read/write logic. * - * @param space the address space - * @param backing the backing, if applicable - * @return the new space + * @return the new space map */ - protected abstract S newSpace(AddressSpace space, B backing); + protected abstract AbstractSpaceMap newSpaceMap(); @Override protected S getForSpace(AddressSpace space, boolean toWrite) { - return spaces.computeIfAbsent(space, s -> { - B backing = s.isUniqueSpace() ? null : getBacking(space); - return newSpace(s, backing); - }); + return spaceMap.getForSpace(space, toWrite); } @Override @@ -140,7 +123,7 @@ public abstract class AbstractBytesPcodeExecutorState + * This also provides an internal mechanism for breaking the piece down into the spaces defined by a + * language. It also provides for the special treatment of the {@code unique} space. + * * @param the type used to address contents, convertible to and from {@code long} * @param the type of values stored * @param the type of an execute state space, internally associated with an address space @@ -28,23 +35,108 @@ import ghidra.program.model.lang.Language; public abstract class AbstractLongOffsetPcodeExecutorStatePiece implements PcodeExecutorStatePiece { + /** + * A map of address spaces to objects which store or cache state for that space + * + * @param the type of object for each address space + */ + public abstract static class AbstractSpaceMap { + protected final Map spaces = new HashMap<>(); + + public abstract S getForSpace(AddressSpace space, boolean toWrite); + + public Collection values() { + return spaces.values(); + } + } + + /** + * Use this when each S contains the complete state for the address space + * + * @param the type of object for each address space + */ + public abstract static class SimpleSpaceMap extends AbstractSpaceMap { + /** + * Construct a new space internally associated with the given address space + * + *

+ * As the name implies, this often simply wraps {@code S}'s constructor + * + * @param space the address space + * @return the new space + */ + protected abstract S newSpace(AddressSpace space); + + @Override + public S getForSpace(AddressSpace space, boolean toWrite) { + return spaces.computeIfAbsent(space, s -> newSpace(s)); + } + } + + /** + * Use this when each S is possibly a cache to some other state (backing) object + * + * @param the type of the object backing the cache for each address space + * @param the type of cache for each address space + */ + public abstract static class CacheingSpaceMap extends AbstractSpaceMap { + /** + * Get the object backing the cache for the given address space + * + * @param space the space + * @return the backing object + */ + protected abstract B getBacking(AddressSpace space); + + /** + * Construct a new space internally associated with the given address space, having the + * given backing + * + *

+ * As the name implies, this often simply wraps {@code S}'s constructor + * + * @param space the address space + * @param backing the backing, if applicable. null for the unique space + * @return the new space + */ + protected abstract S newSpace(AddressSpace space, B backing); + + @Override + public S getForSpace(AddressSpace space, boolean toWrite) { + return spaces.computeIfAbsent(space, + s -> newSpace(s, s.isUniqueSpace() ? null : getBacking(s))); + } + } + protected final Language language; + protected final PcodeArithmetic addressArithmetic; protected final PcodeArithmetic arithmetic; protected final AddressSpace uniqueSpace; /** * Construct a state piece for the given language and arithmetic * - * @param language the langauge (used for its memory model) + * @param language the language (used for its memory model) * @param arithmetic an arithmetic used to generate default values of {@code T} */ public AbstractLongOffsetPcodeExecutorStatePiece(Language language, - PcodeArithmetic arithmetic) { + PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic) { this.language = language; + this.addressArithmetic = addressArithmetic; this.arithmetic = arithmetic; uniqueSpace = language.getAddressFactory().getUniqueSpace(); } + @Override + public PcodeArithmetic getAddressArithmetic() { + return addressArithmetic; + } + + @Override + public PcodeArithmetic getArithmetic() { + return arithmetic; + } + /** * Set a value in the unique space * @@ -83,6 +175,7 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece * @param toWrite in case internal spaces are generated lazily, this indicates the space must be * present, because it is going to be written to. * @return the space, or {@code null} + * @see AbstractSpaceMap */ protected abstract S getForSpace(AddressSpace space, boolean toWrite); @@ -120,23 +213,14 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece return arithmetic.fromConst(0, size); } - /** - * Convert an offset of type {@code A} to {@code long} - * - * @param offset the offset as an {@code A} - * @return the offset as a long - */ - protected abstract long offsetToLong(A offset); - @Override - public void setVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit, - T val) { - setVar(space, offsetToLong(offset), size, truncateAddressableUnit, val); + public void setVar(AddressSpace space, A offset, int size, boolean quantize, T val) { + long lOffset = addressArithmetic.toLong(offset, Purpose.STORE); + setVar(space, lOffset, size, quantize, val); } @Override - public void setVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit, - T val) { + public void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) { checkRange(space, offset, size); if (space.isConstantSpace()) { throw new IllegalArgumentException("Cannot write to constant space"); @@ -146,17 +230,18 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece return; } S s = getForSpace(space, true); - offset = truncateOffset(space, offset); + offset = quantizeOffset(space, offset); setInSpace(s, offset, size, val); } @Override - public T getVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit) { - return getVar(space, offsetToLong(offset), size, truncateAddressableUnit); + public T getVar(AddressSpace space, A offset, int size, boolean quantize) { + long lOffset = addressArithmetic.toLong(offset, Purpose.LOAD); + return getVar(space, lOffset, size, quantize); } @Override - public T getVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit) { + public T getVar(AddressSpace space, long offset, int size, boolean quantize) { checkRange(space, offset, size); if (space.isConstantSpace()) { return arithmetic.fromConst(offset, size); @@ -168,7 +253,7 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece if (s == null) { return getFromNullSpace(size); } - offset = truncateOffset(space, offset); + offset = quantizeOffset(space, offset); return getFromSpace(s, offset, size); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractOffsetTransformedPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractOffsetTransformedPcodeExecutorState.java deleted file mode 100644 index a7439c959d..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractOffsetTransformedPcodeExecutorState.java +++ /dev/null @@ -1,70 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.pcode.exec; - -import ghidra.program.model.address.AddressSpace; - -/** - * An executor state decorator which transforms the offset type - * - * @param the offset type of the decorator - * @param the offset type of the delegate - * @param the type of values - */ -public abstract class AbstractOffsetTransformedPcodeExecutorState - implements PcodeExecutorStatePiece { - - private final PcodeExecutorStatePiece state; - - /** - * Construct a decorator around the given delegate - * - * @param state the delegate - */ - public AbstractOffsetTransformedPcodeExecutorState(PcodeExecutorStatePiece state) { - this.state = state; - } - - /** - * Transform an offset of type {@code A} to type {@code B} - * - * @param offset the offset as an {@code A} - * @return the offset as a {@code B} - */ - protected abstract B transformOffset(A offset); - - @Override - public void setVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit, - T val) { - state.setVar(space, transformOffset(offset), size, truncateAddressableUnit, val); - } - - @Override - public void setVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit, - T val) { - state.setVar(space, offset, size, truncateAddressableUnit, val); - } - - @Override - public T getVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit) { - return state.getVar(space, transformOffset(offset), size, truncateAddressableUnit); - } - - @Override - public T getVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit) { - return state.getVar(space, offset, size, truncateAddressableUnit); - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeArithmetic.java index 928bc90733..575a22f901 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeArithmetic.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeArithmetic.java @@ -17,41 +17,55 @@ package ghidra.pcode.exec; import java.math.BigInteger; -import ghidra.pcode.opbehavior.BinaryOpBehavior; -import ghidra.pcode.opbehavior.UnaryOpBehavior; import ghidra.program.model.address.Address; +import ghidra.program.model.lang.Endian; /** - * A rider arithmetic that reports the address of the control value + * An auxiliary arithmetic that reports the address of the control value * *

* This is intended for use as the right side of a {@link PairedPcodeArithmetic}. Note that constant * and unique spaces are never returned. Furthermore, any computation performed on a value, * producing a temporary value, philosophically does not exist at any address in the state. Thus, - * every operation in this arithmetic results in {@code null}. The accompanying state - * {@link AddressOfPcodeExecutorState} does the real "address of" logic. + * every operation in this arithmetic results in {@code null}. The accompanying state piece + * {@link AddressOfPcodeExecutorStatePiece} does the real "address of" logic. */ public enum AddressOfPcodeArithmetic implements PcodeArithmetic

{ // NB: No temp value has a real address - /** - * The singleton instance. - */ + /** The singleton instance */ INSTANCE; @Override - public Address unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, Address in1) { + public Endian getEndian() { return null; } @Override - public Address binaryOp(BinaryOpBehavior op, int sizeout, int sizein1, Address in1, int sizein2, + public Address unaryOp(int opcode, int sizeout, int sizein1, Address in1) { + return null; + } + + @Override + public Address binaryOp(int opcode, int sizeout, int sizein1, Address in1, int sizein2, Address in2) { return null; } @Override - public Address fromConst(long value, int size) { - return null; // TODO: Do we care about Constant space? + public Address modBeforeStore(int sizeout, int sizeinAddress, Address inAddress, + int sizeinValue, Address inValue) { + return inValue; + } + + @Override + public Address modAfterLoad(int sizeout, int sizeinAddress, Address inAddress, int sizeinValue, + Address inValue) { + return inValue; + } + + @Override + public Address fromConst(byte[] value) { + return null; // TODO: Do we care about constant space? } @Override @@ -60,17 +74,17 @@ public enum AddressOfPcodeArithmetic implements PcodeArithmetic
{ } @Override - public boolean isTrue(Address cond) { - throw new AssertionError("Cannot decide branches using 'address of'"); + public Address fromConst(long value, int size) { + return null; } @Override - public BigInteger toConcrete(Address value, boolean isContextreg) { - throw new AssertionError("Should not attempt to concretize 'address of'"); + public byte[] toConcrete(Address value, Purpose purpose) { + throw new ConcretionError("Cannot decide branches using 'address of'", purpose); } @Override - public Address sizeOf(Address value) { - return fromConst(value.getAddressSpace().getSize() / 8, SIZEOF_SIZEOF); + public long sizeOf(Address value) { + return value.getAddressSpace().getSize() / 8; } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java similarity index 61% rename from Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java rename to Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java index abd1f0df91..f3bab1d65d 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorStatePiece.java @@ -18,13 +18,13 @@ package ghidra.pcode.exec; import java.util.HashMap; import java.util.Map; -import ghidra.pcode.utils.Utils; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.mem.MemBuffer; /** - * A rider state piece that reports the address of the control value + * An auxiliary state piece that reports the address of the control value * *

* This is intended for use as the right side of a {@link PairedPcodeExecutorState} or @@ -33,47 +33,50 @@ import ghidra.program.model.mem.MemBuffer; * as the value, so that values transiting unique space can correctly have their source addresses * reported. */ -public class AddressOfPcodeExecutorState +public class AddressOfPcodeExecutorStatePiece implements PcodeExecutorStatePiece { - private final boolean isBigEndian; - private Map unique = new HashMap<>(); + private final BytesPcodeArithmetic addressArithmetic; + private final Map unique = new HashMap<>(); /** * Construct an "address of" state piece * * @param isBigEndian true if the control language is big endian */ - public AddressOfPcodeExecutorState(boolean isBigEndian) { - this.isBigEndian = isBigEndian; + public AddressOfPcodeExecutorStatePiece(boolean isBigEndian) { + this.addressArithmetic = BytesPcodeArithmetic.forEndian(isBigEndian); } @Override - public byte[] longToOffset(AddressSpace space, long l) { - return Utils.longToBytes(l, space.getPointerSize(), isBigEndian); + public PcodeArithmetic getAddressArithmetic() { + return addressArithmetic; } @Override - public void setVar(AddressSpace space, byte[] offset, int size, - boolean truncateAddressableUnit, Address val) { + public PcodeArithmetic

getArithmetic() { + return AddressOfPcodeArithmetic.INSTANCE; + } + + @Override + public void setVar(AddressSpace space, byte[] offset, int size, boolean quantize, Address val) { if (!space.isUniqueSpace()) { return; } - long off = Utils.bytesToLong(offset, offset.length, isBigEndian); - unique.put(off, val); + long lOffset = addressArithmetic.toLong(offset, Purpose.STORE); + unique.put(lOffset, val); } @Override - public Address getVar(AddressSpace space, byte[] offset, int size, - boolean truncateAddressableUnit) { - long off = Utils.bytesToLong(offset, offset.length, isBigEndian); + public Address getVar(AddressSpace space, byte[] offset, int size, boolean quantize) { + long lOffset = addressArithmetic.toLong(offset, Purpose.LOAD); if (!space.isUniqueSpace()) { - return space.getAddress(off); + return space.getAddress(lOffset); } - return unique.get(off); + return unique.get(lOffset); } @Override - public MemBuffer getConcreteBuffer(Address address) { - throw new AssertionError("Cannot make 'address of' concrete buffers"); + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + throw new ConcretionError("Cannot make 'address of' concrete buffers", purpose); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java index 934b91a8b2..4ad1f72643 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java @@ -26,12 +26,17 @@ import java.util.stream.Stream; import org.apache.commons.lang3.reflect.TypeUtils; +import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibrary; import ghidra.program.model.pcode.Varnode; import utilities.util.AnnotationUtilities; /** * A userop library wherein Java methods are exported via a special annotation * + *

+ * See {@code StandAloneEmuExampleScript} for an example of implementing a userop library. A more + * complex example is {@link EmuLinuxAmd64SyscallUseropLibrary}. + * * @param the type of data processed by the library */ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibrary { @@ -54,7 +59,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra opdef.posExecutor = pos; } }, - STATE(OpState.class, PcodeExecutorStatePiece.class) { + STATE(OpState.class, PcodeExecutorState.class) { @Override int getPos(AnnotatedPcodeUseropDefinition opdef) { return opdef.posState; @@ -88,8 +93,8 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra } }; - static boolean processParameter(AnnotatedPcodeUseropDefinition opdef, int i, - Parameter p) { + static boolean processParameter(AnnotatedPcodeUseropDefinition opdef, Type declClsOpType, + int i, Parameter p) { ParamAnnotProc only = null; for (ParamAnnotProc proc : ParamAnnotProc.values()) { if (proc.hasAnnot(p)) { @@ -105,7 +110,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra if (only == null) { return false; } - only.processParameterPerAnnot(opdef, i, p); + only.processParameterPerAnnot(opdef, declClsOpType, i, p); return true; } @@ -125,15 +130,43 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra return p.getAnnotation(annotCls) != null; } - void processParameterPerAnnot(AnnotatedPcodeUseropDefinition opdef, int i, - Parameter p) { + Type getArgumentType(Type opType) { + TypeVariable[] typeParams = paramCls.getTypeParameters(); + if (typeParams.length == 0) { + return paramCls; + } + if (typeParams.length == 1) { + return TypeUtils.parameterize(paramCls, opType); + } + throw new AssertionError(); + } + + void processParameterPerAnnot(AnnotatedPcodeUseropDefinition opdef, Type declClsOpType, + int i, Parameter p) { if (getPos(opdef) != -1) { throw new IllegalArgumentException( "Can only have one parameter with @" + annotCls.getSimpleName()); } - if (!p.getType().isAssignableFrom(paramCls)) { + Type pType = p.getParameterizedType(); + Map, Type> typeArgs = TypeUtils.getTypeArguments(pType, paramCls); + if (typeArgs == null) { throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + - annotCls.getSimpleName() + " must acccept " + paramCls.getSimpleName()); + annotCls.getSimpleName() + " must acccept " + getArgumentType(declClsOpType)); + } + if (typeArgs.isEmpty()) { + // Nothing + } + else if (typeArgs.size() == 1) { + Type declMthOpType = typeArgs.get(paramCls.getTypeParameters()[0]); + if (!Objects.equals(declClsOpType, declMthOpType)) { + throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + + annotCls.getSimpleName() + " must acccept " + + getArgumentType(declClsOpType)); + } + } + else { + throw new AssertionError("Internal: paramCls for @" + annotCls.getSimpleName() + + "should only have one type parameter "); } setPos(opdef, i); } @@ -148,8 +181,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra implements PcodeUseropDefinition { protected static AnnotatedPcodeUseropDefinition create(PcodeUserop annot, - AnnotatedPcodeUseropLibrary library, Class opType, Lookup lookup, - Method method) { + AnnotatedPcodeUseropLibrary library, Type opType, Lookup lookup, Method method) { if (annot.variadic()) { return new VariadicAnnotatedPcodeUseropDefinition<>(library, opType, lookup, method); @@ -168,8 +200,8 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra private int posLib = -1; private int posOut = -1; - public AnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary library, - Class opType, Lookup lookup, Method method) { + public AnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary library, Type opType, + Lookup lookup, Method method) { initStarting(); this.method = method; try { @@ -181,22 +213,21 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra PcodeUserop.class.getSimpleName() + " annotation. Override getMethodLookup()"); } - - Class rType = method.getReturnType(); - if (rType != void.class && !opType.isAssignableFrom(rType)) { + Type declClsOpType = PcodeUseropLibrary.getOperandType(method.getDeclaringClass()); + Type rType = method.getGenericReturnType(); + if (rType != void.class && !TypeUtils.isAssignable(rType, declClsOpType)) { throw new IllegalArgumentException( "Method " + method.getName() + " with @" + PcodeUserop.class.getSimpleName() + - " annotation must return void or a type assignable to " + - opType.getSimpleName()); + " annotation must return void or a type assignable to " + declClsOpType); } Parameter[] params = method.getParameters(); for (int i = 0; i < params.length; i++) { Parameter p = params[i]; - boolean processed = ParamAnnotProc.processParameter(this, i, p); + boolean processed = ParamAnnotProc.processParameter(this, declClsOpType, i, p); if (!processed) { - processNonAnnotatedParameter(opType, i, p); + processNonAnnotatedParameter(declClsOpType, opType, i, p); } } initFinished(); @@ -248,7 +279,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra // Optional override } - protected abstract void processNonAnnotatedParameter(Class opType, int i, + protected abstract void processNonAnnotatedParameter(Type declClsOpType, Type opType, int i, Parameter p); protected void initFinished() { @@ -275,7 +306,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra private Set posTs; public FixedArgsAnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary library, - Class opType, Lookup lookup, Method method) { + Type opType, Lookup lookup, Method method) { super(library, opType, lookup, method); } @@ -286,17 +317,19 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra } @Override - protected void processNonAnnotatedParameter(Class opType, int i, Parameter p) { - if (p.getType().equals(Varnode.class)) { + protected void processNonAnnotatedParameter(Type declClsOpType, Type opType, int i, + Parameter p) { + Type pType = p.getParameterizedType(); + if (TypeUtils.isAssignable(Varnode.class, pType)) { // Just use the Varnode by default } - else if (p.getType().isAssignableFrom(opType)) { + else if (TypeUtils.isAssignable(declClsOpType, pType)) { posTs.add(i); } else { throw new IllegalArgumentException("Input parameter " + p.getName() + " of userop " + method.getName() + " must be " + - Varnode.class.getSimpleName() + " or accept " + opType.getSimpleName()); + Varnode.class.getSimpleName() + " or accept " + declClsOpType); } posIns.add(i); } @@ -342,34 +375,42 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra extends AnnotatedPcodeUseropDefinition { private int posIns; - private Class opType; + private Class opRawType; public VariadicAnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary library, - Class opType, Lookup lookup, Method method) { + Type opType, Lookup lookup, Method method) { super(library, opType, lookup, method); } @Override protected void initStarting() { posIns = -1; - opType = null; + opRawType = null; } @Override - protected void processNonAnnotatedParameter(Class opType, int i, Parameter p) { + protected void processNonAnnotatedParameter(Type declClsOpType, Type opType, int i, + Parameter p) { if (posIns != -1) { throw new IllegalArgumentException( "Only one non-annotated parameter is allowed to receive the inputs"); } - if (p.getType().equals(Varnode[].class)) { + Type pType = p.getParameterizedType(); + Type eType = TypeUtils.getArrayComponentType(pType); + if (eType == null) { + throw new IllegalArgumentException( + "Variadic userop must receive inputs as " + declClsOpType + "[] or " + + Varnode.class.getSimpleName() + "[]"); + } + if (pType.equals(Varnode[].class)) { // Just pass inVars as is } - else if (p.getType().isAssignableFrom(Array.newInstance(opType, 0).getClass())) { - this.opType = opType; + else if (TypeUtils.isAssignable(declClsOpType, eType)) { + this.opRawType = TypeUtils.getRawType(opType, getClass()); } else { throw new IllegalArgumentException( - "Variadic userop must receive inputs as T[] or " + + "Variadic userop must receive inputs as " + declClsOpType + "[] or " + Varnode.class.getSimpleName() + "[]"); } posIns = i; @@ -383,15 +424,19 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra } } + protected Object[] readVars(PcodeExecutorState state, List vars) { + Object[] vals = (Object[]) Array.newInstance(opRawType, vars.size()); + for (int i = 0; i < vals.length; i++) { + vals[i] = state.getVar(vars.get(i)); + } + return vals; + } + @Override protected void placeInputs(PcodeExecutor executor, List args, List inVars) { - PcodeExecutorStatePiece state = executor.getState(); - if (opType != null) { - Stream ts = inVars.stream().map(state::getVar); - @SuppressWarnings("unchecked") - Object valsArr = ts.toArray(l -> (T[]) Array.newInstance(opType, l)); - args.set(posIns, valsArr); + if (opRawType != null) { + args.set(posIns, readVars(executor.getState(), inVars)); } else { args.set(posIns, inVars.toArray(Varnode[]::new)); @@ -447,10 +492,8 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra * An annotation to receive the executor itself into a parameter * *

- * The annotated parameter must have a type assignable from {@link PcodeExecutor} with parameter - * {@code } matching that of the actual executor. TODO: No "bind-time" check of the type - * parameter is performed. An incorrect parameter will likely cause a {@link ClassCastException} - * despite the lack of any compiler warnings. + * The annotated parameter must have type {@link PcodeExecutor} with the same {@code } as the + * class declaring the method. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @@ -461,10 +504,8 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra * An annotation to receive the executor's state into a parameter * *

- * The annotated parameter must have a type assignable from {@link PcodeExecutorStatePiece} with - * parameters {@code } matching that of the executor. TODO: No "bind-time" check of the - * type parameters is performed. An incorrect parameter will likely cause a - * {@link ClassCastException} despite the lack of any compiler warnings. + * The annotated parameter must have type {@link PcodeExecutorState} with the same {@code } + * as the class declaring the method. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @@ -482,10 +523,8 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra * definition to receive the complete library. * *

- * The annotated parameter must have a type assignable from {@link PcodeUseropLibrary} with - * parameter {@code } matching that of the executor. TODO: No "bind-time" check of the type - * parameters is performed. An incorrect parameter will likely cause a - * {@link ClassCastException} despite the lack of any compiler warnings. + * The annotated parameter must have type {@link PcodeUseropLibrary} with the same {@code } + * as the class declaring the method. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @@ -496,7 +535,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra * An annotation to receive the output varnode into a parameter * *

- * The annotated parameter must have a type assignable from {@link Varnode}. + * The annotated parameter must have type {@link Varnode}. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) @@ -512,7 +551,7 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra */ public AnnotatedPcodeUseropLibrary() { Lookup lookup = getMethodLookup(); - Class opType = getOperandType(); + Type opType = getOperandType(); @SuppressWarnings({ "unchecked", "rawtypes" }) Class> cls = (Class) this.getClass(); Set methods = CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls)); @@ -527,18 +566,8 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra * * @return the type of data processed by the userop */ - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected Class getOperandType() { - Map, Type> args = - TypeUtils.getTypeArguments(getClass(), AnnotatedPcodeUseropLibrary.class); - if (args == null) { - return (Class) Object.class; - } - Type type = args.get(AnnotatedPcodeUseropLibrary.class.getTypeParameters()[0]); - if (!(type instanceof Class)) { - return (Class) Object.class; - } - return (Class) type; + protected Type getOperandType() { + return PcodeUseropLibrary.getOperandType(getClass()); } /** diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BigIntegerPcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BigIntegerPcodeArithmetic.java deleted file mode 100644 index 0ac9add609..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BigIntegerPcodeArithmetic.java +++ /dev/null @@ -1,69 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.pcode.exec; - -import java.math.BigInteger; - -import ghidra.pcode.opbehavior.BinaryOpBehavior; -import ghidra.pcode.opbehavior.UnaryOpBehavior; - -/** - * A p-code arithmetic that operates on {@link BigInteger} values - * - *

- * Note: it appears this class is no longer used anywhere, which means it's probably not tested. - */ -@Deprecated(forRemoval = true) // TODO: Not getting used -public enum BigIntegerPcodeArithmetic implements PcodeArithmetic { - INSTANCE; - - @Override - public BigInteger unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, BigInteger in1) { - return op.evaluateUnary(sizeout, sizein1, in1); - } - - @Override - public BigInteger binaryOp(BinaryOpBehavior op, int sizeout, int sizein1, BigInteger in1, - int sizein2, BigInteger in2) { - return op.evaluateBinary(sizeout, sizein1, in1, in2); - } - - @Override - public BigInteger fromConst(long value, int size) { - return BigInteger.valueOf(value); - } - - @Override - public BigInteger fromConst(BigInteger value, int size, boolean isContextreg) { - return value; - } - - @Override - public boolean isTrue(BigInteger cond) { - return !cond.equals(BigInteger.ZERO); - } - - @Override - public BigInteger toConcrete(BigInteger value, boolean isContextreg) { - return value; - } - - @Override - public BigInteger sizeOf(BigInteger value) { - // NOTE: Determining the minimum necessary size to contain it is not correct. - throw new AssertionError("Size is not known"); - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeArithmetic.java index 7346701d7b..83a55ad284 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeArithmetic.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeArithmetic.java @@ -17,27 +17,27 @@ package ghidra.pcode.exec; import java.math.BigInteger; -import ghidra.pcode.opbehavior.BinaryOpBehavior; -import ghidra.pcode.opbehavior.UnaryOpBehavior; +import ghidra.pcode.opbehavior.*; import ghidra.pcode.utils.Utils; +import ghidra.program.model.lang.Endian; import ghidra.program.model.lang.Language; /** - * A p-code arithmetic that operates on byte array values + * A p-code arithmetic that operates on concrete byte array values * *

* The arithmetic interprets the arrays as big- or little-endian values, then performs the - * arithmetic as specified by the p-code operation. + * arithmetic as specified by the p-code operation. The implementation defers to {@link OpBehavior}. */ public enum BytesPcodeArithmetic implements PcodeArithmetic { /** * The instance which interprets arrays as big-endian values */ - BIG_ENDIAN(true), + BIG_ENDIAN(Endian.BIG), /** * The instance which interprets arrays as little-endian values */ - LITTLE_ENDIAN(false); + LITTLE_ENDIAN(Endian.LITTLE); /** * Obtain the instance for the given endianness @@ -59,70 +59,72 @@ public enum BytesPcodeArithmetic implements PcodeArithmetic { return forEndian(language.isBigEndian()); } - private final boolean isBigEndian; + private final Endian endian; - private BytesPcodeArithmetic(boolean isBigEndian) { - this.isBigEndian = isBigEndian; + private BytesPcodeArithmetic(Endian endian) { + this.endian = endian; } @Override - public byte[] unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, byte[] in1) { + public Endian getEndian() { + return endian; + } + + @Override + public byte[] unaryOp(int opcode, int sizeout, int sizein1, byte[] in1) { + UnaryOpBehavior b = (UnaryOpBehavior) OpBehaviorFactory.getOpBehavior(opcode); + boolean isBigEndian = endian.isBigEndian(); if (sizein1 > 8 || sizeout > 8) { - BigInteger in1Val = Utils.bytesToBigInteger(in1, in1.length, isBigEndian, false); - BigInteger outVal = op.evaluateUnary(sizeout, sizein1, in1Val); + BigInteger in1Val = Utils.bytesToBigInteger(in1, sizein1, isBigEndian, false); + BigInteger outVal = b.evaluateUnary(sizeout, sizein1, in1Val); return Utils.bigIntegerToBytes(outVal, sizeout, isBigEndian); } - else { - long in1Val = Utils.bytesToLong(in1, sizein1, isBigEndian); - long outVal = op.evaluateUnary(sizeout, sizein1, in1Val); - return Utils.longToBytes(outVal, sizeout, isBigEndian); - } + long in1Val = Utils.bytesToLong(in1, sizein1, isBigEndian); + long outVal = b.evaluateUnary(sizeout, sizein1, in1Val); + return Utils.longToBytes(outVal, sizeout, isBigEndian); } @Override - public byte[] binaryOp(BinaryOpBehavior op, int sizeout, int sizein1, byte[] in1, int sizein2, + public byte[] binaryOp(int opcode, int sizeout, int sizein1, byte[] in1, int sizein2, byte[] in2) { + BinaryOpBehavior b = (BinaryOpBehavior) OpBehaviorFactory.getOpBehavior(opcode); + boolean isBigEndian = endian.isBigEndian(); if (sizein1 > 8 || sizein2 > 8 || sizeout > 8) { BigInteger in1Val = Utils.bytesToBigInteger(in1, sizein1, isBigEndian, false); BigInteger in2Val = Utils.bytesToBigInteger(in2, sizein2, isBigEndian, false); - BigInteger outVal = op.evaluateBinary(sizeout, sizein1, in1Val, in2Val); + BigInteger outVal = b.evaluateBinary(sizeout, sizein1, in1Val, in2Val); return Utils.bigIntegerToBytes(outVal, sizeout, isBigEndian); } - else { - long in1Val = Utils.bytesToLong(in1, sizein1, isBigEndian); - long in2Val = Utils.bytesToLong(in2, sizein2, isBigEndian); - long outVal = op.evaluateBinary(sizeout, sizein1, in1Val, in2Val); - return Utils.longToBytes(outVal, sizeout, isBigEndian); - } + long in1Val = Utils.bytesToLong(in1, sizein1, isBigEndian); + long in2Val = Utils.bytesToLong(in2, sizein2, isBigEndian); + long outVal = b.evaluateBinary(sizeout, sizein1, in1Val, in2Val); + return Utils.longToBytes(outVal, sizeout, isBigEndian); } @Override - public byte[] fromConst(long value, int size) { - return Utils.longToBytes(value, size, isBigEndian); + public byte[] modBeforeStore(int sizeout, int sizeinAddress, byte[] inAddress, int sizeinValue, + byte[] inValue) { + return inValue; } @Override - public byte[] fromConst(BigInteger value, int size, boolean isContextreg) { - return Utils.bigIntegerToBytes(value, size, isBigEndian || isContextreg); + public byte[] modAfterLoad(int sizeout, int sizeinAddress, byte[] inAddress, int sizeinValue, + byte[] inValue) { + return inValue; } @Override - public boolean isTrue(byte[] cond) { - for (byte b : cond) { - if (b != 0) { - return true; - } - } - return false; + public byte[] fromConst(byte[] value) { + return value; } @Override - public BigInteger toConcrete(byte[] value, boolean isContextreg) { - return Utils.bytesToBigInteger(value, value.length, isBigEndian || isContextreg, false); + public byte[] toConcrete(byte[] value, Purpose purpose) { + return value; } @Override - public byte[] sizeOf(byte[] value) { - return fromConst(value.length, SIZEOF_SIZEOF); + public long sizeOf(byte[] value) { + return value.length; } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java index d5ae36952c..5c4d73427d 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java @@ -15,26 +15,19 @@ */ package ghidra.pcode.exec; -import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; /** - * A plain concrete state suitable for simple emulation, without any backing objects + * A state composing a single {@link BytesPcodeExecutorStatePiece} */ -public class BytesPcodeExecutorState - extends AbstractBytesPcodeExecutorState> { - +public class BytesPcodeExecutorState extends DefaultPcodeExecutorState { /** - * Construct a state for the given language + * Create the state * - * @param langauge the language (used for its memory model) + * @param language the language (processor model) */ public BytesPcodeExecutorState(Language language) { - super(language); - } - - @Override - protected BytesPcodeExecutorStateSpace newSpace(AddressSpace space, Void backing) { - return new BytesPcodeExecutorStateSpace<>(language, space, backing); + super(new BytesPcodeExecutorStatePiece(language), + BytesPcodeArithmetic.forLanguage(language)); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..a167d36ce6 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStatePiece.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.pcode.exec; + +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; + +/** + * A plain concrete state piece without any backing objects + */ +public class BytesPcodeExecutorStatePiece + extends AbstractBytesPcodeExecutorStatePiece> { + + /** + * Construct a state for the given language + * + * @param langauge the language (used for its memory model) + */ + public BytesPcodeExecutorStatePiece(Language language) { + super(language); + } + + @Override + protected AbstractSpaceMap> newSpaceMap() { + return new SimpleSpaceMap<>() { + @Override + protected BytesPcodeExecutorStateSpace newSpace(AddressSpace space) { + return new BytesPcodeExecutorStateSpace<>(language, space, null); + } + }; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java index 78a5072048..384e4f3299 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java @@ -27,7 +27,7 @@ import ghidra.program.model.lang.Register; import ghidra.util.Msg; /** - * A p-code executor state space for storing bytes, retrieved and set as arrays. + * A p-code executor state space for storing and retrieving bytes as arrays * * @param if this space is a cache, the type of object backing this space */ diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ConcretionError.java similarity index 50% rename from Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorState.java rename to Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ConcretionError.java index ebfde5317b..b3ff609c32 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ConcretionError.java @@ -15,19 +15,31 @@ */ package ghidra.pcode.exec; -import ghidra.program.model.lang.Language; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; /** - * A device in the type hierarchy that turns a suitable state piece into a state - * - * @param the type of values and addresses in the state - * @param the type of an execute state space, internally associated with an address space + * The emulator or a client attempted to concretize an abstract value */ -public abstract class AbstractLongOffsetPcodeExecutorState - extends AbstractLongOffsetPcodeExecutorStatePiece - implements PcodeExecutorState { +public class ConcretionError extends PcodeExecutionException { + private final Purpose purpose; - public AbstractLongOffsetPcodeExecutorState(Language language, PcodeArithmetic arithmetic) { - super(language, arithmetic); + /** + * Create the exception + * + * @param message a message for the client + * @param purpose the reason why the emulator needs a concrete value + */ + public ConcretionError(String message, Purpose purpose) { + super(message); + this.purpose = purpose; + } + + /** + * Get the reason why the emulator needs a concrete value + * + * @return the purpose + */ + public Purpose getPurpose() { + return purpose; } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java new file mode 100644 index 0000000000..13ddd407f3 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/DefaultPcodeExecutorState.java @@ -0,0 +1,67 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.mem.MemBuffer; + +/** + * A p-code executor state formed from a piece whose address and value types are the same + * + *

+ * This class will also wire in the arithmetic's + * {@link PcodeArithmetic#modBeforeStore(int, int, Object, int, Object)} and + * {@link PcodeArithmetic#modAfterLoad(int, int, Object, int, Object)}, which is only possible when + * the address and value type are guaranteed to match. + * + * @param the type of values and addresses in the state + */ +public class DefaultPcodeExecutorState implements PcodeExecutorState { + protected final PcodeExecutorStatePiece piece; + protected final PcodeArithmetic arithmetic; + + public DefaultPcodeExecutorState(PcodeExecutorStatePiece piece, + PcodeArithmetic arithmetic) { + this.piece = piece; + this.arithmetic = arithmetic; + } + + public DefaultPcodeExecutorState(PcodeExecutorStatePiece piece) { + this(piece, piece.getArithmetic()); + } + + @Override + public T getVar(AddressSpace space, T offset, int size, boolean quantize) { + return piece.getVar(space, offset, size, quantize); + } + + @Override + public void setVar(AddressSpace space, T offset, int size, boolean quantize, T val) { + piece.setVar(space, offset, size, quantize, val); + } + + @Override + public PcodeArithmetic getArithmetic() { + return arithmetic; + } + + @Override + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + return piece.getConcreteBuffer(address, purpose); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java index 045107067f..c620802fb8 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java @@ -15,29 +15,34 @@ */ package ghidra.pcode.exec; -import java.math.BigInteger; -import java.util.Map.Entry; +import java.util.Objects; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; -import ghidra.pcode.opbehavior.BinaryOpBehavior; -import ghidra.pcode.opbehavior.UnaryOpBehavior; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.pcode.PcodeOp; /** * An arithmetic composed from two. * *

* The new arithmetic operates on tuples where each is subject to its respective arithmetic. One - * exception is {@link #isTrue(Entry)}, which is typically used to control branches. This arithmetic - * defers to left ("control") arithmetic. + * exception is {@link #toConcrete(Pair, Purpose)}. This arithmetic defers to left ("control") + * arithmetic. Thus, conventionally, when part of the pair represents the concrete value, it should + * be the left. + * + *

+ * See {@link PairedPcodeExecutorStatePiece} regarding composing three or more elements. Generally, + * it's recommended the client provide its own "record" type and the corresponding arithmetic and + * state piece to manipulate it. Nesting pairs would work, but is not recommended. * * @param the type of the left ("control") element - * @param the type of the right ("rider") element + * @param the type of the right ("auxiliary") element */ public class PairedPcodeArithmetic implements PcodeArithmetic> { private final PcodeArithmetic leftArith; private final PcodeArithmetic rightArith; + private final Endian endian; /** * Construct a composed arithmetic from the given two @@ -46,52 +51,103 @@ public class PairedPcodeArithmetic implements PcodeArithmetic> * @param rightArith the right ("rider") arithmetic */ public PairedPcodeArithmetic(PcodeArithmetic leftArith, PcodeArithmetic rightArith) { + Endian lend = leftArith.getEndian(); + Endian rend = rightArith.getEndian(); + if (lend != null && rend != null && lend != rend) { + throw new IllegalArgumentException("Arithmetics must agree in endianness"); + } + this.endian = lend != null ? lend : rend; this.leftArith = leftArith; this.rightArith = rightArith; } @Override - public Pair unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, Pair in1) { - return new ImmutablePair<>( - leftArith.unaryOp(op, sizeout, sizein1, in1.getLeft()), - rightArith.unaryOp(op, sizeout, sizein1, in1.getRight())); + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (this.getClass() != obj.getClass()) { + return false; + } + PairedPcodeArithmetic that = (PairedPcodeArithmetic) obj; + if (!Objects.equals(this.leftArith, that.leftArith)) { + return false; + } + if (!Objects.equals(this.rightArith, that.rightArith)) { + return false; + } + return true; } @Override - public Pair binaryOp(BinaryOpBehavior op, int sizeout, int sizein1, Pair in1, - int sizein2, Pair in2) { - return new ImmutablePair<>( - leftArith.binaryOp(op, sizeout, sizein1, in1.getLeft(), sizein2, in2.getLeft()), - rightArith.binaryOp(op, sizeout, sizein1, in1.getRight(), sizein2, in2.getRight())); + public Endian getEndian() { + return endian; } @Override - public Pair fromConst(long value, int size) { - return new ImmutablePair<>(leftArith.fromConst(value, size), - rightArith.fromConst(value, size)); - } - - @Override - public Pair fromConst(BigInteger value, int size, boolean isContextreg) { - return new ImmutablePair<>(leftArith.fromConst(value, size, isContextreg), - rightArith.fromConst(value, size, isContextreg)); - } - - @Override - public boolean isTrue(Pair cond) { - return leftArith.isTrue(cond.getLeft()); - } - - @Override - public BigInteger toConcrete(Pair value, boolean isContextreg) { - return leftArith.toConcrete(value.getLeft(), isContextreg); - } - - @Override - public Pair sizeOf(Pair value) { + public Pair unaryOp(int opcode, int sizeout, int sizein1, + Pair in1) { return Pair.of( - leftArith.sizeOf(value.getLeft()), - rightArith.sizeOf(value.getRight())); + leftArith.unaryOp(opcode, sizeout, sizein1, in1.getLeft()), + rightArith.unaryOp(opcode, sizeout, sizein1, in1.getRight())); + } + + @Override + public Pair unaryOp(PcodeOp op, Pair in1) { + return Pair.of( + leftArith.unaryOp(op, in1.getLeft()), + rightArith.unaryOp(op, in1.getRight())); + } + + @Override + public Pair binaryOp(int opcode, int sizeout, int sizein1, + Pair in1, int sizein2, Pair in2) { + return Pair.of( + leftArith.binaryOp(opcode, sizeout, sizein1, in1.getLeft(), sizein2, in2.getLeft()), + rightArith.binaryOp(opcode, sizeout, sizein1, in1.getRight(), sizein2, in2.getRight())); + } + + @Override + public Pair binaryOp(PcodeOp op, Pair in1, Pair in2) { + return Pair.of( + leftArith.binaryOp(op, in1.getLeft(), in2.getLeft()), + rightArith.binaryOp(op, in1.getRight(), in2.getRight())); + } + + @Override + public Pair modBeforeStore(int sizeout, int sizeinAddress, Pair inAddress, + int sizeinValue, Pair inValue) { + return Pair.of( + leftArith.modBeforeStore(sizeout, sizeinAddress, inAddress.getLeft(), sizeinValue, + inValue.getLeft()), + rightArith.modBeforeStore(sizeout, sizeinAddress, inAddress.getRight(), sizeinValue, + inValue.getRight())); + } + + @Override + public Pair modAfterLoad(int sizeout, int sizeinAddress, Pair inAddress, + int sizeinValue, Pair inValue) { + return Pair.of( + leftArith.modAfterLoad(sizeout, sizeinAddress, inAddress.getLeft(), sizeinValue, + inValue.getLeft()), + rightArith.modAfterLoad(sizeout, sizeinAddress, inAddress.getRight(), sizeinValue, + inValue.getRight())); + } + + @Override + public Pair fromConst(byte[] value) { + return Pair.of(leftArith.fromConst(value), rightArith.fromConst(value)); + } + + @Override + public byte[] toConcrete(Pair value, Purpose purpose) { + return leftArith.toConcrete(value.getLeft(), purpose); + } + + @Override + public long sizeOf(Pair value) { + return leftArith.sizeOf(value.getLeft()); + // TODO: Assert that the right agrees? Nah. Some aux types have no size. } /** diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java index 58d076c00d..6f153bf73b 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorState.java @@ -15,9 +15,9 @@ */ package ghidra.pcode.exec; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.mem.MemBuffer; @@ -26,57 +26,59 @@ import ghidra.program.model.mem.MemBuffer; * A paired executor state * *

- * This composes two delegate states "left" and "write" creating a single state which instead stores - * pairs of values, where the left component has the value type of the left state, and the right - * component has the value type of the right state. Note that both states are addressed using only - * the left "control" component. Otherwise, every operation on this state is decomposed into + * This composes a delegate state and piece "left" and "write" creating a single state which instead + * stores pairs of values, where the left component has the value type of the left state, and the + * right component has the value type of the right state. Note that both states are addressed using + * only the left "control" component. Otherwise, every operation on this state is decomposed into * operations upon the delegate states, and the final result composed from the results of those * operations. * *

* Where a response cannot be composed of both states, the paired state defers to the left. In this * way, the left state controls the machine, while the right is computed in tandem. The right never - * directly controls the machine; however, by overriding - * {@link #getVar(AddressSpace, Object, int, boolean)} and/or - * {@link #setVar(AddressSpace, Object, int, boolean, Object)}, the right can affect the left and - * indirectly control the machine. + * directly controls the machine + * + *

+ * See {@link PairedPcodeExecutorStatePiece} regarding the composition of three or more pieces. * * @param the type of values for the "left" state * @param the type of values for the "right" state */ -public class PairedPcodeExecutorState - extends AbstractOffsetTransformedPcodeExecutorState, L, Pair> - implements PcodeExecutorState> { +public class PairedPcodeExecutorState implements PcodeExecutorState> { + private final PairedPcodeExecutorStatePiece piece; + private final PcodeArithmetic> arithmetic; - private final PcodeExecutorStatePiece left; - private final PcodeExecutorStatePiece right; + public PairedPcodeExecutorState(PairedPcodeExecutorStatePiece piece) { + this.piece = piece; + this.arithmetic = piece.getArithmetic(); + } /** * Compose a paired state from the given left and right states * * @param left the state backing the left side of paired values ("control") - * @param right the state backing the right side of paired values ("rider") + * @param right the state backing the right side of paired values ("auxiliary") */ - public PairedPcodeExecutorState(PcodeExecutorStatePiece left, + public PairedPcodeExecutorState(PcodeExecutorState left, + PcodeExecutorStatePiece right, PcodeArithmetic> arithmetic) { + this.piece = + new PairedPcodeExecutorStatePiece<>(left, right, left.getArithmetic(), arithmetic); + this.arithmetic = arithmetic; + } + + public PairedPcodeExecutorState(PcodeExecutorState left, PcodeExecutorStatePiece right) { - super(new PairedPcodeExecutorStatePiece<>(left, right)); - this.left = left; - this.right = right; + this(left, right, new PairedPcodeArithmetic<>(left.getArithmetic(), right.getArithmetic())); } @Override - public Pair longToOffset(AddressSpace space, long l) { - return new ImmutablePair<>(left.longToOffset(space, l), null); + public PcodeArithmetic> getArithmetic() { + return arithmetic; } @Override - protected L transformOffset(Pair offset) { - return offset.getLeft(); - } - - @Override - public MemBuffer getConcreteBuffer(Address address) { - return left.getConcreteBuffer(address); + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + return piece.getConcreteBuffer(address, purpose); } /** @@ -85,7 +87,7 @@ public class PairedPcodeExecutorState * @return the left state */ public PcodeExecutorStatePiece getLeft() { - return left; + return piece.getLeft(); } /** @@ -94,6 +96,17 @@ public class PairedPcodeExecutorState * @return the right state */ public PcodeExecutorStatePiece getRight() { - return right; + return piece.getRight(); + } + + @Override + public void setVar(AddressSpace space, Pair offset, int size, boolean quantize, + Pair val) { + piece.setVar(space, offset.getLeft(), size, quantize, val); + } + + @Override + public Pair getVar(AddressSpace space, Pair offset, int size, boolean quantize) { + return piece.getVar(space, offset.getLeft(), size, quantize); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java index bb044d8424..11831a7e52 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PairedPcodeExecutorStatePiece.java @@ -15,9 +15,9 @@ */ package ghidra.pcode.exec; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.mem.MemBuffer; @@ -26,11 +26,21 @@ import ghidra.program.model.mem.MemBuffer; * A paired executor state piece * *

- * This compose two delegate pieces "left" and "right" creating a single piece which instead stores - * pairs of values, where the left component has the value type of the left state, and the right - * component has the value type of the right state. Both pieces must have the same address type. - * Every operation on this piece is decomposed into operations upon the delegate pieces, and the - * final result composed from the results of those operations. + * This composes two delegate pieces "left" and "right" creating a single piece which stores pairs + * of values, where the left component has the value type of the left piece, and the right component + * has the value type of the right piece. Both pieces must have the same address type. Every + * operation on this piece is decomposed into operations upon the delegate pieces, and the final + * result composed from the results of those operations. + * + *

+ * To compose three or more states, first ask if it is really necessary. Second, consider + * implementing the {@link PcodeExecutorStatePiece} interface directly. Third, use the Church-style + * triple. In that third case, the implementor must decide which side has the nested tuple. Putting + * it on the right keeps the concrete piece (conventionally on the left) in the most shallow + * position, so it can be accessed efficiently. However, putting it on the left (implying it's in + * the deepest position) keeps the concrete piece near the other pieces to which it's most closely + * bound. The latter goal is only important when the paired arithmetics mix information between + * their elements. * * @see PairedPcodeExecutorState * @param the type of offset, usually the type of a controlling state @@ -42,36 +52,50 @@ public class PairedPcodeExecutorStatePiece private final PcodeExecutorStatePiece left; private final PcodeExecutorStatePiece right; + private final PcodeArithmetic addressArithmetic; + private final PcodeArithmetic> arithmetic; + + public PairedPcodeExecutorStatePiece(PcodeExecutorStatePiece left, + PcodeExecutorStatePiece right, PcodeArithmetic addressArithmetic, + PcodeArithmetic> arithmetic) { + this.left = left; + this.right = right; + this.addressArithmetic = addressArithmetic; + this.arithmetic = arithmetic; + } public PairedPcodeExecutorStatePiece(PcodeExecutorStatePiece left, PcodeExecutorStatePiece right) { - this.left = left; - this.right = right; + this(left, right, left.getAddressArithmetic(), + new PairedPcodeArithmetic<>(left.getArithmetic(), right.getArithmetic())); } @Override - public A longToOffset(AddressSpace space, long l) { - return left.longToOffset(space, l); + public PcodeArithmetic getAddressArithmetic() { + return addressArithmetic; } @Override - public void setVar(AddressSpace space, A offset, int size, - boolean truncateAddressableUnit, Pair val) { - left.setVar(space, offset, size, truncateAddressableUnit, val.getLeft()); - right.setVar(space, offset, size, truncateAddressableUnit, val.getRight()); + public PcodeArithmetic> getArithmetic() { + return arithmetic; } @Override - public Pair getVar(AddressSpace space, A offset, int size, - boolean truncateAddressableUnit) { - return new ImmutablePair<>( - left.getVar(space, offset, size, truncateAddressableUnit), - right.getVar(space, offset, size, truncateAddressableUnit)); + public void setVar(AddressSpace space, A offset, int size, boolean quantize, Pair val) { + left.setVar(space, offset, size, quantize, val.getLeft()); + right.setVar(space, offset, size, quantize, val.getRight()); } @Override - public MemBuffer getConcreteBuffer(Address address) { - return left.getConcreteBuffer(address); + public Pair getVar(AddressSpace space, A offset, int size, boolean quantize) { + return Pair.of( + left.getVar(space, offset, size, quantize), + right.getVar(space, offset, size, quantize)); + } + + @Override + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + return left.getConcreteBuffer(address, purpose); } /** diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java index f3fed985ac..fc4e4b5c3f 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeArithmetic.java @@ -18,18 +18,26 @@ package ghidra.pcode.exec; import java.math.BigInteger; import ghidra.pcode.opbehavior.*; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.lang.Endian; import ghidra.program.model.pcode.PcodeOp; /** * An interface that defines arithmetic p-code operations on values of type {@code T}. * + *

+ * See {@link BytesPcodeArithmetic} for the typical pattern when implementing an arithmetic. There + * are generally two cases: 1) Where endianness matters, 2) Where endianness does not matter. The + * first is typical. The implementation should be an {@link Enum} with two constants, one for the + * big endian implementation, and one for the little endian implementation. The class should also + * provide static methods: {@code forEndian(boolean isBigEndian)} for getting the correct one based + * on endianness, and {@code forLanguage(Language language)} for getting the correct one given a + * language. If endianness does not matter, then the implementation should follow a singleton + * pattern. See notes on {@link #getEndian()} for the endian-agnostic case. + * * @param the type of values operated on */ public interface PcodeArithmetic { - BinaryOpBehavior INT_ADD = - (BinaryOpBehavior) OpBehaviorFactory.getOpBehavior(PcodeOp.INT_ADD); - UnaryOpBehavior INT_ZEXT = - (UnaryOpBehavior) OpBehaviorFactory.getOpBehavior(PcodeOp.INT_ZEXT); /** * The number of bytes needed to encode the size (in bytes) of any value @@ -37,18 +45,46 @@ public interface PcodeArithmetic { int SIZEOF_SIZEOF = 8; /** - * The arithmetic for operating on bytes in big-endian + * Various reasons the emulator may require a concrete value */ - PcodeArithmetic BYTES_BE = BytesPcodeArithmetic.BIG_ENDIAN; + enum Purpose { + /** The value is needed to parse an instruction */ + DECODE, + /** The value is needed for disassembly context */ + CONTEXT, + /** The value is needed to decide a conditional branch */ + CONDITION, + /** The value will be used as the address of an indirect branch */ + BRANCH, + /** The value will be used as the address of a value to load */ + LOAD, + /** The value will be used as the address of a value to store */ + STORE, + /** Some other reason, perhaps for userop library use */ + OTHER, + /** The user or a tool is inspecting the value */ + INSPECT + } + /** - * The arithmetic for operating on bytes in little-endian + * Get the endianness of this arithmetic + * + *

+ * Often T is a byte array, or at least represents one abstractly. Ideally, it is an array where + * each element is an abstraction of a byte. If that is the case, then the arithmetic likely has + * to interpret those bytes as integral values according to an endianness. This should return + * that endianness. + * + *

+ * If the abstraction has no notion of endianness, return null. In that case, the both + * {@link #fromConst(BigInteger, int, boolean)} and {@link #fromConst(long, int)} must be + * overridden. Furthermore, unless {@link #toConcrete(Object, Purpose)} is guaranteed to throw + * an exception, then {@link #toBigInteger(Object, Purpose)} and + * {@link #toLong(Object, Purpose)} must also be overridden. + * + * @return the endianness or null */ - PcodeArithmetic BYTES_LE = BytesPcodeArithmetic.LITTLE_ENDIAN; - /** - * The arithmetic for operating on {@link BigInteger}s. - */ - @Deprecated(forRemoval = true) // TODO: Not getting used - PcodeArithmetic BIGINT = BigIntegerPcodeArithmetic.INSTANCE; + Endian getEndian(); /** * Apply a unary operator to the given input @@ -58,13 +94,35 @@ public interface PcodeArithmetic { * size. For example, a {@link BigInteger} may have a minimum encoding size, but that does not * necessarily reflect the size of the variable from which is was read. * - * @param op the behavior of the operator + * @implNote {@link OpBehaviorFactory#getOpBehavior(int)} for the given opcode is guaranteed to + * return a derivative of {@link UnaryOpBehavior}. + * + * @param opcode the p-code opcode * @param sizeout the size (in bytes) of the output variable * @param sizein1 the size (in bytes) of the input variable * @param in1 the input value * @return the output value */ - T unaryOp(UnaryOpBehavior op, int sizeout, int sizein1, T in1); + T unaryOp(int opcode, int sizeout, int sizein1, T in1); + + /** + * Apply a unary operator to the given input + * + *

+ * This provides the full p-code op, allowing deeper inspection of the code. For example, an + * arithmetic may wish to distinguish immediate (constant) values from variables. By default, + * this unpacks the details and defers to {@link #unaryOp(int, int, int, Object)}. + * + * @implNote {@link OpBehaviorFactory#getOpBehavior(int)} for the given opcode is guaranteed to + * return a derivative of {@link UnaryOpBehavior}. + * + * @param op the operation + * @param in1 the input value + * @return the output value + */ + default T unaryOp(PcodeOp op, T in1) { + return unaryOp(op.getOpcode(), op.getOutput().getSize(), op.getInput(0).getSize(), in1); + } /** * Apply a binary operator to the given inputs @@ -74,7 +132,11 @@ public interface PcodeArithmetic { * size. For example, a {@link BigInteger} may have a minimum encoding size, but that does not * necessarily reflect the size of the variable from which is was read. * - * @param op the behavior of the operator + * @implNote {@link OpBehaviorFactory#getOpBehavior(int)} for the given opcode is guaranteed to + * return a derivative of {@link BinaryOpBehavior}. + * + * @param op the operation + * @param b the behavior of the operator * @param sizeout the size (in bytes) of the output variable * @param sizein1 the size (in bytes) of the first (left) input variable * @param in1 the first (left) input value @@ -82,7 +144,68 @@ public interface PcodeArithmetic { * @param in2 the second (right) input value * @return the output value */ - T binaryOp(BinaryOpBehavior op, int sizeout, int sizein1, T in1, int sizein2, T in2); + T binaryOp(int opcode, int sizeout, int sizein1, T in1, int sizein2, T in2); + + /** + * Apply a binary operator to the given input + * + *

+ * This provides the full p-code op, allowing deeper inspection of the code. For example, an + * arithmetic may wish to distinguish immediate (constant) values from variables. By default, + * this unpacks the details and defers to {@link #binaryOp(int, int, int, Object, int, Object)}. + * + * @implNote {@link OpBehaviorFactory#getOpBehavior(int)} for the given opcode is guaranteed to + * return a derivative of {@link BinaryOpBehavior}. + * + * @param op + * @param in1 + * @param in2 + * @return + */ + default T binaryOp(PcodeOp op, T in1, T in2) { + return binaryOp(op.getOpcode(), op.getOutput().getSize(), op.getInput(0).getSize(), in1, + op.getInput(1).getSize(), in2); + } + + /** + * Apply any modifications before a value is stored + * + *

+ * This implements any abstractions associated with {@link PcodeOp#STORE}. This is called on the + * address/offset and the value before the value is actually stored into the state. + * + * @param sizeout the size (in bytes) of the output variable + * @param sizeinAddress the size (in bytes) of the variable used for indirection + * @param inAddress the value used as the address (or offset) + * @param sizeinValue the size (in bytes) of the variable to store + * @param inValue the value to store + * @return the modified value to store + */ + T modBeforeStore(int sizeout, int sizeinAddress, T inAddress, int sizeinValue, T inValue); + + /** + * Apply any modifications after a value is loaded + * + *

+ * This implements any abstractions associated with {@link PcodeOp#LOAD}. This is called on the + * address/offset and the value after the value is actually loaded from the state. + * + * @param sizeout the size (in bytes) of the output variable + * @param sizeinAddress the size (in bytes) of the variable used for indirection + * @param inAddress the value used as the address (or offset) + * @param sizeinValue the size (in bytes) of the variable loaded + * @param inValue the value loaded + * @return the modified value loaded + */ + T modAfterLoad(int sizeout, int sizeinAddress, T inAddress, int sizeinValue, T inValue); + + /** + * Convert the given constant concrete value to type {@code T} having the same size. + * + * @param value the constant value + * @return the value as a {@code T} + */ + T fromConst(byte[] value); /** * Convert the given constant concrete value to type {@code T} having the given size. @@ -96,7 +219,9 @@ public interface PcodeArithmetic { * @param size the size (in bytes) of the variable into which the value is to be stored * @return the value as a {@code T} */ - T fromConst(long value, int size); + default T fromConst(long value, int size) { + return fromConst(Utils.longToBytes(value, size, getEndian().isBigEndian())); + } /** * Convert the given constant concrete value to type {@code T} having the given size. @@ -113,7 +238,10 @@ public interface PcodeArithmetic { * big endian, no matter the machine language's endianness. * @return the value as a {@code T} */ - T fromConst(BigInteger value, int size, boolean isContextreg); + default T fromConst(BigInteger value, int size, boolean isContextreg) { + return fromConst( + Utils.bigIntegerToBytes(value, size, isContextreg || getEndian().isBigEndian())); + } /** * Convert the given constant concrete value to type {@code T} having the given size. @@ -127,53 +255,89 @@ public interface PcodeArithmetic { return fromConst(value, size, false); } + /** + * Convert, if possible, the given abstract value to a concrete byte array + * + * @param value the abstract value + * @param size the expected size (in bytes) of the array + * @param the reason why the emulator needs a concrete value + * @return the array + * @throws ConcretionError if the value cannot be made concrete + */ + byte[] toConcrete(T value, Purpose purpose); + /** * Convert, if possible, the given abstract condition to a concrete boolean value * * @param cond the abstract condition + * @param purpose probably {@link Purpose#CONDITION} * @return the boolean value */ - boolean isTrue(T cond); + default boolean isTrue(T cond, Purpose purpose) { + byte[] concrete = toConcrete(cond, purpose); + for (byte b : concrete) { + if (b != 0) { + return true; + } + } + return false; + } /** - * Convert, if possible, the given abstract value to a concrete value + * Convert, if possible, the given abstract value to a concrete big integer * *

- * If the conversion is not possible, throw an exception. TODO: Decide on conventions of which - * exception to throw and/or establish a hierarchy of checked exceptions. + * If the conversion is not possible, throw an exception. * * @param value the abstract value - * @param isContextreg true to indicate the value is from the disassembly context register. If - * {@code T} represents bytes, and the value is the contextreg, then the bytes are in - * big endian, no matter the machine language's endianness. + * @param purpose the reason why the emulator needs a concrete value * @return the concrete value + * @throws ConcretionError if the value cannot be made concrete */ - BigInteger toConcrete(T value, boolean isContextreg); + default BigInteger toBigInteger(T value, Purpose purpose) { + byte[] concrete = toConcrete(value, purpose); + return Utils.bytesToBigInteger(concrete, concrete.length, + purpose == Purpose.CONTEXT || getEndian().isBigEndian(), false); + } /** - * Make concrete, if possible, the given abstract value + * Convert, if possible, the given abstract value to a concrete long * *

- * If the conversion is not possible, throw an exception. TODO: Decide on conventions of which - * exception to throw and/or establish a hierarchy of checked exceptions. + * If the conversion is not possible, throw an exception. * * @param value the abstract value + * @param purpose the reason why the emulator needs a concrete value * @return the concrete value + * @throws ConcretionError if the value cannot be made concrete */ - default BigInteger toConcrete(T value) { - return toConcrete(value, false); + default long toLong(T value, Purpose purpose) { + byte[] concrete = toConcrete(value, purpose); + return Utils.bytesToLong(concrete, concrete.length, + purpose == Purpose.CONTEXT || getEndian().isBigEndian()); } /** * Get the size in bytes, if possible, of the given abstract value * *

- * If the abstract value does not conceptually have a size, throw an exception. Note the - * returned size should itself have a size of {@link #SIZEOF_SIZEOF}. TODO: Establish - * conventions for exceptions. + * If the abstract value does not conceptually have a size, throw an exception. * * @param value the abstract value * @return the size in bytes */ - T sizeOf(T value); + long sizeOf(T value); + + /** + * Get the size in bytes, if possible, of the given abstract value, as an abstract value + * + *

+ * The returned size should itself has a size of {@link #SIZEOF_SIZEOF}. + * + * @param value the abstract value + * @return the size in bytes, as an abstract value + */ + default T sizeOfAbstract(T value) { + return fromConst(sizeOf(value), SIZEOF_SIZEOF); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java index 4c15026754..ca40c7a5b7 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutor.java @@ -21,6 +21,7 @@ import java.util.Map; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.PcodeEmulator; import ghidra.pcode.error.LowlevelError; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.pcode.opbehavior.*; import ghidra.program.model.address.Address; @@ -41,7 +42,7 @@ import ghidra.program.model.pcode.Varnode; public class PcodeExecutor { protected final SleighLanguage language; protected final PcodeArithmetic arithmetic; - protected final PcodeExecutorStatePiece state; + protected final PcodeExecutorState state; protected final Register pc; protected final int pointerSize; @@ -53,7 +54,7 @@ public class PcodeExecutor { * @param state an implementation of load/store p-code ops */ public PcodeExecutor(SleighLanguage language, PcodeArithmetic arithmetic, - PcodeExecutorStatePiece state) { + PcodeExecutorState state) { this.language = language; this.arithmetic = arithmetic; this.state = state; @@ -85,28 +86,61 @@ public class PcodeExecutor { * * @return the state */ - public PcodeExecutorStatePiece getState() { + public PcodeExecutorState getState() { return state; } + /** + * Compile and execute a line of Sleigh + * + * @param line the line, excluding the semicolon + */ public void executeSleighLine(String line) { PcodeProgram program = SleighProgramCompiler.compileProgram(language, "line", List.of(line + ";"), PcodeUseropLibrary.NIL); execute(program, PcodeUseropLibrary.nil()); } + /** + * Begin execution of the given program + * + * @param program the program, e.g., from an injection, or a decoded instruction + * @return the frame + */ public PcodeFrame begin(PcodeProgram program) { return begin(program.code, program.useropNames); } + /** + * Execute a program using the given library + * + * @param program the program, e.g., from an injection, or a decoded instruction + * @param library the library + * @return the frame + */ public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary library) { return execute(program.code, program.useropNames, library); } + /** + * Begin execution of a list of p-code ops + * + * @param code the ops + * @param useropNames the map of userop numbers to names + * @return the frame + */ public PcodeFrame begin(List code, Map useropNames) { return new PcodeFrame(language, code, useropNames); } + /** + * Execute a list of p-code ops + * + * @param code the ops + * @param useropNames the map of userop numbers to names + * @param library the library of userops + * @return the frame + */ public PcodeFrame execute(List code, Map useropNames, PcodeUseropLibrary library) { PcodeFrame frame = begin(code, useropNames); @@ -139,6 +173,11 @@ public class PcodeExecutor { } } + /** + * Handle an unrecognized or unimplemented p-code op + * + * @param op the op + */ protected void badOp(PcodeOp op) { switch (op.getOpcode()) { case PcodeOp.UNIMPLEMENTED: @@ -150,6 +189,13 @@ public class PcodeExecutor { } } + /** + * Step on p-code op + * + * @param op the op + * @param frame the current frame + * @param library the library, invoked in case of {@link PcodeOp#CALLOTHER} + */ public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary library) { OpBehavior b = OpBehaviorFactory.getOpBehavior(op.getOpcode()); if (b == null) { @@ -198,6 +244,12 @@ public class PcodeExecutor { } } + /** + * Step a single p-code op + * + * @param frame the frame whose next op to execute + * @param library the userop library + */ public void step(PcodeFrame frame, PcodeUseropLibrary library) { try { stepOp(frame.nextOp(), frame, library); @@ -211,55 +263,97 @@ public class PcodeExecutor { } } + /** + * Skip a single p-code op + * + * @param frame the frame whose next op to skip + */ public void skip(PcodeFrame frame) { frame.nextOp(); } + /** + * Assert that a varnode is constant and get its value as an integer. + * + *

+ * Here "constant" means a literal or immediate value. It does not read from the state. + * + * @param vn the varnode + * @return the value + */ protected int getIntConst(Varnode vn) { assert vn.getAddress().getAddressSpace().isConstantSpace(); return (int) vn.getAddress().getOffset(); } + /** + * Execute the given unary op + * + * @param op the op + * @param b the op behavior + */ public void executeUnaryOp(PcodeOp op, UnaryOpBehavior b) { Varnode in1Var = op.getInput(0); Varnode outVar = op.getOutput(); T in1 = state.getVar(in1Var); - T out = arithmetic.unaryOp(b, outVar.getSize(), - in1Var.getSize(), in1); + T out = arithmetic.unaryOp(op, in1); state.setVar(outVar, out); } + /** + * Execute the given binary op + * + * @param op the op + * @param b the op behavior + */ public void executeBinaryOp(PcodeOp op, BinaryOpBehavior b) { Varnode in1Var = op.getInput(0); Varnode in2Var = op.getInput(1); Varnode outVar = op.getOutput(); T in1 = state.getVar(in1Var); T in2 = state.getVar(in2Var); - T out = arithmetic.binaryOp(b, outVar.getSize(), - in1Var.getSize(), in1, in2Var.getSize(), in2); + T out = arithmetic.binaryOp(op, in1, in2); state.setVar(outVar, out); } + /** + * Execute a load + * + * @param op the op + */ public void executeLoad(PcodeOp op) { int spaceID = getIntConst(op.getInput(0)); AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID); - T offset = state.getVar(op.getInput(1)); + Varnode inOffset = op.getInput(1); + T offset = state.getVar(inOffset); Varnode outvar = op.getOutput(); - T out = state.getVar(space, offset, outvar.getSize(), true); - state.setVar(outvar, out); - } - public void executeStore(PcodeOp op) { - int spaceID = getIntConst(op.getInput(0)); - AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID); - T offset = state.getVar(op.getInput(1)); - Varnode valVar = op.getInput(2); - T val = state.getVar(valVar); - state.setVar(space, offset, valVar.getSize(), true, val); + T out = state.getVar(space, offset, outvar.getSize(), true); + T mod = arithmetic.modAfterLoad(outvar.getSize(), inOffset.getSize(), offset, + outvar.getSize(), out); + state.setVar(outvar, mod); } /** - * Called when execution branches to a target address + * Execute a store + * + * @param op the op + */ + public void executeStore(PcodeOp op) { + int spaceID = getIntConst(op.getInput(0)); + AddressSpace space = language.getAddressFactory().getAddressSpace(spaceID); + Varnode inOffset = op.getInput(1); + T offset = state.getVar(inOffset); + Varnode valVar = op.getInput(2); + + T val = state.getVar(valVar); + T mod = arithmetic.modBeforeStore(valVar.getSize(), inOffset.getSize(), offset, + valVar.getSize(), val); + state.setVar(space, offset, valVar.getSize(), true, mod); + } + + /** + * Extension point: Called when execution branches to a target address * *

* NOTE: This is not called for the fall-through case @@ -267,16 +361,36 @@ public class PcodeExecutor { * @param target the target address */ protected void branchToAddress(Address target) { - // Extension point } + /** + * Set the state's pc to the given offset and finish the frame + * + *

+ * This implements only part of the p-code control flow semantics. An emulator must also + * override {@link #branchToAddress(Address)}, so that it can update its internal program + * counter. The emulator could just read the program counter from the state after every + * completed frame, but receiving it "out of band" is faster. + * + * @param offset the offset (the new value of the program counter) + * @param frame the frame to finish + */ protected void branchToOffset(T offset, PcodeFrame frame) { - state.setVar(pc.getAddressSpace(), pc.getOffset(), (pc.getBitLength() + 7) / 8, false, - offset); + state.setVar(pc, offset); frame.finishAsBranch(); } - public void executeBranch(PcodeOp op, PcodeFrame frame) { + /** + * Perform the actual logic of a branch p-code op + * + *

+ * This is a separate method, so that overriding {@link #executeBranch(PcodeOp, PcodeFrame)} + * does not implicitly modify {@link #executeConditionalBranch(PcodeOp, PcodeFrame)}. + * + * @param op the op + * @param frame the frame + */ + protected void doExecuteBranch(PcodeOp op, PcodeFrame frame) { Address target = op.getInput(0).getAddress(); if (target.isConstantAddress()) { frame.branch((int) target.getOffset()); @@ -287,33 +401,101 @@ public class PcodeExecutor { } } + /** + * Execute a branch + * + *

+ * This merely defers to {@link #doExecuteBranch(PcodeOp, PcodeFrame)}. To instrument the + * operation, override this. To modify or instrument branching in general, override + * {@link #doExecuteBranch(PcodeOp, PcodeFrame)}, {@link #branchToOffset(Object, PcodeFrame)}, + * and/or {@link #branchToAddress(Address)}. + * + * @param op the op + * @param frame the frame + */ + public void executeBranch(PcodeOp op, PcodeFrame frame) { + doExecuteBranch(op, frame); + } + + /** + * Execute a conditional branch + * + * @param op the op + * @param frame the frame + */ public void executeConditionalBranch(PcodeOp op, PcodeFrame frame) { Varnode condVar = op.getInput(1); T cond = state.getVar(condVar); - if (arithmetic.isTrue(cond)) { - executeBranch(op, frame); + if (arithmetic.isTrue(cond, Purpose.CONDITION)) { + doExecuteBranch(op, frame); } } - public void executeIndirectBranch(PcodeOp op, PcodeFrame frame) { + /** + * Perform the actual logic of an indirect branch p-code op + * + *

+ * This is a separate method, so that overriding + * {@link #executeIndirectBranch(PcodeOp, PcodeFrame)} does not implicitly modify + * {@link #executeIndirectCall(PcodeOp, PcodeFrame)} and + * {@link #executeReturn(PcodeOp, PcodeFrame)}. + * + * @param op the op + * @param frame the frame + */ + protected void doExecuteIndirectBranch(PcodeOp op, PcodeFrame frame) { T offset = state.getVar(op.getInput(0)); branchToOffset(offset, frame); - long concrete = arithmetic.toConcrete(offset).longValue(); + long concrete = arithmetic.toLong(offset, Purpose.BRANCH); Address target = op.getSeqnum().getTarget().getNewAddress(concrete, true); branchToAddress(target); } + /** + * Execute an indirect branch + * + *

+ * This merely defers to {@link #doExecuteIndirectBranch(PcodeOp, PcodeFrame)}. To instrument + * the operation, override this. To modify or instrument indirect branching in general, override + * {@link #doExecuteIndirectBranch(PcodeOp, PcodeFrame)}. + * + * @param op the op + * @param frame the frame + */ + public void executeIndirectBranch(PcodeOp op, PcodeFrame frame) { + doExecuteIndirectBranch(op, frame); + } + + /** + * Execute a call + * + * @param op the op + * @param frame the frame + */ public void executeCall(PcodeOp op, PcodeFrame frame) { Address target = op.getInput(0).getAddress(); branchToOffset(arithmetic.fromConst(target.getOffset(), pointerSize), frame); branchToAddress(target); } + /** + * Execute an indirect call + * + * @param op the op + * @param frame the frame + */ public void executeIndirectCall(PcodeOp op, PcodeFrame frame) { - executeIndirectBranch(op, frame); + doExecuteIndirectBranch(op, frame); } + /** + * Get the name of a userop + * + * @param opNo the userop number + * @param frame the frame + * @return the name, or null if it is not defined + */ public String getUseropName(int opNo, PcodeFrame frame) { if (opNo < language.getNumberOfUserDefinedOpNames()) { return language.getUserDefinedOpName(opNo); @@ -321,23 +503,52 @@ public class PcodeExecutor { return frame.getUseropName(opNo); } + /** + * Execute a userop call + * + * @param op the op + * @param frame the frame + * @param library the library of userops + */ public void executeCallother(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary library) { int opNo = getIntConst(op.getInput(0)); String opName = getUseropName(opNo, frame); if (opName == null) { - throw new AssertionError( - "Pcode userop " + opNo + " is not defined"); + throw new AssertionError("Pcode userop " + opNo + " is not defined"); } PcodeUseropDefinition opDef = library.getUserops().get(opName); - if (opDef == null) { - throw new SleighLinkException( - "Sleigh userop '" + opName + "' is not in the library " + library); + if (opDef != null) { + opDef.execute(this, library, op.getOutput(), + List.of(op.getInputs()).subList(1, op.getNumInputs())); + return; } - opDef.execute(this, library, op.getOutput(), - List.of(op.getInputs()).subList(1, op.getNumInputs())); + onMissingUseropDef(op, frame, opName, library); } + /** + * Extension point: Behavior when a userop definition was not found in the library + * + *

+ * The default behavior is to throw a {@link SleighLinkException}. + * + * @param op the op + * @param frame the frame + * @param opName the name of the p-code userop + * @param library the library + */ + protected void onMissingUseropDef(PcodeOp op, PcodeFrame frame, String opName, + PcodeUseropLibrary library) { + throw new SleighLinkException( + "Sleigh userop '" + opName + "' is not in the library " + library); + } + + /** + * Execute a return + * + * @param op the op + * @param frame the frame + */ public void executeReturn(PcodeOp op, PcodeFrame frame) { - executeIndirectBranch(op, frame); + doExecuteIndirectBranch(op, frame); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java index 27caca84a7..0be2d367c0 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorState.java @@ -17,16 +17,28 @@ package ghidra.pcode.exec; import org.apache.commons.lang3.tuple.Pair; +import ghidra.program.model.address.AddressSpace; + /** - * An interface that provides storage for values of type {@code T}, addressed by offsets of type - * {@code T}. + * An interface that provides storage for values of type {@code T} + * + *

+ * This is not much more than a stricter form of {@link PcodeExecutorStatePiece}, in that it + * requires the value and address offset types to agree, so that a p-code executor or emulator can + * perform loads and stores using indirect addresses. The typical pattern for implementing a state + * is to compose it from pieces. See {@link PcodeExecutorStatePiece}. * * @param the type of offsets and values */ public interface PcodeExecutorState extends PcodeExecutorStatePiece { + @Override + default PcodeArithmetic getAddressArithmetic() { + return getArithmetic(); + } + /** - * Use this state as the control, paired with the given state as the rider. + * Use this state as the control, paired with the given auxiliary state. * *

* CAUTION: Often, the default paired state is not quite sufficient. Consider diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java index 83a082634b..4d8a06958a 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutorStatePiece.java @@ -15,6 +15,7 @@ */ package ghidra.pcode.exec; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.*; import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; @@ -22,9 +23,17 @@ import ghidra.program.model.pcode.Varnode; /** * An interface that provides storage for values of type {@code T}, addressed by offsets of type - * {@code A}. + * {@code A} + * + *

+ * The typical pattern for implementing a state is to compose it from one or more state pieces. Each + * piece must use the same address type and arithmetic. If more than one piece is needed, they are + * composed using {@link PairedPcodeExecutorStatePiece}. Once all the pieces are composed, the root + * piece can be wrapped to make a state using {@link DefaultPcodeExecutorState} or + * {@link PairedPcodeExecutorState}. The latter corrects the address type to be a pair so it matches + * the type of values. * - * @param the type of offsets + * @param the type of address offsets * @param the type of values */ public interface PcodeExecutorStatePiece { @@ -47,17 +56,18 @@ public interface PcodeExecutorStatePiece { } /** - * Convert the given offset from {@code long} to type {@code A} + * Get the arithmetic used to manipulate addresses of the type used by this state * - *

- * Note, is it unlikely (and discouraged) to encode the space in {@code A}. The reason the space - * is given is to ensure the result has the correct size. - * - * @param space the space where the offset applies - * @param l the offset - * @return the same offset as type {@code A} + * @return the address (or offset) arithmetic */ - A longToOffset(AddressSpace space, long l); + PcodeArithmetic getAddressArithmetic(); + + /** + * Get the arithmetic used to manipulate values of the type stored by this state + * + * @return the arithmetic + */ + PcodeArithmetic getArithmetic(); /** * Set the value of a register variable @@ -87,27 +97,36 @@ public interface PcodeExecutorStatePiece { * @param space the address space * @param offset the offset within the space * @param size the size of the variable - * @param truncateAddressableUnit true to truncate to the language's "addressable unit" + * @param quantize true to quantize to the language's "addressable unit" * @param val the value */ - void setVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit, T val); + void setVar(AddressSpace space, A offset, int size, boolean quantize, T val); /** * Set the value of a variable * - *

- * This method is typically used for writing memory variables. - * * @param space the address space * @param offset the offset within the space * @param size the size of the variable - * @param truncateAddressableUnit true to truncate to the language's "addressable unit" + * @param quantize true to quantize to the language's "addressable unit" * @param val the value */ - default void setVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit, - T val) { + default void setVar(AddressSpace space, long offset, int size, boolean quantize, T val) { checkRange(space, offset, size); - setVar(space, longToOffset(space, offset), size, truncateAddressableUnit, val); + A aOffset = getAddressArithmetic().fromConst(offset, space.getPointerSize()); + setVar(space, aOffset, size, quantize, val); + } + + /** + * Set the value of a variable + * + * @param address the address in memory + * @param size the size of the variable + * @param quantize true to quantize to the language's "addressable unit" + * @param val the value + */ + default void setVar(Address address, int size, boolean quantize, T val) { + setVar(address.getAddressSpace(), address.getOffset(), size, quantize, val); } /** @@ -139,10 +158,10 @@ public interface PcodeExecutorStatePiece { * @param space the address space * @param offset the offset within the space * @param size the size of the variable - * @param truncateAddressableUnit true to truncate to the language's "addressable unit" + * @param quantize true to quantize to the language's "addressable unit" * @return the value */ - T getVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit); + T getVar(AddressSpace space, A offset, int size, boolean quantize); /** * Get the value of a variable @@ -153,30 +172,47 @@ public interface PcodeExecutorStatePiece { * @param space the address space * @param offset the offset within the space * @param size the size of the variable - * @param truncateAddressableUnit true to truncate to the language's "addressalbe unit" + * @param quantize true to quantize to the language's "addressable unit" * @return the value */ - default T getVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit) { + default T getVar(AddressSpace space, long offset, int size, boolean quantize) { checkRange(space, offset, size); - return getVar(space, longToOffset(space, offset), size, truncateAddressableUnit); + A aOffset = getAddressArithmetic().fromConst(offset, space.getPointerSize()); + return getVar(space, aOffset, size, quantize); + } + + /** + * Get the value of a variable + * + *

+ * This method is typically used for reading memory variables. + * + * @param address the address of the variable + * @param size the size of the variable + * @param quantize true to quantize to the language's "addressable unit" + * @return the value + */ + default T getVar(Address address, int size, boolean quantize) { + return getVar(address.getAddressSpace(), address.getOffset(), size, quantize); } /** * Bind a buffer of concrete bytes at the given start address * * @param address the start address + * @param purpose the reason why the emulator needs a concrete value * @return a buffer */ - MemBuffer getConcreteBuffer(Address address); + MemBuffer getConcreteBuffer(Address address, Purpose purpose); /** - * Truncate the given offset to the language's "addressable unit" + * Quantize the given offset to the language's "addressable unit" * * @param space the space where the offset applies * @param offset the offset - * @return the truncated offset + * @return the quantized offset */ - default long truncateOffset(AddressSpace space, long offset) { + default long quantizeOffset(AddressSpace space, long offset) { return space.truncateAddressableWordOffset(offset) * space.getAddressableUnitSize(); } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java index e1253bae6a..5b9a485d64 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java @@ -15,20 +15,24 @@ */ package ghidra.pcode.exec; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; import java.util.*; +import org.apache.commons.lang3.reflect.TypeUtils; + import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; import ghidra.program.model.pcode.Varnode; import ghidra.sleigh.grammar.Location; /** - * A "library" of p-code userops available to a p-code executor. + * A "library" of p-code userops available to a p-code executor * *

* The library can provide definitions of p-code userops already declared by the executor's language - * as well as completely new userops accessible to SLEIGH/p-code compiled for the executor. The - * recommended way to implement a library is to extend {@link AnnotatedPcodeUseropLibrary}. + * as well as completely new userops accessible to Sleigh/p-code later compiled for the executor. + * The recommended way to implement a library is to extend {@link AnnotatedPcodeUseropLibrary}. * * @param the type of values accepted by the p-code userops. */ @@ -45,6 +49,28 @@ public interface PcodeUseropLibrary { } } + /** + * Get the type {@code T} for the given class + * + *

+ * If the class does not implement {@link PcodeUseropLibrary}, this returns null. If it does, + * but no arguments are given (i.e., it implements the raw type), this return {@link Object}. + * + * @param cls the class + * @return the type, or null + */ + static Type getOperandType(Class cls) { + Map, Type> args = + TypeUtils.getTypeArguments(cls, PcodeUseropLibrary.class); + if (args == null) { + return null; + } + if (args.isEmpty()) { + return Object.class; + } + return args.get(PcodeUseropLibrary.class.getTypeParameters()[0]); + } + /** * The empty userop library. * diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoRangeCellRendererTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoRangeCellRendererTest.java index addc7c54a0..406ed45f25 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoRangeCellRendererTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/table/DemoRangeCellRendererTest.java @@ -19,6 +19,7 @@ import static org.junit.Assume.assumeFalse; import java.awt.BorderLayout; import java.awt.event.*; +import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -26,19 +27,30 @@ import javax.swing.JFrame; import javax.swing.JScrollPane; import javax.swing.table.TableColumn; -import org.junit.Before; -import org.junit.Test; +import org.junit.*; import com.google.common.collect.Range; import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; +import ghidra.test.AbstractGhidraHeadedIntegrationTest; +import ghidra.test.TestEnv; import ghidra.util.SystemUtilities; import ghidra.util.table.GhidraTable; -public class DemoRangeCellRendererTest { +public class DemoRangeCellRendererTest extends AbstractGhidraHeadedIntegrationTest { + private TestEnv env; + @Before - public void checkNotBatch() { + public void setupDemo() throws IOException { assumeFalse(SystemUtilities.isInTestingBatchMode()); + env = new TestEnv(); + } + + @After + public void tearDownDemo() { + if (env != null) { + env.dispose(); + } } protected static class MyRow { @@ -105,11 +117,12 @@ public class DemoRangeCellRendererTest { @Test public void testDemoRangeCellRenderer() throws Throwable { + new TestEnv().getTool(); JFrame window = new JFrame(); window.setLayout(new BorderLayout()); DefaultEnumeratedColumnTableModel model = - new DefaultEnumeratedColumnTableModel<>("People", MyColumns.class); + new DefaultEnumeratedColumnTableModel<>(env.getTool(), "People", MyColumns.class); GhidraTable table = new GhidraTable(model); GTableFilterPanel filterPanel = new GTableFilterPanel<>(table, model); diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/timeline/TimelinePanelTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/timeline/TimelinePanelTest.java deleted file mode 100644 index ee85b93450..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/test/java/docking/widgets/timeline/TimelinePanelTest.java +++ /dev/null @@ -1,209 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package docking.widgets.timeline; - -import static org.junit.Assert.assertEquals; - -import java.awt.event.*; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.function.Function; - -import javax.swing.JFrame; -import javax.swing.JScrollPane; - -import org.junit.Before; -import org.junit.Test; - -import com.google.common.collect.Range; - -import docking.widgets.table.DefaultEnumeratedColumnTableModel; -import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn; -import docking.widgets.table.EnumeratedColumnTableModel; - -public class TimelinePanelTest { - protected static class MyRow { - private final String name; - private Range lifespan; - - public MyRow(String name, Range lifespan) { - this.name = name; - this.lifespan = lifespan; - } - - public String getName() { - return name; - } - - public Range getLifespan() { - return lifespan; - } - - public void setLifespan(Range lifespan) { - this.lifespan = lifespan; - } - - @Override - public String toString() { - return name; - } - } - - protected enum MyColumns implements EnumeratedTableColumn { - NAME("Name", String.class, MyRow::getName), - BIRTH("Lifespan", Range.class, MyRow::getLifespan); - - private String header; - private Class cls; - private Function getter; - - private MyColumns(String header, Class cls, Function getter) { - this.header = header; - this.cls = cls; - this.getter = getter; - } - - @Override - public Class getValueClass() { - return cls; - } - - @Override - public Object getValueOf(MyRow row) { - return getter.apply(row); - } - - @Override - public String getHeader() { - return header; - } - } - - private JFrame window; - private EnumeratedColumnTableModel model; - private TimelinePanel timeline; - - protected void inspect() { - window.setBounds(0, 0, 1000, 200); - CompletableFuture windowClosed = new CompletableFuture<>(); - WindowListener listener = new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - windowClosed.complete(null); - } - }; - window.addWindowListener(listener); - window.setVisible(true); - try { - windowClosed.get(); - } - catch (InterruptedException | ExecutionException e) { - // Whatever - } - finally { - window.removeWindowListener(listener); - } - } - - @Before - public void setUp() { - window = new JFrame(); - model = new DefaultEnumeratedColumnTableModel<>("People", MyColumns.class); - timeline = new TimelinePanel<>(model, MyRow::getLifespan); - window.add(new JScrollPane(timeline)); - } - - @Test - public void testEmpty() { - assertEquals(0, timeline.tracks.size()); - assertEquals(0, timeline.trackMap.size()); - } - - @Test - public void testSingleItemClosed() { - model.add(new MyRow("Albert", Range.closed(1879, 1955))); - - assertEquals(1, timeline.tracks.size()); - assertEquals(1, timeline.trackMap.size()); - assertEquals(1, timeline.tracks.get(0).objects.asMapOfRanges().size()); - } - - @Test - public void testTwoItemsOneTrack() { - model.add(new MyRow("Albert", Range.closed(1879, 1955))); - model.add(new MyRow("Bob", Range.atLeast(1956))); - - assertEquals(1, timeline.tracks.size()); - assertEquals(2, timeline.trackMap.size()); - assertEquals(2, timeline.tracks.get(0).objects.asMapOfRanges().size()); - } - - @Test - public void testTwoItemsTwoTracks() { - model.add(new MyRow("Albert", Range.closed(1879, 1955))); - model.add(new MyRow("Bill", Range.atLeast(1955))); - - assertEquals(2, timeline.tracks.size()); - assertEquals(2, timeline.trackMap.size()); - assertEquals(1, timeline.tracks.get(0).objects.asMapOfRanges().size()); - assertEquals(1, timeline.tracks.get(1).objects.asMapOfRanges().size()); - } - - @Test - public void testTwoItemsOneTrackMixedBounds() { - model.add(new MyRow("Albert", Range.closed(1879, 1955))); - model.add(new MyRow("Bill", Range.greaterThan(1955))); - - assertEquals(1, timeline.tracks.size()); - assertEquals(2, timeline.trackMap.size()); - assertEquals(2, timeline.tracks.get(0).objects.asMapOfRanges().size()); - } - - @Test - public void testTwoItemsThenClear() { - model.add(new MyRow("Albert", Range.closed(1879, 1955))); - model.add(new MyRow("Bill", Range.atLeast(1955))); - model.clear(); - - assertEquals(0, timeline.tracks.size()); - assertEquals(0, timeline.trackMap.size()); - } - - @Test - public void testTwoItemsTwoTracksRemoveOne() { - model.add(new MyRow("Albert", Range.closed(1879, 1955))); - model.add(new MyRow("Bill", Range.atLeast(1955))); - model.deleteWith(row -> row.getLifespan().contains(1900)); - - assertEquals(1, timeline.tracks.size()); - assertEquals(1, timeline.trackMap.size()); - assertEquals(1, timeline.tracks.get(0).objects.asMapOfRanges().size()); - } - - @Test - public void testTwoItemsOneTrackUpdateRequiresTwo() { - model.add(new MyRow("Albert", Range.closed(1879, 1955))); - MyRow elvis = new MyRow("Elvis", Range.closed(1935, 1977)); - model.add(elvis); - elvis.setLifespan(Range.atLeast(1935)); - model.notifyUpdated(elvis); - - assertEquals(2, timeline.tracks.size()); - assertEquals(2, timeline.trackMap.size()); - assertEquals(1, timeline.tracks.get(0).objects.asMapOfRanges().size()); - assertEquals(1, timeline.tracks.get(1).objects.asMapOfRanges().size()); - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibraryTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibraryTest.java index 2445c32f61..a8484bb9bd 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibraryTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibraryTest.java @@ -15,45 +15,74 @@ */ package ghidra.pcode.emu.linux; -import static ghidra.pcode.emu.sys.EmuSyscallLibrary.SYSCALL_CONVENTION_NAME; -import static ghidra.pcode.emu.sys.EmuSyscallLibrary.SYSCALL_SPACE_NAME; import static org.junit.Assert.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; import org.junit.*; import ghidra.app.plugin.assembler.Assembler; import ghidra.app.plugin.assembler.Assemblers; -import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.PcodeEmulator; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.sys.EmuProcessExitedException; +import ghidra.pcode.emu.sys.SyscallTestHelper; +import ghidra.pcode.emu.sys.SyscallTestHelper.SyscallName; import ghidra.pcode.emu.unix.*; import ghidra.pcode.exec.*; -import ghidra.program.model.address.*; -import ghidra.program.model.data.DataTypeConflictHandler; -import ghidra.program.model.data.PointerDataType; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; import ghidra.program.model.lang.Register; -import ghidra.program.model.lang.SpaceNames; -import ghidra.program.model.listing.FunctionManager; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; -import ghidra.program.model.symbol.SourceType; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.util.database.UndoableTransaction; import ghidra.util.task.TaskMonitor; public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessIntegrationTest { + public enum Syscall implements SyscallName { + /** + * These are a subset of the linux_amd64 system call numbers as of writing this test, but it + * doesn't really matter as long as the user program and syscall library agree. + */ + READ(0, "read"), + WRITE(1, "write"), + OPEN(2, "open"), + CLOSE(3, "close"), + READV(19, "readv"), + WRITEV(20, "writev"), + GROUP_EXIT(231, "group_exit"); + + public final int number; + public final String name; + + private Syscall(int number, String name) { + this.number = number; + this.name = name; + } + + @Override + public int getNumber() { + return number; + } + + @Override + public String getName() { + return name; + } + } + protected final class LinuxAmd64PcodeEmulator extends PcodeEmulator { protected EmuLinuxAmd64SyscallUseropLibrary syscalls; public LinuxAmd64PcodeEmulator() { - super((SleighLanguage) program.getLanguage()); + super(program.getLanguage()); } @Override @@ -63,24 +92,15 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles } } - /** - * These are the linux_amd64 system call numbers as of writing this test, but it doesn't really - * matter as long as the user program and syscall library agree. - */ - protected static final int SYSCALLNO_READ = 0; - protected static final int SYSCALLNO_WRITE = 1; - protected static final int SYSCALLNO_OPEN = 2; - protected static final int SYSCALLNO_CLOSE = 3; - protected static final int SYSCALLNO_READV = 19; - protected static final int SYSCALLNO_WRITEV = 20; - protected static final int SYSCALLNO_GROUP_EXIT = 231; + public static final SyscallTestHelper SYSCALL_HELPER = + new SyscallTestHelper(List.of(Syscall.values())); protected static final byte[] BYTES_HW = "Hello, World!\n".getBytes(); protected static final byte[] BYTES_HELLO = "Hello, ".getBytes(); protected static final byte[] BYTES_WORLD = "World!\n".getBytes(); Program program; - SleighLanguage language; + Language language; Assembler asm; Register regRIP; @@ -92,20 +112,10 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles private EmuUnixFileSystem fs; PcodeArithmetic arithmetic; - protected void placeSyscall(long number, String name) throws Exception { - AddressSpace spaceSyscall = - program.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME); - FunctionManager functions = program.getFunctionManager(); - - Address addr = spaceSyscall.getAddress(number); - functions.createFunction(name, addr, new AddressSet(addr), SourceType.USER_DEFINED) - .setCallingConvention(SYSCALL_CONVENTION_NAME); - } - @Before public void setUp() throws Exception { program = createDefaultProgram("HelloWorld", "x86:LE:64:default", "gcc", this); - language = (SleighLanguage) program.getLanguage(); + language = program.getLanguage(); arithmetic = BytesPcodeArithmetic.forLanguage(language); regRIP = program.getRegister("RIP"); @@ -119,25 +129,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles .createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY, false); - // Fulfill requirements for the syscall userop library: - // 1) The "/pointer" data type exists, so it knows the machine word size - program.getDataTypeManager() - .resolve(PointerDataType.dataType, DataTypeConflictHandler.DEFAULT_HANDLER); - // 2) Create the syscall space and add those we'll be using - Address startOther = program.getAddressFactory() - .getAddressSpace(SpaceNames.OTHER_SPACE_NAME) - .getAddress(0); - MemoryBlock blockSyscall = program.getMemory() - .createUninitializedBlock(SYSCALL_SPACE_NAME, startOther, 0x10000, true); - blockSyscall.setPermissions(true, false, true); - - placeSyscall(SYSCALLNO_READ, "read"); - placeSyscall(SYSCALLNO_WRITE, "write"); - placeSyscall(SYSCALLNO_OPEN, "open"); - placeSyscall(SYSCALLNO_CLOSE, "close"); - placeSyscall(SYSCALLNO_READV, "readv"); - placeSyscall(SYSCALLNO_WRITEV, "writev"); - placeSyscall(SYSCALLNO_GROUP_EXIT, "group_exit"); + SYSCALL_HELPER.bootstrapProgram(program); } fs = new BytesEmuUnixFileSystem(); @@ -196,12 +188,12 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles public void testWriteStdout() throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { asm.assemble(start, - "MOV RAX," + SYSCALLNO_WRITE, + "MOV RAX," + Syscall.WRITE.number, "MOV RDI," + EmuUnixFileDescriptor.FD_STDOUT, "LEA RSI,[0x00400800]", "MOV RDX," + BYTES_HW.length, "SYSCALL", - "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RAX," + Syscall.GROUP_EXIT.number, "MOV RDI,0", "SYSCALL"); block.putBytes(space.getAddress(0x00400800), BYTES_HW); @@ -228,12 +220,12 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles public void testReadStdin() throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { asm.assemble(start, - "MOV RAX," + SYSCALLNO_READ, + "MOV RAX," + Syscall.READ.number, "MOV RDI," + EmuUnixFileDescriptor.FD_STDIN, "LEA RSI,[0x00400800]", "MOV RDX," + BYTES_HW.length, "SYSCALL", - "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RAX," + Syscall.GROUP_EXIT.number, "MOV RDI,0", "SYSCALL"); } @@ -280,12 +272,12 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles buf.putLong(posIov1len, endWorld.subtract(strWorld)); asm.assemble(start, - "MOV RAX," + SYSCALLNO_WRITEV, + "MOV RAX," + Syscall.WRITEV.number, "MOV RDI," + EmuUnixFileDescriptor.FD_STDOUT, "LEA RSI,[0x" + iov + "]", "MOV RDX,2", "SYSCALL", - "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RAX," + Syscall.GROUP_EXIT.number, "MOV RDI,0", "SYSCALL"); block.putBytes(data, buf.array()); @@ -302,8 +294,8 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles // Step through writev and verify return value and actual output effect thread.stepInstruction(5); - assertEquals(BigInteger.valueOf(BYTES_HW.length), - arithmetic.toConcrete(thread.getState().getVar(regRAX))); + assertEquals(BYTES_HW.length, + arithmetic.toLong(thread.getState().getVar(regRAX), Purpose.OTHER)); assertArrayEquals(BYTES_HW, stdout.toByteArray()); stepGroupExit(thread); @@ -335,12 +327,12 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles buf.putLong(posIov1len, endWorld.subtract(strWorld)); asm.assemble(start, - "MOV RAX," + SYSCALLNO_READV, + "MOV RAX," + Syscall.READV.number, "MOV RDI," + EmuUnixFileDescriptor.FD_STDIN, "LEA RSI,[0x" + iov + "]", "MOV RDX,2", "SYSCALL", - "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RAX," + Syscall.GROUP_EXIT.number, "MOV RDI,0", "SYSCALL"); block.putBytes(data, buf.array()); @@ -357,8 +349,8 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles // Step through readv and verify return value and actual output effect thread.stepInstruction(5); - assertEquals(BigInteger.valueOf(BYTES_HW.length), - arithmetic.toConcrete(thread.getState().getVar(regRAX))); + assertEquals(BYTES_HW.length, + arithmetic.toLong(thread.getState().getVar(regRAX), Purpose.OTHER)); assertArrayEquals(BYTES_HELLO, emu.getSharedState().getVar(space, strHello.getOffset(), BYTES_HELLO.length, true)); assertArrayEquals(BYTES_WORLD, @@ -371,7 +363,7 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles public void testOpenWriteClose() throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { asm.assemble(start, - "MOV RAX," + SYSCALLNO_OPEN, + "MOV RAX," + Syscall.OPEN.number, "LEA RDI,[0x00400880]", "MOV RSI," + (AbstractEmuLinuxSyscallUseropLibrary.O_WRONLY | AbstractEmuLinuxSyscallUseropLibrary.O_CREAT), @@ -379,16 +371,16 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles "SYSCALL", "MOV RBP, RAX", - "MOV RAX," + SYSCALLNO_WRITE, + "MOV RAX," + Syscall.WRITE.number, "MOV RDI,RBP", "LEA RSI,[0x00400800]", "MOV RDX," + BYTES_HW.length, "SYSCALL", - "MOV RAX," + SYSCALLNO_CLOSE, + "MOV RAX," + Syscall.CLOSE.number, "MOV RDI,RBP", - "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RAX," + Syscall.GROUP_EXIT.number, "MOV RDI,0", "SYSCALL"); block.putBytes(space.getAddress(0x00400800), BYTES_HW); @@ -409,23 +401,23 @@ public class EmuLinuxAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadles public void testOpenReadClose() throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { asm.assemble(start, - "MOV RAX," + SYSCALLNO_OPEN, + "MOV RAX," + Syscall.OPEN.number, "LEA RDI,[0x00400880]", "MOV RSI," + (AbstractEmuLinuxSyscallUseropLibrary.O_RDONLY), "MOV RDX," + (0600), "SYSCALL", "MOV RBP, RAX", - "MOV RAX," + SYSCALLNO_READ, + "MOV RAX," + Syscall.READ.number, "MOV RDI,RBP", "LEA RSI,[0x00400800]", "MOV RDX," + BYTES_HW.length, "SYSCALL", - "MOV RAX," + SYSCALLNO_CLOSE, + "MOV RAX," + Syscall.CLOSE.number, "MOV RDI,RBP", - "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RAX," + Syscall.GROUP_EXIT.number, "MOV RDI,0", "SYSCALL"); block.putBytes(space.getAddress(0x00400880), "myfile\0".getBytes()); diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibraryTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibraryTest.java index 594dccb2ec..fe1a1da8d0 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibraryTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibraryTest.java @@ -15,15 +15,13 @@ */ package ghidra.pcode.emu.linux; -import static ghidra.pcode.emu.sys.EmuSyscallLibrary.SYSCALL_CONVENTION_NAME; -import static ghidra.pcode.emu.sys.EmuSyscallLibrary.SYSCALL_SPACE_NAME; import static org.junit.Assert.*; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; -import java.math.BigInteger; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.List; import org.junit.*; @@ -33,27 +31,58 @@ import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.PcodeEmulator; import ghidra.pcode.emu.PcodeThread; import ghidra.pcode.emu.sys.EmuProcessExitedException; +import ghidra.pcode.emu.sys.SyscallTestHelper; +import ghidra.pcode.emu.sys.SyscallTestHelper.SyscallName; import ghidra.pcode.emu.unix.*; import ghidra.pcode.exec.*; -import ghidra.program.model.address.*; -import ghidra.program.model.data.DataTypeConflictHandler; -import ghidra.program.model.data.PointerDataType; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Register; -import ghidra.program.model.lang.SpaceNames; -import ghidra.program.model.listing.FunctionManager; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemoryBlock; -import ghidra.program.model.symbol.SourceType; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.util.database.UndoableTransaction; import ghidra.util.task.TaskMonitor; public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessIntegrationTest { + public enum Syscall implements SyscallName { + /** + * These are the linux_x86 system call numbers as of writing this test, but it doesn't + * really matter as long as the user program and syscall library agree. + */ + EXIT(1, "exit"), + READ(3, "read"), + WRITE(4, "write"), + OPEN(5, "open"), + CLOSE(6, "close"), + READV(145, "readv"), + WRITEV(146, "writev"); + + public final int number; + public final String name; + + private Syscall(int number, String name) { + this.number = number; + this.name = name; + } + + @Override + public int getNumber() { + return number; + } + + @Override + public String getName() { + return name; + } + } + protected final class LinuxX86PcodeEmulator extends PcodeEmulator { protected EmuLinuxX86SyscallUseropLibrary syscalls; public LinuxX86PcodeEmulator() { - super((SleighLanguage) program.getLanguage()); + super(program.getLanguage()); } @Override @@ -63,17 +92,8 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI } } - /** - * These are the linux_x86 system call numbers as of writing this test, but it doesn't really - * matter as long as the user program and syscall library agree. - */ - protected static final int SYSCALLNO_EXIT = 1; - protected static final int SYSCALLNO_READ = 3; - protected static final int SYSCALLNO_WRITE = 4; - protected static final int SYSCALLNO_OPEN = 5; - protected static final int SYSCALLNO_CLOSE = 6; - protected static final int SYSCALLNO_READV = 145; - protected static final int SYSCALLNO_WRITEV = 146; + public static final SyscallTestHelper SYSCALL_HELPER = + new SyscallTestHelper(List.of(Syscall.values())); protected static final byte[] BYTES_HW = "Hello, World!\n".getBytes(); protected static final byte[] BYTES_HELLO = "Hello, ".getBytes(); @@ -92,16 +112,6 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI private EmuUnixFileSystem fs; PcodeArithmetic arithmetic; - protected void placeSyscall(long number, String name) throws Exception { - AddressSpace spaceSyscall = - program.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME); - FunctionManager functions = program.getFunctionManager(); - - Address addr = spaceSyscall.getAddress(number); - functions.createFunction(name, addr, new AddressSet(addr), SourceType.USER_DEFINED) - .setCallingConvention(SYSCALL_CONVENTION_NAME); - } - @Before public void setUp() throws Exception { program = createDefaultProgram("HelloWorld", "x86:LE:32:default", "gcc", this); @@ -119,25 +129,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI .createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY, false); - // Fulfill requirements for the syscall userop library: - // 1) The "/pointer" data type exists, so it knows the machine word size - program.getDataTypeManager() - .resolve(PointerDataType.dataType, DataTypeConflictHandler.DEFAULT_HANDLER); - // 2) Create the syscall space and add those we'll be using - Address startOther = program.getAddressFactory() - .getAddressSpace(SpaceNames.OTHER_SPACE_NAME) - .getAddress(0); - MemoryBlock blockSyscall = program.getMemory() - .createUninitializedBlock(SYSCALL_SPACE_NAME, startOther, 0x10000, true); - blockSyscall.setPermissions(true, false, true); - - placeSyscall(SYSCALLNO_EXIT, "exit"); - placeSyscall(SYSCALLNO_READ, "read"); - placeSyscall(SYSCALLNO_WRITE, "write"); - placeSyscall(SYSCALLNO_OPEN, "open"); - placeSyscall(SYSCALLNO_CLOSE, "close"); - placeSyscall(SYSCALLNO_READV, "readv"); - placeSyscall(SYSCALLNO_WRITEV, "writev"); + SYSCALL_HELPER.bootstrapProgram(program); } fs = new BytesEmuUnixFileSystem(); @@ -196,12 +188,12 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI public void testWriteStdout() throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { asm.assemble(start, - "MOV EAX," + SYSCALLNO_WRITE, + "MOV EAX," + Syscall.WRITE.number, "MOV EBX," + EmuUnixFileDescriptor.FD_STDOUT, "LEA ECX,[0x00400800]", "MOV EDX," + BYTES_HW.length, "INT 0x80", - "MOV EAX," + SYSCALLNO_EXIT, + "MOV EAX," + Syscall.EXIT.number, "MOV EBX,0", "INT 0x80"); block.putBytes(space.getAddress(0x00400800), BYTES_HW); @@ -228,12 +220,12 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI public void testReadStdin() throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { asm.assemble(start, - "MOV EAX," + SYSCALLNO_READ, + "MOV EAX," + Syscall.READ.number, "MOV EBX," + EmuUnixFileDescriptor.FD_STDIN, "LEA ECX,[0x00400800]", "MOV EDX," + BYTES_HW.length, "INT 0x80", - "MOV EAX," + SYSCALLNO_EXIT, + "MOV EAX," + Syscall.EXIT.number, "MOV EBX,0", "INT 0x80"); } @@ -280,12 +272,12 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI buf.putInt(posIov1len, (int) endWorld.subtract(strWorld)); asm.assemble(start, - "MOV EAX," + SYSCALLNO_WRITEV, + "MOV EAX," + Syscall.WRITEV.number, "MOV EBX," + EmuUnixFileDescriptor.FD_STDOUT, "LEA ECX,[0x" + iov + "]", "MOV EDX,2", "INT 0x80", - "MOV EAX," + SYSCALLNO_EXIT, + "MOV EAX," + Syscall.EXIT.number, "MOV EBX,0", "INT 0x80"); block.putBytes(data, buf.array()); @@ -302,8 +294,8 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI // Step through writev and verify return value and actual output effect thread.stepInstruction(5); - assertEquals(BigInteger.valueOf(BYTES_HW.length), - arithmetic.toConcrete(thread.getState().getVar(regEAX))); + assertEquals(BYTES_HW.length, + arithmetic.toLong(thread.getState().getVar(regEAX), Purpose.OTHER)); assertArrayEquals(BYTES_HW, stdout.toByteArray()); stepGroupExit(thread); @@ -335,12 +327,12 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI buf.putInt(posIov1len, (int) endWorld.subtract(strWorld)); asm.assemble(start, - "MOV EAX," + SYSCALLNO_READV, + "MOV EAX," + Syscall.READV.number, "MOV EBX," + EmuUnixFileDescriptor.FD_STDIN, "LEA ECX,[0x" + iov + "]", "MOV EDX,2", "INT 0x80", - "MOV EAX," + SYSCALLNO_EXIT, + "MOV EAX," + Syscall.EXIT.number, "MOV EBX,0", "INT 0x80"); block.putBytes(data, buf.array()); @@ -357,8 +349,8 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI // Step through readv and verify return value and actual output effect thread.stepInstruction(5); - assertEquals(BigInteger.valueOf(BYTES_HW.length), - arithmetic.toConcrete(thread.getState().getVar(regEAX))); + assertEquals(BYTES_HW.length, + arithmetic.toLong(thread.getState().getVar(regEAX), Purpose.OTHER)); assertArrayEquals(BYTES_HELLO, emu.getSharedState().getVar(space, strHello.getOffset(), BYTES_HELLO.length, true)); assertArrayEquals(BYTES_WORLD, @@ -371,7 +363,7 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI public void testOpenWriteClose() throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { asm.assemble(start, - "MOV EAX," + SYSCALLNO_OPEN, + "MOV EAX," + Syscall.OPEN.number, "LEA EBX,[0x00400880]", "MOV ECX," + (AbstractEmuLinuxSyscallUseropLibrary.O_WRONLY | AbstractEmuLinuxSyscallUseropLibrary.O_CREAT), @@ -379,16 +371,16 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI "INT 0x80", "MOV EBP, EAX", - "MOV EAX," + SYSCALLNO_WRITE, + "MOV EAX," + Syscall.WRITE.number, "MOV EBX,EBP", "LEA ECX,[0x00400800]", "MOV EDX," + BYTES_HW.length, "INT 0x80", - "MOV EAX," + SYSCALLNO_CLOSE, + "MOV EAX," + Syscall.CLOSE.number, "MOV EBX,EBP", - "MOV EAX," + SYSCALLNO_EXIT, + "MOV EAX," + Syscall.EXIT.number, "MOV EBX,0", "INT 0x80"); block.putBytes(space.getAddress(0x00400800), BYTES_HW); @@ -409,23 +401,23 @@ public class EmuLinuxX86SyscallUseropLibraryTest extends AbstractGhidraHeadlessI public void testOpenReadClose() throws Exception { try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { asm.assemble(start, - "MOV EAX," + SYSCALLNO_OPEN, + "MOV EAX," + Syscall.OPEN.number, "LEA EBX,[0x00400880]", "MOV ECX," + (AbstractEmuLinuxSyscallUseropLibrary.O_RDONLY), "MOV EDX," + (0600), "INT 0x80", "MOV EBP, EAX", - "MOV EAX," + SYSCALLNO_READ, + "MOV EAX," + Syscall.READ.number, "MOV EBX,EBP", "LEA ECX,[0x00400800]", "MOV EDX," + BYTES_HW.length, "INT 0x80", - "MOV EAX," + SYSCALLNO_CLOSE, + "MOV EAX," + Syscall.CLOSE.number, "MOV EBX,EBP", - "MOV EAX," + SYSCALLNO_EXIT, + "MOV EAX," + Syscall.EXIT.number, "MOV EBX,0", "INT 0x80"); block.putBytes(space.getAddress(0x00400880), "myfile\0".getBytes()); diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/EmuAmd64SyscallUseropLibraryTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/EmuAmd64SyscallUseropLibraryTest.java index e6373850c2..548c7bc888 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/EmuAmd64SyscallUseropLibraryTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/EmuAmd64SyscallUseropLibraryTest.java @@ -26,6 +26,7 @@ import ghidra.app.plugin.assembler.Assemblers; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.emu.*; import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.*; import ghidra.program.model.data.DataTypeConflictHandler; import ghidra.program.model.data.PointerDataType; @@ -62,7 +63,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte @Override public long readSyscallNumber(PcodeExecutorStatePiece state) { - return machine.getArithmetic().toConcrete(state.getVar(regRAX)).longValue(); + return machine.getArithmetic().toLong(state.getVar(regRAX), Purpose.OTHER); } @PcodeUserop @@ -87,7 +88,7 @@ public class EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessInte protected SyscallTestUseropLibrary syscalls; public SyscallTestPcodeEmulator() { - super((SleighLanguage) program.getLanguage()); + super(program.getLanguage()); } @Override diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/SyscallTestHelper.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/SyscallTestHelper.java new file mode 100644 index 0000000000..a99047cde8 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/SyscallTestHelper.java @@ -0,0 +1,108 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.sys; + +import static ghidra.pcode.emu.sys.EmuSyscallLibrary.SYSCALL_CONVENTION_NAME; +import static ghidra.pcode.emu.sys.EmuSyscallLibrary.SYSCALL_SPACE_NAME; + +import java.util.List; + +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.data.PointerDataType; +import ghidra.program.model.lang.SpaceNames; +import ghidra.program.model.listing.FunctionManager; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.symbol.SourceType; + +/** + * Utilities for preparing a program for use in testing system call simulations + */ +public class SyscallTestHelper { + /** + * A number-name pair defining a syscall + */ + public interface SyscallName { + /** + * Get the system call number, i.e., its offset in "syscall"/OTHER space + * + * @return the syscall number + */ + int getNumber(); + + /** + * Get the system call name, i.e., its API name and the name used in the system call + * simulator's library + * + * @return the syscall name + */ + String getName(); + } + + private final List names; + + /** + * Create a helper using the given list of system calls + * + * @param names the number-name pairs for the system calls to support + */ + public SyscallTestHelper(List names) { + this.names = names; + } + + /** + * Prepare a program, simulating the analysis normally performed by the system call analyzer + * + * @param program the program to prepare + * @throws Exception if something goes wrong + */ + public void bootstrapProgram(Program program) throws Exception { + // Fulfill requirements for the syscall userop library: + // 1) The "/pointer" data type exists, so it knows the machine word size + program.getDataTypeManager() + .resolve(PointerDataType.dataType, DataTypeConflictHandler.DEFAULT_HANDLER); + // 2) Create the syscall space and add those we'll be using + Address startOther = program.getAddressFactory() + .getAddressSpace(SpaceNames.OTHER_SPACE_NAME) + .getAddress(0); + MemoryBlock blockSyscall = program.getMemory() + .createUninitializedBlock(SYSCALL_SPACE_NAME, startOther, 0x10000, true); + blockSyscall.setPermissions(true, false, true); + + for (SyscallName n : names) { + placeSyscall(program, n.getNumber(), n.getName()); + } + } + + /** + * Place a system call function in the "syscall" overlay on OTHER + * + * @param program the program with "syscall" space already create + * @param number the syscall number + * @param name the syscall name + * @throws Exception if something goes wrong + */ + protected void placeSyscall(Program program, long number, String name) throws Exception { + AddressSpace spaceSyscall = + program.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME); + FunctionManager functions = program.getFunctionManager(); + + Address addr = spaceSyscall.getAddress(number); + functions.createFunction(name, addr, new AddressSet(addr), SourceType.USER_DEFINED) + .setCallingConvention(SYSCALL_CONVENTION_NAME); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java index 0c2ebb30ea..f355808804 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java @@ -197,10 +197,10 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGhidraHeadlessInteg @Test public void testOpState() throws Exception { var library = new TestUseropLibrary() { - PcodeExecutorStatePiece state; + PcodeExecutorState state; @PcodeUserop - private void __testop(@OpState PcodeExecutorStatePiece state) { + private void __testop(@OpState PcodeExecutorState state) { this.state = state; } }; @@ -247,7 +247,7 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGhidraHeadlessInteg public void testKitchenSink() throws Exception { var library = new TestUseropLibrary() { PcodeExecutor executor; - PcodeExecutorStatePiece state; + PcodeExecutorState state; PcodeUseropLibrary lib; Varnode outVar; Varnode inVar0; @@ -259,7 +259,7 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGhidraHeadlessInteg @OpLibrary PcodeUseropLibrary lib, @OpExecutor PcodeExecutor executor, Varnode inVar0, - @OpState PcodeExecutorStatePiece state, + @OpState PcodeExecutorState state, byte[] inVal1) { this.executor = executor; this.state = state; @@ -344,6 +344,15 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGhidraHeadlessInteg }; } + @Test(expected = IllegalArgumentException.class) + public void testErrExecutorTypeParam() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpExecutor PcodeExecutor executor) { + } + }; + } + @Test(expected = IllegalArgumentException.class) public void testErrStateType() throws Exception { new TestUseropLibrary() { @@ -353,6 +362,15 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGhidraHeadlessInteg }; } + @Test(expected = IllegalArgumentException.class) + public void testErrStateTypeParam() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpState PcodeExecutorState state) { + } + }; + } + @Test(expected = IllegalArgumentException.class) public void testErrOutputType() throws Exception { new TestUseropLibrary() { @@ -394,8 +412,8 @@ public class AnnotatedPcodeUseropLibraryTest extends AbstractGhidraHeadlessInteg public void testErrDuplicateState() throws Exception { new TestUseropLibrary() { @PcodeUserop - private void __testop(@OpState PcodeExecutorStatePiece state0, - @OpState PcodeExecutorStatePiece state1) { + private void __testop(@OpState PcodeExecutorState state0, + @OpState PcodeExecutorState state1) { } }; } diff --git a/Ghidra/Debug/TaintAnalysis/Module.manifest b/Ghidra/Debug/TaintAnalysis/Module.manifest new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Ghidra/Debug/TaintAnalysis/README.md b/Ghidra/Debug/TaintAnalysis/README.md new file mode 100644 index 0000000000..9aa52b9ee4 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/README.md @@ -0,0 +1,7 @@ +# Taint Analysis Module + +This module is both a useful feature and a good reference for implementing a custom emulator + +Users: see the help pages in Ghidra. + +Developers: see the javadocs for the `ghidra.taint` package. diff --git a/Ghidra/Debug/TaintAnalysis/build.gradle b/Ghidra/Debug/TaintAnalysis/build.gradle new file mode 100644 index 0000000000..1c1fc7e82d --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/build.gradle @@ -0,0 +1,33 @@ +/* ### + * 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. + */ +apply from: "$rootProject.projectDir/gradle/javaProject.gradle" +apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle" +apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle" +apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle" + +apply plugin: 'eclipse' +eclipse.project.name = 'Debug TaintAnalysis' + +dependencies { + api project(':Debugger') + + // Oof. Apparently, this doesn't work transitively.... + testImplementation project(path: ':ProposedUtils', configuration: 'testArtifacts') + testImplementation project(path: ':Framework-AsyncComm', configuration: 'testArtifacts') + testImplementation project(path: ':Framework-Debugging', configuration: 'testArtifacts') + testImplementation project(path: ':Framework-TraceModeling', configuration: 'testArtifacts') + testImplementation project(path: ':Debugger', configuration: 'testArtifacts') +} diff --git a/Ghidra/Debug/TaintAnalysis/certification.manifest b/Ghidra/Debug/TaintAnalysis/certification.manifest new file mode 100644 index 0000000000..97db3c3c58 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/certification.manifest @@ -0,0 +1,4 @@ +##VERSION: 2.0 +##MODULE IP: JSch License +Module.manifest||GHIDRA||||END| +README.md||GHIDRA||||END| diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..36bf8ad17d --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/AbstractTaintPcodeExecutorStatePiece.java @@ -0,0 +1,118 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint; + +import ghidra.pcode.emu.taint.plain.TaintSpace; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; +import ghidra.program.model.mem.MemBuffer; +import ghidra.taint.model.TaintVec; + +/** + * An abstract taint state piece + * + *

+ * Because we want to reduce code repetition, we use the type hierarchy to increase the capabilities + * of the state piece as we progress from stand-alone to Debugger-integrated. The framework-provided + * class from which this derives, however, introduces the idea of a space map, whose values have + * type {@code }. We'll be using types derived from {@link TaintSpace}, which is where all the + * taint storage logic is actually located. Because that logic is what we're actually extending with + * each more capable state piece, we have to ensure that type can be substituted. Thus, we have to + * create these abstract classes from which the actual state pieces are derived, leaving {@code } + * bounded, but unspecified. + * + * @param the type of spaces + */ +public abstract class AbstractTaintPcodeExecutorStatePiece + extends AbstractLongOffsetPcodeExecutorStatePiece { + + /** + * The map from address space to storage space + * + *

+ * While the concept is introduced in the super class, we're not required to actually use one. + * We just have to implement {@link #getForSpace(AddressSpace, boolean)}. Nevertheless, the + * provided map is probably the best way, so we'll follow the pattern. + */ + protected final AbstractSpaceMap spaceMap = newSpaceMap(); + + /** + * Create a state piece + * + * @param language the emulator's language + * @param addressArithmetic the arithmetic for the address type + * @param arithmetic the arithmetic for the value type + */ + public AbstractTaintPcodeExecutorStatePiece(Language language, + PcodeArithmetic addressArithmetic, PcodeArithmetic arithmetic) { + super(language, addressArithmetic, arithmetic); + } + + /** + * Extension point: Create the actual space map + * + *

+ * This will need to be implemented by each state piece, i.e., non-abstract derivating class. + * The space map will provide instances of {@code }, which will provide the actual (extended) + * storage logic. + * + * @return the space map + */ + protected abstract AbstractSpaceMap newSpaceMap(); + + @Override + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { + throw new ConcretionError("Cannot make Taint concrete", purpose); + } + + /** + * {@inheritDoc} + * + *

+ * Here, we just follow the pattern: delegate to the space map. + */ + @Override + protected S getForSpace(AddressSpace space, boolean toWrite) { + return spaceMap.getForSpace(space, toWrite); + } + + /** + * {@inheritDoc} + * + *

+ * Because the super class places no bound on {@code }, we have to provide the delegation to + * the storage space. + */ + @Override + protected void setInSpace(TaintSpace space, long offset, int size, TaintVec val) { + space.set(offset, val); + } + + /** + * {@inheritDoc} + * + *

+ * Because the super class places no bound on {@code }, we have to provide the delegation to + * the storage space. + */ + @Override + protected TaintVec getFromSpace(TaintSpace space, long offset, int size) { + return space.get(offset, size); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java new file mode 100644 index 0000000000..191b0640df --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPartsFactory.java @@ -0,0 +1,236 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.app.plugin.core.debug.service.emulation.ReadsTargetMemoryPcodeExecutorStatePiece; +import ghidra.app.plugin.core.debug.service.emulation.ReadsTargetRegistersPcodeExecutorStatePiece; +import ghidra.pcode.emu.*; +import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; +import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; +import ghidra.pcode.emu.taint.full.TaintDebuggerPcodeExecutorState; +import ghidra.pcode.emu.taint.plain.TaintPcodeExecutorState; +import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorState; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; +import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; +import ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece; +import ghidra.pcode.exec.trace.TracePcodeExecutorState; +import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; +import ghidra.program.model.lang.Language; +import ghidra.taint.model.TaintVec; + +/** + * The parts factory for creating emulators with taint analysis + * + *

+ * This is probably the most straightforward means of implementing a concrete-plus-auxiliary + * emulator in Ghidra. For our case, the auxiliary piece is the {@link TaintVec}. Ideally, the + * auxiliary piece implements the analog of a byte array, so that each byte in the concrete piece + * corresponds to an element in the abstract piece. We've done that here by letting each taint set + * in the vector be the taint on the corresponding byte. Each part we implement must adhere to that + * rule. For an overview of the parts of a p-code emulator, see {@link PcodeEmulator}. + * + *

+ * As recommended by the documentation, we've implemented the factory as a singleton. As presented + * in the source, we'll visit each component in this order: + *

    + *
  • P-code Arithmetic: {@link TaintPcodeArithmetic}
  • + *
  • Userop Library: {@link TaintPcodeUseropLibrary}
  • + *
  • P-code Executor: {@link TaintPcodeThreadExecutor}
  • + *
  • Machine State
  • + *
      + *
    • Stand alone: {@link TaintPcodeExecutorState}
    • + *
    • Trace integrated: {@link TaintTracePcodeExecutorState}
    • + *
    • Debugger integrated: {@link TaintDebuggerPcodeExecutorState}
    • + *
    + *
+ * + *

+ * If you're following from the {@link ghidra.taint} package documentation, you'll want to return to + * {@link ghidra.pcode.emu.taint.plain} before you examine the trace-integrated state. Similarly, + * you'll want to return to {@link ghidra.pcode.emu.taint.trace} before you examine the + * Debugger-integrated state. + */ +public enum TaintPartsFactory implements AuxDebuggerEmulatorPartsFactory { + /** This singleton factory instance */ + INSTANCE; + + /** + * {@inheritDoc} + * + *

+ * Here we simply return the arithmetic for taint vectors for the emulator's language. + */ + @Override + public PcodeArithmetic getArithmetic(Language language) { + return TaintPcodeArithmetic.forLanguage(language); + } + + /** + * {@inheritDoc} + * + *

+ * We introduce two userops for tainting variables. Aside from initializing a trace (assuming a + * trace-integrated emulator), or writing directly to the state in the script, this library will + * allow clients to quickly initialize taints in the machine. Furthermore, this can permit the + * placement of taints in intermediate states of the machine during its execution. We construct + * and return the library here. + */ + @Override + public PcodeUseropLibrary> createSharedUseropLibrary( + AuxPcodeEmulator emulator) { + return new TaintPcodeUseropLibrary(); + } + + /** + * {@inheritDoc} + * + *

+ * We have no thread-specific userops to add, which means we also have no need to stubs, so here + * we just return the empty library. + */ + @Override + public PcodeUseropLibrary> createLocalUseropStub( + AuxPcodeEmulator emulator) { + return PcodeUseropLibrary.nil(); + } + + /** + * {@inheritDoc} + * + *

+ * We have no thread-specific userops to add, so here we just return the empty library. + */ + @Override + public PcodeUseropLibrary> createLocalUseropLibrary( + AuxPcodeEmulator emulator, PcodeThread> thread) { + return PcodeUseropLibrary.nil(); + } + + /** + * {@inheritDoc} + * + *

+ * We'd like to instrument conditional branches to check for taint, so we'll need a custom + * executor. We construct it here. + */ + @Override + public PcodeThreadExecutor> createExecutor( + AuxPcodeEmulator emulator, + DefaultPcodeThread> thread) { + return new TaintPcodeThreadExecutor(thread); + } + + /** + * {@inheritDoc} + * + *

+ * To track what variables in the machine state are tainted, we need a taint state. We construct + * the part for the machine's memory here. + */ + @Override + public PcodeExecutorState> createSharedState( + AuxPcodeEmulator emulator, BytesPcodeExecutorStatePiece concrete) { + return new TaintPcodeExecutorState(emulator.getLanguage(), concrete); + } + + /** + * {@inheritDoc} + * + *

+ * To track what variables in the machine state are tainted, we need a taint state. We construct + * the part for a thread's registers and temporary (unique) variables here. + */ + @Override + public PcodeExecutorState> createLocalState( + AuxPcodeEmulator emulator, PcodeThread> thread, + BytesPcodeExecutorStatePiece concrete) { + return new TaintPcodeExecutorState(emulator.getLanguage(), concrete); + } + + /** + * {@inheritDoc} + * + *

+ * If you're following the {@link ghidra.taint} package documentation, please finish reading + * about the stand-alone emulator before proceeding to this trace-integrated part. This part + * extends the shared state of the stand-alone emulator so that it can lazily deserialize taint + * sets from a trace database. It can also serialize intermediate and/or final taint sets back + * into a trace database. + */ + @Override + public TracePcodeExecutorState> createTraceSharedState( + AuxTracePcodeEmulator emulator, BytesTracePcodeExecutorStatePiece concrete) { + return new TaintTracePcodeExecutorState(concrete); + } + + /** + * {@inheritDoc} + * + *

+ * If you're following the {@link ghidra.taint} package documentation, please finish reading + * about the stand-alone emulator before proceeding to this trace-integrated part. This part + * extends the local state of the stand-alone emulator so that it can lazily deserialize taint + * sets from a trace database. It can also serialize intermediate and/or final taint sets back + * into a trace database. + */ + @Override + public TracePcodeExecutorState> createTraceLocalState( + AuxTracePcodeEmulator emulator, PcodeThread> emuThread, + BytesTracePcodeExecutorStatePiece concrete) { + return new TaintTracePcodeExecutorState(concrete); + } + + /** + * {@inheritDoc} + * + *

+ * If you're following the {@link ghidra.taint} package documentation, please finish reading + * about the stand-alone and trace-integrated emulators before proceeding to this + * Debugger-integrated part. This part extends the shared state of the trace-integrated emulator + * so that it can also deserialize taint sets from mapped static programs. Since taint is not + * generally a concept understood by a live debugger, we need not retrieve anything (other than + * concrete state) from the target. + */ + @Override + public TracePcodeExecutorState> createDebuggerSharedState( + AuxDebuggerPcodeEmulator emulator, + ReadsTargetMemoryPcodeExecutorStatePiece concrete) { + return new TaintDebuggerPcodeExecutorState(concrete); + } + + /** + * {@inheritDoc} + * + *

+ * If you're following the {@link ghidra.taint} package documentation, please finish reading + * about the stand-alone and trace-integrated emulators before proceeding to this method. Since + * taint is not generally a concept understood by a live debugger, we need not retrieve anything + * (other than concrete state) from the target. Furthermore, because static program mappings do + * not apply to registers, we need not consider them. Thus, we can just re-use the + * trace-integrated local state. The concrete piece given to us, which we just pass to our + * paired state, will handle retrieving concrete values from the live target, if applicable. + */ + @Override + public TracePcodeExecutorState> createDebuggerLocalState( + AuxDebuggerPcodeEmulator emulator, + PcodeThread> emuThread, + ReadsTargetRegistersPcodeExecutorStatePiece concrete) { + return new TaintTracePcodeExecutorState(concrete); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeArithmetic.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeArithmetic.java new file mode 100644 index 0000000000..25e2ce35b2 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeArithmetic.java @@ -0,0 +1,238 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint; + +import java.util.Objects; + +import ghidra.pcode.exec.ConcretionError; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.program.model.lang.Endian; +import ghidra.program.model.lang.Language; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.taint.model.TaintVec; +import ghidra.taint.model.TaintVec.ShiftMode; + +/** + * The p-code arithmetic on the taint domain + * + *

+ * The p-code arithmetic serves as the bridge between p-code and the domain of analysis. + * Technically, the state itself also contributes minimally to that bridge. + */ +public enum TaintPcodeArithmetic implements PcodeArithmetic { + /** The instance for big-endian languages */ + BIG_ENDIAN(Endian.BIG), + /** The instance for little-endian languages */ + LITTLE_ENDIAN(Endian.LITTLE); + + /** + * Get the taint arithmetic for the given endianness + * + *

+ * This method is provided since clients of this class may expect it, as they would for any + * realization of {@link PcodeArithmetic}. + * + * @param bigEndian true for big endian, false for little + * @return the arithmetic + */ + public static TaintPcodeArithmetic forEndian(boolean bigEndian) { + return bigEndian ? BIG_ENDIAN : LITTLE_ENDIAN; + } + + /** + * Get the taint arithmetic for the given langauge + * + *

+ * This method is provided since clients of this class may expect it, as they would for any + * realization of {@link PcodeArithmetic}. + * + * @param language the langauge + * @return the arithmetic + */ + public static TaintPcodeArithmetic forLanguage(Language language) { + return forEndian(language.isBigEndian()); + } + + private final Endian endian; + + private TaintPcodeArithmetic(Endian endian) { + this.endian = endian; + } + + @Override + public Endian getEndian() { + return endian; + } + + /** + * {@inheritDoc} + * + *

+ * We can't just naively return {@code in1}, because each unary op may mix the bytes of the + * operand a little differently. For {@link PcodeOp#COPY}, we can, since no mixing happens at + * all. This is also the case of both {@link NEGATE} operations ("negate" is a bit of a + * misnomer, as they merely inverts the bits.) For {@link PcodeOp#INT_ZEXT}, we append empties + * to the correct end of the vector. Similarly, we replicate the most-significant element and + * append for {@link PcodeOp#INT_SEXT}. For {@link PcodeOp#INT_2COMP} (which negates an integer + * in 2's complement), we have to consider that the "add one" step may cause a cascade of + * carries. All others, we assume every byte could be tainted by any other byte in the vector, + * so we union and broadcast. + */ + @Override + public TaintVec unaryOp(int opcode, int sizeout, int sizein1, TaintVec in1) { + switch (opcode) { + case PcodeOp.COPY: + case PcodeOp.BOOL_NEGATE: + case PcodeOp.INT_NEGATE: + return in1; + case PcodeOp.INT_ZEXT: + return in1.extended(sizeout, endian.isBigEndian(), false); + case PcodeOp.INT_SEXT: + return in1.extended(sizeout, endian.isBigEndian(), true); + case PcodeOp.INT_2COMP: + return in1.copy().setCascade(endian.isBigEndian()); + default: + return TaintVec.copies(in1.union(), sizeout); + } + } + + /** + * {@inheritDoc} + * + *

+ * We override the form taking the full p-code op, so that we can treat certain idioms. Notably, + * on x86, {@code XOR RAX,RAX} is a common optimization of {@code MOV RAX,0}, since it takes + * fewer bytes to encode. Thus, we must examine the input variables, not their values, to detect + * this. Note that, while less common, {@code SUB RAX,RAX} would accomplish the same. + * Additionally, in p-code {@link PcodeOp#INT_XOR} is identical to {@link PcodeOp#BOOL_XOR}. + * When we detect these idioms, we want to clear any taints, since the value output is constant. + * This is achieved intuitively, by deferring to {@link #fromConst(long, int)}, passing in 0 and + * the output size. + */ + @Override + public TaintVec binaryOp(PcodeOp op, TaintVec in1, TaintVec in2) { + // TODO: Detect immediate operands and be more precise + switch (op.getOpcode()) { + case PcodeOp.INT_XOR: + case PcodeOp.INT_SUB: + case PcodeOp.BOOL_XOR: + if (Objects.equals(op.getInput(0), op.getInput(1))) { + return fromConst(0, op.getOutput().getSize()); + } + default: + } + return PcodeArithmetic.super.binaryOp(op, in1, in2); + } + + /** + * {@inheritDoc} + * + *

+ * For bitwise operations, we pair-wise union corresponding elements of the two input taint + * vectors. For integer add and subtract, we do the same, but account for the carry bits + * possibly cascading into bytes of higher significance. For {@link PcodeOp#PIECE}, we perform + * the analog as on concrete state, since the operand sizes are constant. For all others, we + * must consider that every output byte is potentially affected by any or all bytes of both + * input operands. Thus, we union and broadcast. + */ + @Override + public TaintVec binaryOp(int opcode, int sizeout, int sizein1, TaintVec in1, + int sizein2, TaintVec in2) { + switch (opcode) { + case PcodeOp.BOOL_AND: + case PcodeOp.BOOL_OR: + case PcodeOp.BOOL_XOR: + case PcodeOp.INT_AND: + case PcodeOp.INT_OR: + case PcodeOp.INT_XOR: + return in1.zipUnion(in2); + case PcodeOp.INT_ADD: + case PcodeOp.INT_SUB: { + TaintVec temp = in1.zipUnion(in2); + return temp.setCascade(endian.isBigEndian()); + } + case PcodeOp.PIECE: { + TaintVec temp = in1.extended(sizeout, endian.isBigEndian(), false); + temp.setShifted(endian.isBigEndian() ? -sizein2 : sizein2, ShiftMode.UNBOUNDED); + return temp.set(endian.isBigEndian() ? sizeout - sizein2 : 0, in2); + } + default: { + TaintVec temp = in1.zipUnion(in2); + return temp.setCopies(temp.union()); + } + } + } + + /** + * {@inheritDoc} + * + *

+ * Here we handle indirect taint for indirect writes + */ + @Override + public TaintVec modBeforeStore(int sizeout, int sizeinAddress, TaintVec inAddress, + int sizeinValue, TaintVec inValue) { + return inValue.tagIndirectWrite(inAddress); + } + + /** + * {@inheritDoc} + * + *

+ * Here we handle indirect taint for indirect reads + */ + @Override + public TaintVec modAfterLoad(int sizeout, int sizeinAddress, TaintVec inAddress, + int sizeinValue, TaintVec inValue) { + return inValue.tagIndirectRead(inAddress); + } + + /** + * {@inheritDoc} + * + *

+ * Constant values have no taint, so we just return a vector of empty taint sets + */ + @Override + public TaintVec fromConst(byte[] value) { + return TaintVec.empties(value.length); + } + + /** + * {@inheritDoc} + * + *

+ * Taint vectors have no values. We're expect the taint arithmetic to be used as an auxiliary to + * concrete bytes, so the paired arithmetic should always defer to its concrete element. Thus, + * an {@link AssertionError} might also be fitting here, but we'll stick to convention, since + * technically a user script could attempt to concretize taint. + */ + @Override + public byte[] toConcrete(TaintVec value, Purpose purpose) { + throw new ConcretionError("Cannot make taint concrete", purpose); + } + + /** + * {@inheritDoc} + * + *

+ * Taint vectors do have length, so return it here. + */ + @Override + public long sizeOf(TaintVec value) { + return value.length; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeThreadExecutor.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeThreadExecutor.java new file mode 100644 index 0000000000..b4716e4c72 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeThreadExecutor.java @@ -0,0 +1,71 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.DefaultPcodeThread; +import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; +import ghidra.pcode.exec.PcodeExecutor; +import ghidra.pcode.exec.PcodeFrame; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.taint.model.TaintVec; +import ghidra.util.Msg; + +/** + * An instrumented executor for the Taint Analyzer + * + *

+ * This part is responsible for executing all the actual p-code operations generated by each decoded + * instruction. Each thread in the emulator gets a distinct executor. So far, we haven't actually + * added any instrumentation, but the conditions of {@link PcodeOp#CBRANCH} operations will likely + * be examined by the user, so we set up the skeleton here. + */ +public class TaintPcodeThreadExecutor extends PcodeThreadExecutor> { + + /** + * Create the executor + * + * @param thread the thread being created + */ + public TaintPcodeThreadExecutor(DefaultPcodeThread> thread) { + super(thread); + } + + /** + * {@inheritDoc} + * + *

+ * This is invoked on every {@link PcodeOp#CBRANCH}, allowing us a decent place to instrument + * the emulator and add some diagnostics. Refer to + * {@link PcodeExecutor#executeConditionalBranch(PcodeOp, PcodeFrame)} to see the operations + * inputs. Alternatively, we could override + * {@link TaintPcodeArithmetic#isTrue(TaintVec, Purpose)}; however, we'd have access to less + * contextual information at that position. + */ + @Override + public void executeConditionalBranch(PcodeOp op, PcodeFrame frame) { + Pair condition = state.getVar(op.getInput(1)); + TaintVec taint = condition.getRight(); + if (!taint.union().isEmpty()) { + // getInstruction may return null if an inject executes a CBRANCH + Msg.trace(this, "Conditional branch '" + thread.getInstruction() + "' at " + + thread.getCounter() + " decided by tainted value: " + taint); + // TODO: Record these somewhere more useful. + } + super.executeConditionalBranch(op, frame); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeUseropLibrary.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeUseropLibrary.java new file mode 100644 index 0000000000..9316791f0c --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/TaintPcodeUseropLibrary.java @@ -0,0 +1,84 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint; + +import java.util.Set; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary; +import ghidra.taint.model.*; +import ghidra.trace.model.time.schedule.TraceSchedule; + +/** + * A userop library for tainting machine state variables + * + *

+ * Because Sleigh doesn't allow string literals, we're somewhat limited in what we allow a client to + * express. We'll allow the generation of taint variables and taint arrays on a 0-up basis, instead + * of allowing users to "name" the variable. These p-code ops become accessible to scripts, can be + * used in p-code injects, and can also be used in a {@link TraceSchedule}, i.e., in the "go to + * time" dialog. + */ +public class TaintPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary> { + private long nextVarId; + private long nextArrId; + + protected TaintSet nextVar() { + TaintMark mark = new TaintMark("var_" + nextVarId++, Set.of()); + return TaintSet.of(mark); + } + + protected String nextArrName() { + return "arr_" + nextArrId++; + } + + /** + * Taint the given machine variable with a single taint symbol + * + *

+ * This generates a single taint symbol (mark), places it in a singleton set, and then broadcast + * unions it with the taint vector already on the input variable. For example, assuming an + * initial state with no taints, the Sleigh code {@code RAX = taint_var(RAX)} will cause every + * byte of RAX to be tainted with "var_0". + * + * @param in the input value + * @return the same value, with the generated taint unioned in + */ + @PcodeUserop + public Pair taint_var(Pair in) { + return Pair.of(in.getLeft(), in.getRight().eachUnion(nextVar())); + } + + /** + * Taint the given machine variable with an array of taint symbols + * + *

+ * This generates a 0-up indexed sequence of taint symbols, unioning each with the corresponding + * taint set of the input taint vector. For example, assuming an initial state with no taints, + * the Sleigh code {@code RAX = taint_arr(RAX)} will cause RAX to be tainted as + * [arr_0_0][arr_0_1]...[arr_0_7]. + * + * @param in + * @return + */ + @PcodeUserop + public Pair taint_arr(Pair in) { + TaintVec taint = in.getRight(); + taint = taint.zipUnion(TaintVec.array(nextArrName(), 0, taint.length)); + return Pair.of(in.getLeft(), taint); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java new file mode 100644 index 0000000000..761015bb48 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulator.java @@ -0,0 +1,58 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.full; + +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.emu.taint.TaintPartsFactory; +import ghidra.pcode.emu.taint.plain.TaintPcodeEmulator; +import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory; +import ghidra.pcode.exec.debug.auxiliary.AuxDebuggerPcodeEmulator; +import ghidra.taint.model.TaintVec; +import ghidra.trace.model.Trace; + +/** + * A Debugger-integrated emulator with taint analysis + */ +public class TaintDebuggerPcodeEmulator extends AuxDebuggerPcodeEmulator { + /** + * Create an emulator + * + * @param tool the tool creating the emulator + * @param trace the source trace + * @param snap the source snap + * @param recorder if applicable, the recorder for the trace's live target + */ + public TaintDebuggerPcodeEmulator(PluginTool tool, Trace trace, long snap, + TraceRecorder recorder) { + super(tool, trace, snap, recorder); + } + + /** + * {@inheritDoc} + * + *

+ * Here, we just return the singleton parts factory. This appears simple because all the + * complexity is encapsulated in the factory. See {@link TaintPartsFactory} to see everything + * the implementation actually entails. Notice that this is the same parts factory used by + * {@link TaintPcodeEmulator}. The {@link AuxDebugggerPcodeEmulator} knows to use the more + * capable state parts. + */ + @Override + protected AuxDebuggerEmulatorPartsFactory getPartsFactory() { + return TaintPartsFactory.INSTANCE; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorFactory.java new file mode 100644 index 0000000000..11d6a2888f --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorFactory.java @@ -0,0 +1,43 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.full; + +import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeEmulatorFactory; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine; +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.trace.model.Trace; + +/** + * An emulator factory for making the {@link TaintDebuggerPcodeEmulator} discoverable to the UI + * + *

+ * This is the final class to create a full Debugger-integrated emulator. This class is what makes + * it appear in the menu of possible emulators the user may configure. + */ +public class TaintDebuggerPcodeEmulatorFactory implements DebuggerPcodeEmulatorFactory { + + @Override + public String getTitle() { + return "Taint Analyzer with Concrete Emulation"; + } + + @Override + public DebuggerPcodeMachine create(PluginTool tool, Trace trace, long snap, + TraceRecorder recorder) { + return new TaintDebuggerPcodeEmulator(tool, trace, snap, recorder); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorState.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorState.java new file mode 100644 index 0000000000..4fc2e7441e --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorState.java @@ -0,0 +1,53 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.full; + +import ghidra.app.plugin.core.debug.service.emulation.ReadsTargetMemoryPcodeExecutorStatePiece; +import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorState; + +/** + * A paired concrete-plus-taint Debugger-integrated state + * + *

+ * This contains the emulator's machine state along with the taint markings, just like + * {@link TaintTracePcodeExecutorState}, except that it can also read state from mapped static + * programs. In reality, this just composes concrete and taint state pieces, which actually do all + * the work. + */ +public class TaintDebuggerPcodeExecutorState extends TaintTracePcodeExecutorState { + + /** + * Create a state from the two given pieces + * + * @param concrete the concrete piece + * @param the taint piece + */ + public TaintDebuggerPcodeExecutorState(ReadsTargetMemoryPcodeExecutorStatePiece concrete, + TaintDebuggerPcodeExecutorStatePiece taint) { + super(concrete, taint); + } + + /** + * Create a state from the given concrete piece and an internally constructed taint piece + * + * @param concrete the concrete piece + */ + public TaintDebuggerPcodeExecutorState(ReadsTargetMemoryPcodeExecutorStatePiece concrete) { + super(concrete, new TaintDebuggerPcodeExecutorStatePiece( + concrete.getTool(), concrete.getTrace(), concrete.getSnap(), concrete.getThread(), + concrete.getFrame(), concrete.getRecorder())); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..8a85f4dace --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeExecutorStatePiece.java @@ -0,0 +1,87 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.full; + +import ghidra.app.services.TraceRecorder; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.emu.taint.trace.*; +import ghidra.program.model.address.AddressSpace; +import ghidra.trace.model.Trace; +import ghidra.trace.model.property.TracePropertyMapSpace; +import ghidra.trace.model.thread.TraceThread; + +/** + * The Debugger-integrated state piece for holding taint marks + * + *

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

+ * Here we create a map that uses {@link TaintDebuggerSpace}s. There is some repeated code with + * {@link TaintTracePcodeExecutorStatePiece#newSpaceMap()}. We could factor that, but I thought + * it a little pedantic. + */ + @Override + protected AbstractSpaceMap newSpaceMap() { + return new CacheingSpaceMap, TaintDebuggerSpace>() { + @Override + protected TracePropertyMapSpace getBacking(AddressSpace space) { + if (map == null) { + return null; + } + if (space.isRegisterSpace()) { + return map.getPropertyMapRegisterSpace(thread, frame, false); + } + return map.getPropertyMapSpace(space, false); + } + + @Override + protected TaintDebuggerSpace newSpace(AddressSpace space, + TracePropertyMapSpace backing) { + return new TaintDebuggerSpace(tool, trace, space, backing, snap); + } + }; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerSpace.java new file mode 100644 index 0000000000..1074e8619b --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/full/TaintDebuggerSpace.java @@ -0,0 +1,90 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.full; + +import com.google.common.collect.Range; + +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; +import ghidra.pcode.emu.taint.trace.TaintTraceSpace; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.util.StringPropertyMap; +import ghidra.program.util.ProgramLocation; +import ghidra.taint.model.TaintSet; +import ghidra.trace.model.DefaultTraceLocation; +import ghidra.trace.model.Trace; +import ghidra.trace.model.property.TracePropertyMapSpace; + +/** + * The storage space for taint sets in a trace's address space + * + *

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

+ * The taint trace space will call this when the cache misses and the trace has no taint set + * stored, allowing us to populate it with a taint set stored in a mapped static program. See + * notes in {@link TaintTraceSpace#whenNull(long)}. + */ + @Override + protected TaintSet whenTraceNull(long offset) { + DebuggerStaticMappingService mappingService = + tool.getService(DebuggerStaticMappingService.class); + ProgramLocation sloc = + mappingService.getOpenMappedLocation(new DefaultTraceLocation(trace, null, + Range.singleton(snap), space.getAddress(offset))); + if (sloc == null) { + return super.whenTraceNull(offset); + } + + // NB. This is stored in the program, not the user data, despite what the name implies + StringPropertyMap map = sloc.getProgram() + .getUsrPropertyManager() + .getStringPropertyMap(TaintTracePcodeExecutorStatePiece.NAME); + if (map == null) { + return super.whenTraceNull(offset); + } + String string = map.getString(sloc.getAddress()); + if (string == null) { + return super.whenTraceNull(offset); + } + return TaintSet.parse(string); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintEmuUnixFileSystem.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintEmuUnixFileSystem.java new file mode 100644 index 0000000000..a7e95c857f --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintEmuUnixFileSystem.java @@ -0,0 +1,116 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.lib; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.sys.*; +import ghidra.pcode.emu.unix.*; +import ghidra.taint.model.TaintVec; + +/** + * A file system containing tainted files + */ +public class TaintEmuUnixFileSystem extends AbstractEmuUnixFileSystem> { + + /** + * A taint-contents for a file whose contents are not tainted + */ + public static class UntaintedFileContents implements EmuFileContents { + @Override + public long read(long offset, TaintVec buf, long fileSize) { + buf.setEmpties(); + return buf.length; + } + + @Override + public long write(long offset, TaintVec buf, long curSize) { + return 0; // I don't care + } + + @Override + public void truncate() { + } + } + + /** + * A taint-contents for a read-only file whose contents are completely tainted + */ + public static class ReadOnlyTaintArrayFileContents implements EmuFileContents { + private final String filename; + + public ReadOnlyTaintArrayFileContents(String filename) { + this.filename = filename; + } + + @Override + public long read(long offset, TaintVec buf, long fileSize) { + buf.setArray(filename, offset); + return buf.length; + } + + @Override + public long write(long offset, TaintVec buf, long curSize) { + return 0; // I don't care + } + + @Override + public void truncate() { + } + } + + /** + * A file whose contents have a taint piece + */ + public static class TaintEmuUnixFile extends AbstractEmuUnixFile> { + protected BytesEmuFileContents concrete = new BytesEmuFileContents(); + + public TaintEmuUnixFile(String pathname, int mode) { + super(pathname, mode); + } + + @Override + protected EmuFileContents> createDefaultContents() { + return new PairedEmuFileContents<>(concrete, new UntaintedFileContents()); + } + + public void setTainted(boolean tainted) { + contents = + new PairedEmuFileContents<>(concrete, new ReadOnlyTaintArrayFileContents(pathname)); + } + } + + @Override + public AbstractEmuUnixFile> newFile(String pathname, int mode) + throws EmuIOException { + return new TaintEmuUnixFile(pathname, mode); + } + + /** + * Place a tainted file into the file system with the given contents + * + * @param pathname the pathname of the file + * @param contents the concrete contents of the file + */ + public void putTaintedFile(String pathname, byte[] contents) { + TaintEmuUnixFile file = new TaintEmuUnixFile(pathname, 0777); + file.setTainted(true); + EmuUnixFileStat stat = file.getStat(); + file.concrete.write(0, contents, stat.st_size); + stat.st_size = contents.length; + putFile(pathname, file); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintFileReadsLinuxAmd64SyscallLibrary.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintFileReadsLinuxAmd64SyscallLibrary.java new file mode 100644 index 0000000000..51859b4bf1 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintFileReadsLinuxAmd64SyscallLibrary.java @@ -0,0 +1,72 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.lib; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibrary; +import ghidra.pcode.emu.unix.EmuUnixFileSystem; +import ghidra.pcode.emu.unix.EmuUnixUser; +import ghidra.pcode.exec.PcodeExecutorState; +import ghidra.program.model.listing.Program; +import ghidra.taint.model.TaintVec; + +/** + * A library for performing Taint Analysis on a Linux-amd64 program that reads from tainted files + * + *

+ * This library is not currently accessible from the UI. It can be used with scripts by overriding a + * taint emulator's userop library factory method. + * + *

+ * TODO: A means of adding and configuring userop libraries in the UI. + * + *

+ * TODO: Example scripts. + */ +public class TaintFileReadsLinuxAmd64SyscallLibrary + extends EmuLinuxAmd64SyscallUseropLibrary> { + + public TaintFileReadsLinuxAmd64SyscallLibrary(PcodeMachine> machine, + EmuUnixFileSystem> fs, Program program, EmuUnixUser user) { + super(machine, fs, program, user); + } + + public TaintFileReadsLinuxAmd64SyscallLibrary(PcodeMachine> machine, + EmuUnixFileSystem> fs, Program program) { + super(machine, fs, program); + } + + @Override + public Pair unix_read(PcodeExecutorState> state, + Pair fd, Pair bufPtr, + Pair count) { + + Pair result = super.unix_read(state, fd, bufPtr, count); + + TaintVec taintResult = result.getRight(); + // TODO: Some representation of a "min" function. For now, just mix everything + taintResult = TaintVec.copies(taintResult.union().union(count.getRight().union()), + taintResult.length); + // TODO: This seems to make sense, but maybe a different tag? + // We're "reading" the size field from a table lookup, keyed by fd + taintResult = taintResult.tagIndirectRead(fd.getRight()); + + // TODO: Should I taint the output buffer with the file descriptor? Meh. + return Pair.of(result.getLeft(), taintResult); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulator.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulator.java new file mode 100644 index 0000000000..3313782a60 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulator.java @@ -0,0 +1,60 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.plain; + +import ghidra.pcode.emu.auxiliary.AuxEmulatorPartsFactory; +import ghidra.pcode.emu.auxiliary.AuxPcodeEmulator; +import ghidra.pcode.emu.taint.TaintPartsFactory; +import ghidra.program.model.lang.Language; +import ghidra.taint.model.TaintVec; + +/** + * A stand-alone emulator with taint analysis + */ +public class TaintPcodeEmulator extends AuxPcodeEmulator { + /** + * Create an emulator + * + * @param language the language (processor model) + */ + public TaintPcodeEmulator(Language language) { + super(language); + } + + /** + * {@inheritDoc} + * + *

+ * Here, we just return the singleton parts factory. This appears simple because all the + * complexity is encapsulated in the factory. See {@link TaintPartsFactory} to see everything + * the implementation actually entails. + */ + @Override + protected AuxEmulatorPartsFactory getPartsFactory() { + return TaintPartsFactory.INSTANCE; + } + + /** + * {@inheritDoc} + * + *

+ * We override this here so that clients don't have to cast the state. + */ + @Override + public TaintPcodeExecutorState getSharedState() { + return (TaintPcodeExecutorState) super.getSharedState(); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorState.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorState.java new file mode 100644 index 0000000000..383ef14a51 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorState.java @@ -0,0 +1,55 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.plain; + +import ghidra.pcode.exec.*; +import ghidra.program.model.lang.Language; +import ghidra.taint.model.TaintVec; + +/** + * A paired concrete-plus-taint state + * + *

+ * This contains the emulator's machine state along with the taint markings. Technically, one of + * these will hold the machine's memory, while another (for each thread) will hold the machine's + * registers. It's composed of two pieces. The concrete piece holds the actual concrete bytes, while + * the taint piece holds the taint markings. A request to get a variable's value from this state + * will return a pair where the left element comes from the concrete piece and the right element + * comes from the taint piece. + */ +public class TaintPcodeExecutorState extends PairedPcodeExecutorState { + + /** + * Create a state from the two given pieces + * + * @param concrete the concrete piece + * @param taint the taint piece + */ + protected TaintPcodeExecutorState(BytesPcodeExecutorStatePiece concrete, + TaintPcodeExecutorStatePiece taint) { + super(new PairedPcodeExecutorStatePiece<>(concrete, taint)); + } + + /** + * Create a state from the given concrete piece and an internally constructed taint piece + * + * @param language the language for creating the taint piece + * @param concrete the concrete piece + */ + public TaintPcodeExecutorState(Language language, BytesPcodeExecutorStatePiece concrete) { + this(concrete, new TaintPcodeExecutorStatePiece(language, concrete.getAddressArithmetic())); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..dbebde4d18 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintPcodeExecutorStatePiece.java @@ -0,0 +1,76 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.plain; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.emu.taint.AbstractTaintPcodeExecutorStatePiece; +import ghidra.pcode.emu.taint.TaintPcodeArithmetic; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.trace.TracePcodeExecutorState; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; + +/** + * The state piece for holding taint marks in the emulator's machine state + * + *

+ * Because this is already a working solution, most of the logic has already been abstracted into a + * super class {@link AbstractTaintPcodeExecutorStatePiece}. This class serves only to choose the + * type {@link TaintSpace}, which implements the real storage logic, and provide a map from address + * space to that type. Note the concept of a space map is introduced by + * {@link AbstractLongOffsetPcodeExecutorStatePiece}, which is provided by the p-code emulation + * framework. This is suitable for state pieces with concrete addresses. This likely fits your + * auxiliary piece, but may not. If you choose to use abstract addresses for your auxiliary piece, + * then your implementation of state will not follow the archetype presented here. You'll instead + * want to implement {@link TracePcodeExecutorState} directly, take the concrete piece provided, and + * wrap it as you see fit. You may still benefit by referring to the implementation of + * {@link PairedPcodeExecutorState}. When implementing your flavor of + * {@link PairedPcodeExecutorState#getVar(AddressSpace, Pair, int, boolean)}, still consider that + * you could benefit from the concrete element of the offset pair passed in. + */ +public class TaintPcodeExecutorStatePiece extends AbstractTaintPcodeExecutorStatePiece { + /** + * Create the taint piece + * + * @param language the language of the emulator + * @param addressArithmetic the address arithmetic, likely taken from the concrete piece + */ + public TaintPcodeExecutorStatePiece(Language language, + PcodeArithmetic addressArithmetic) { + super(language, addressArithmetic, TaintPcodeArithmetic.forLanguage(language)); + } + + /** + * {@inheritDoc} + * + *

+ * Here we use the simplest scheme for creating a map of {@link TaintSpace}s. This is + * essentially a lazy map from address space to some object for managing taint marks in that + * address space. The space could be a memory space, register space, unique space, etc. This + * piece will look up the space, creating it if necessary, and then delegate the get and set + * methods. + */ + @Override + protected AbstractSpaceMap newSpaceMap() { + return new SimpleSpaceMap() { + @Override + protected TaintSpace newSpace(AddressSpace space) { + return new TaintSpace(); + } + }; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java new file mode 100644 index 0000000000..f05bd15494 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/TaintSpace.java @@ -0,0 +1,111 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.plain; + +import java.util.HashMap; +import java.util.Map; + +import ghidra.pcode.emu.taint.trace.TaintTraceSpace; +import ghidra.taint.model.TaintSet; +import ghidra.taint.model.TaintVec; + +/** + * The storage space for taint sets in a single address space (possibly the register space) + * + *

+ * This is the actual implementation of the in-memory storage for taint marks. For a stand-alone + * emulator, this is the full state. For a trace- or Debugger-integrated emulator, this is a cache + * of taints loaded from a trace backing this emulator. Most likely, that trace is the user's + * current trace. + */ +public class TaintSpace { + // TODO: There must be a better way. Similar to SemisparseByteArray? + protected final Map taints = new HashMap<>(); + + /** + * Mark the variable at offset with the given taint sets + * + *

+ * This marks possibly several offsets, starting at the given offset. The first taint set in the + * vector is used to mark the given offset, then each subsequent set marks each subsequent + * offset. This is analogous to the manner in which bytes would be "written" from a source array + * into concrete state, starting at a given offset. + * + * @param offset the starting offset + * @param val the vector of taint sets + */ + public void set(long offset, TaintVec val) { + for (int i = 0; i < val.length; i++) { + TaintSet s = val.get(i); + /* + * TODO: It'd be nice not to store empties, but then dumping to trace doesn't clear + * emptied taints. + */ + taints.put(offset + i, s); + } + } + + /** + * Retrieve the taint sets for the variable at the given offset + * + *

+ * This retrieve as many taint sets as there are elements in the given buffer vector. This first + * element becomes the taint set at the given offset, then each subsequent element becomes the + * taint set at each subsequent offset until the vector is filled. This is analogous to the + * manner in which bytes would be "read" from concrete state, starting at a given ofset, into a + * destination array. + * + * @param offset the offset + * @param buf the vector to receive taint sets + */ + public void getInto(long offset, TaintVec buf) { + for (int i = 0; i < buf.length; i++) { + TaintSet s = taints.get(offset + i); + buf.set(i, s == null ? whenNull(offset + i) : s); + } + } + + /** + * Retrieve the taint sets for the variable at the given offset + * + *

+ * This works the same as {@link #getInto(long, TaintVec)}, but creates a new vector of the + * given size, reads the taint sets, and returns the vector. + * + * @param offset the offset + * @param size the size of the variable + * @return the taint vector for that variable + */ + public TaintVec get(long offset, int size) { + TaintVec vec = new TaintVec(size); + getInto(offset, vec); + return vec; + } + + /** + * Extension point: Behavior when there is no in-memory taint set at the given offset + * + *

+ * This will be overridden by {@link TaintTraceSpace} to implement the lazy loading and + * deserialization from a trace. + * + * @param offset the offset + * @return the taint set to use + */ + protected TaintSet whenNull(long offset) { + return TaintSet.EMPTY; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/package-info.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/package-info.java new file mode 100644 index 0000000000..d565238183 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/plain/package-info.java @@ -0,0 +1,33 @@ +/* ### + * 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. + */ +/** + * The stand-alone Taint Emulator + * + *

+ * This and the {@link ghidra.pcode.emu.taint} packages contain all the parts necessary to construct + * a stand-alone emulator. Because this is a working solution, the state components already have + * provisions in place for extension to support the fully-integrated solution. Generally, it's a bit + * easier to just get the basic state components implemented, put tests in place, and then re-factor + * them to permit extension as you address each more integrated emulator. + * + *

+ * For this package, I recommend a top-down approach, since the top component provides a flat + * catalog of the lower components. That top piece is actually in a separate package. See + * {@link ghidra.pcode.emu.taint.TaintPartsFactory}. That factory is then used in + * {@link TaintPcodeEmulator} to realize the stand-alone emulator. When you get to the state pieces, + * you may want to pause and read {@link TaintSpace} first. + */ +package ghidra.pcode.emu.taint.plain; diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/AbstractTaintTracePcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/AbstractTaintTracePcodeExecutorStatePiece.java new file mode 100644 index 0000000000..def021f3c3 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/AbstractTaintTracePcodeExecutorStatePiece.java @@ -0,0 +1,102 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.trace; + +import ghidra.pcode.emu.taint.AbstractTaintPcodeExecutorStatePiece; +import ghidra.pcode.emu.taint.TaintPcodeArithmetic; +import ghidra.pcode.exec.BytesPcodeArithmetic; +import ghidra.pcode.exec.trace.TracePcodeExecutorStatePiece; +import ghidra.program.model.lang.Language; +import ghidra.taint.model.TaintVec; +import ghidra.trace.model.Trace; +import ghidra.trace.model.property.TracePropertyMap; +import ghidra.trace.model.thread.TraceThread; + +/** + * An abstract trace-integrated state piece + * + *

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

+ * This does the inverse of the lazy loading. Serialize the state and store it back into the + * trace. Technically, it could be a different trace, but it must have identically-named + * threads. + */ + @Override + public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { + TracePropertyMap map = + trace.getAddressPropertyManager().getOrCreatePropertyMap(NAME, String.class); + for (TaintTraceSpace space : spaceMap.values()) { + space.writeDown(map, snap, thread, frame); + } + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java new file mode 100644 index 0000000000..34b2d85b41 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulator.java @@ -0,0 +1,53 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.trace; + +import ghidra.pcode.emu.taint.TaintPartsFactory; +import ghidra.pcode.emu.taint.plain.TaintPcodeEmulator; +import ghidra.pcode.exec.trace.auxiliary.AuxTraceEmulatorPartsFactory; +import ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator; +import ghidra.taint.model.TaintVec; +import ghidra.trace.model.Trace; + +/** + * A trace-integrated emulator with taint analysis + */ +public class TaintTracePcodeEmulator extends AuxTracePcodeEmulator { + /** + * Create an emulator + * + * @param trace the trace the source trace + * @param snap the source snap + */ + public TaintTracePcodeEmulator(Trace trace, long snap) { + super(trace, snap); + } + + /** + * {@inheritDoc} + * + *

+ * Here, we just return the singleton parts factory. This appears simple because all the + * complexity is encapsulated in the factory. See {@link TaintPartsFactory} to see everything + * the implementation actually entails. Notice that this is the same parts factory used by + * {@link TaintPcodeEmulator}. The {@link AuxTracePcodeEmulator} knows to use the more capable + * state parts. + */ + @Override + protected AuxTraceEmulatorPartsFactory getPartsFactory() { + return TaintPartsFactory.INSTANCE; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java new file mode 100644 index 0000000000..dcaf58db51 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorState.java @@ -0,0 +1,56 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.trace; + +import ghidra.pcode.emu.taint.plain.TaintPcodeExecutorState; +import ghidra.pcode.exec.trace.*; +import ghidra.taint.model.TaintVec; + +/** + * A paired concrete-plus-taint trace-integrated state + * + *

+ * This contains the emulator's machine state along with the taint markings, just like + * {@link TaintPcodeExecutorState}, except that it can read and write state from a trace. In + * reality, this just composes concrete and taint state pieces, which actually do all the work. + */ +public class TaintTracePcodeExecutorState extends PairedTracePcodeExecutorState { + + /** + * Create a state from the two given pieces + * + * @param concrete the concrete piece + * @param taint the taint piece + */ + public TaintTracePcodeExecutorState(BytesTracePcodeExecutorStatePiece concrete, + AbstractTaintTracePcodeExecutorStatePiece taint) { + super(new PairedTracePcodeExecutorStatePiece<>(concrete, taint)); + } + + /** + * Create a state from the given concrete piece and an internally constructed taint piece + * + *

+ * We take all the parameters needed by the taint piece from the concrete piece. + * + * @param concrete the concrete piece + */ + public TaintTracePcodeExecutorState(BytesTracePcodeExecutorStatePiece concrete) { + this(concrete, + new TaintTracePcodeExecutorStatePiece(concrete.getTrace(), concrete.getSnap(), + concrete.getThread(), concrete.getFrame())); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java new file mode 100644 index 0000000000..6c409be0b1 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeExecutorStatePiece.java @@ -0,0 +1,74 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.trace; + +import ghidra.program.model.address.AddressSpace; +import ghidra.trace.model.Trace; +import ghidra.trace.model.property.TracePropertyMapSpace; +import ghidra.trace.model.thread.TraceThread; + +/** + * The trace-integrated state piece for holding taint marks + */ +public class TaintTracePcodeExecutorStatePiece + extends AbstractTaintTracePcodeExecutorStatePiece { + + /** + * Create the taint piece + * + * @param trace the trace from which to load taint marks + * @param snap the snap from which to load taint marks + * @param thread if a register space, the thread from which to load taint marks + * @param frame if a register space, the frame + */ + public TaintTracePcodeExecutorStatePiece(Trace trace, long snap, TraceThread thread, + int frame) { + super(trace, snap, thread, frame); + } + + /** + * {@inheritDoc} + * + *

+ * Here we create a map that uses {@link TaintTraceSpace}s. The framework provides the concept + * of a space map where storage is actually a cache backed by some other object. The backing + * object we'll use here is {@link TracePropertyMapSpace}, which is provided by the + * TraceModeling module. We'll need a little bit of extra logic for fetching a register space + * vs. a plain memory space, but after that, we need not care which address space the backing + * object is for. + */ + @Override + protected AbstractSpaceMap newSpaceMap() { + return new CacheingSpaceMap, TaintTraceSpace>() { + @Override + protected TracePropertyMapSpace getBacking(AddressSpace space) { + if (map == null) { + return null; + } + if (space.isRegisterSpace()) { + return map.getPropertyMapRegisterSpace(thread, frame, false); + } + return map.getPropertyMapSpace(space, false); + } + + @Override + protected TaintTraceSpace newSpace(AddressSpace space, + TracePropertyMapSpace backing) { + return new TaintTraceSpace(space, backing, snap); + } + }; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java new file mode 100644 index 0000000000..6ea84ffa7c --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/TaintTraceSpace.java @@ -0,0 +1,134 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.trace; + +import java.util.Map.Entry; + +import com.google.common.collect.Range; + +import ghidra.pcode.emu.taint.full.TaintDebuggerSpace; +import ghidra.pcode.emu.taint.plain.TaintSpace; +import ghidra.pcode.exec.trace.TracePcodeExecutorState; +import ghidra.program.model.address.*; +import ghidra.taint.model.TaintSet; +import ghidra.trace.database.DBTraceUtils; +import ghidra.trace.model.Trace; +import ghidra.trace.model.property.TracePropertyMap; +import ghidra.trace.model.property.TracePropertyMapSpace; +import ghidra.trace.model.thread.TraceThread; + +/** + * The storage space for taint sets in a trace's address space + * + *

+ * This adds to {@link TaintSpace} the ability to load taint sets from a trace and the ability to + * save them back into a trace. + */ +public class TaintTraceSpace extends TaintSpace { + protected final AddressSpace space; + protected final TracePropertyMapSpace backing; + protected final long snap; + + /** + * Create the space + * + * @param space the address space + * @param backing if present, the backing object + * @param snap the source snap + */ + public TaintTraceSpace(AddressSpace space, TracePropertyMapSpace backing, long snap) { + this.space = space; + this.backing = backing; + this.snap = snap; + } + + /** + * {@inheritDoc} + * + *

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

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

+ * Here we simply iterate over every entry in this space, serialize the taint, and store it into + * the property map at the entry's offset. Because a backing object may not have existed when + * creating this space, we must re-fetch the backing object, creating it if it does not exist. + * We can safely create such spaces, since the client is required to have an open transaction on + * the destination trace while invoking this method (via + * {@link TracePcodeExecutorState#writeDown(Trace, long, TraceThread, int)}). + * + * @param map the backing object, which must now exist + * @param snap the destination snap + * @param thread if a register space, the destination thread + * @param frame if a register space, the destination frame + */ + public void writeDown(TracePropertyMap map, long snap, TraceThread thread, int frame) { + if (space.isUniqueSpace()) { + return; + } + TracePropertyMapSpace backing; + if (space.isRegisterSpace()) { + backing = map.getPropertyMapRegisterSpace(thread, frame, true); + } + else { + backing = map.getPropertyMapSpace(space, true); + } + for (Entry entry : taints.entrySet()) { + TaintSet taint = entry.getValue(); + Range span = DBTraceUtils.atLeastMaybeScratch(snap); + Address address = space.getAddress(entry.getKey()); + if (taint.isEmpty()) { + backing.clear(span, new AddressRangeImpl(address, address)); + } + else { + backing.set(span, address, taint.toString()); + } + } + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/package-info.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/package-info.java new file mode 100644 index 0000000000..f588b3abcd --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/trace/package-info.java @@ -0,0 +1,35 @@ +/* ### + * 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. + */ +/** + * The trace-integrated Taint Emulator + * + *

+ * This package builds on {@link ghidra.pcode.emu.plain} to construct a trace-integrated emulator. + * See that package for remarks about this "working solution." Those state components were factored + * to accommodate the state components introduced by this package. + * + *

+ * For this package, I recommend a bottom-up approach, since you should already be familiar with the + * parts factory and the structure of the stand-alone state part. + * {@link ghidra.pcode.emu.taint.trace.TaintTraceSpace} adds the ability to read and write taint + * sets from a trace. {@link ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece} works + * that into a state piece derived from + * {@link ghidra.pcode.emu.taint.plain.TaintPcodeExecutorStatePiece}. Then, + * {@link ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorState} composes that with a given + * concrete state piece. The factory creates that state for use by the + * {@link ghidra.pcode.emu.taint.trace.TaintTracePcodeEmulator}. + */ +package ghidra.pcode.emu.taint.trace; diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java new file mode 100644 index 0000000000..806334cf7b --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintDebuggerRegisterColumnFactory.java @@ -0,0 +1,65 @@ +/* ### + * 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.taint.gui.field; + +import docking.widgets.table.AbstractDynamicTableColumn; +import docking.widgets.table.DynamicTableColumn; +import ghidra.app.plugin.core.debug.DebuggerCoordinates; +import ghidra.app.plugin.core.debug.gui.register.DebuggerRegisterColumnFactory; +import ghidra.app.plugin.core.debug.gui.register.RegisterRow; +import ghidra.docking.settings.Settings; +import ghidra.framework.plugintool.ServiceProvider; +import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; +import ghidra.trace.model.Trace; + +/** + * A factory for the "Taint" column in the "Registers" panel + * + *

+ * For the most part, this is just a matter of accessing the property map and rendering the value on + * screen. As a cheap shortcut, we'll just instantiate a taint state piece at the panel's + * coordinates and use it to retrieve the actual taint marks, then render that for display. + */ +public class TaintDebuggerRegisterColumnFactory implements DebuggerRegisterColumnFactory { + protected static final String PROP_NAME = TaintTracePcodeExecutorStatePiece.NAME; + public static final String COL_NAME = "Taint"; + + @Override + public DynamicTableColumn create() { + return new AbstractDynamicTableColumn() { + @Override + public String getColumnName() { + return COL_NAME; + } + + @Override + public String getValue(RegisterRow rowObject, Settings settings, Void data, + ServiceProvider serviceProvider) throws IllegalArgumentException { + DebuggerCoordinates current = rowObject.getCurrent(); + Trace trace = current.getTrace(); + if (trace == null) { + return ""; + } + + TaintTracePcodeExecutorStatePiece piece = + new TaintTracePcodeExecutorStatePiece(current.getTrace(), current.getViewSnap(), + current.getThread(), current.getFrame()); + + return piece.getVar(rowObject.getRegister()).toDisplay(); + } + }; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java new file mode 100644 index 0000000000..275832cae7 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldFactory.java @@ -0,0 +1,139 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.taint.gui.field; + +import java.math.BigInteger; + +import docking.widgets.fieldpanel.field.AttributedString; +import docking.widgets.fieldpanel.field.TextFieldElement; +import docking.widgets.fieldpanel.support.FieldLocation; +import ghidra.app.util.HighlightProvider; +import ghidra.app.util.viewer.field.*; +import ghidra.app.util.viewer.format.FieldFormatModel; +import ghidra.app.util.viewer.proxy.ProxyObj; +import ghidra.framework.options.Options; +import ghidra.framework.options.ToolOptions; +import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; +import ghidra.program.model.listing.CodeUnit; +import ghidra.program.model.util.StringPropertyMap; +import ghidra.program.util.ProgramLocation; +import ghidra.taint.model.TaintSet; +import ghidra.taint.model.TaintVec; + +/** + * A field factory for "Taint" in the Listing panels + * + *

+ * This implements an interface that is part of the core framework, even lower than the Debugger + * framework. I used the "sample" module's {@code EntropyFieldFactory} for reference. + */ +public class TaintFieldFactory extends FieldFactory { + public static final String PROPERTY_NAME = TaintTracePcodeExecutorStatePiece.NAME; + public static final String FIELD_NAME = "Taint"; + + public TaintFieldFactory() { + super(FIELD_NAME); + } + + protected TaintFieldFactory(FieldFormatModel formatModel, HighlightProvider highlightProvider, + Options displayOptions, Options fieldOptions) { + super(FIELD_NAME, formatModel, highlightProvider, displayOptions, fieldOptions); + } + + @Override + public FieldFactory newInstance(FieldFormatModel formatModel, + HighlightProvider highlightProvider, ToolOptions displayOptions, + ToolOptions fieldOptions) { + return new TaintFieldFactory(formatModel, highlightProvider, displayOptions, fieldOptions); + } + + /** + * {@inheritDoc} + * + *

+ * This is where the most of the rendering logic is. Here, we access the property map and + * deserialize into a {@link TaintVec} manually (as compared to using a state piece as we did in + * {@link TaintDebuggerRegisterColumnFactory}). Once we have the complete vector, we render it + * for display. + */ + @Override + public ListingField getField(ProxyObj proxy, int varWidth) { + Object obj = proxy.getObject(); + if (!enabled || !(obj instanceof CodeUnit)) { + return null; + } + CodeUnit cu = (CodeUnit) obj; + + StringPropertyMap taintMap = + cu.getProgram().getUsrPropertyManager().getStringPropertyMap(PROPERTY_NAME); + if (taintMap == null) { + return null; + } + + TaintVec vec = new TaintVec(cu.getLength()); + for (int i = 0; i < vec.length; i++) { + String taintString = taintMap.getString(cu.getAddress().add(i)); + vec.set(i, taintString == null ? TaintSet.EMPTY : TaintSet.parse(taintString)); + } + + return ListingTextField.createSingleLineTextField(this, proxy, + new TextFieldElement(new AttributedString(vec.toDisplay(), color, getMetrics()), 0, 0), + startX + varWidth, width, hlProvider); + } + + /** + * {@inheritDoc} + * + *

+ * Because the core framework provides an API for accessing and manipulating the user's cursor, + * we have to provide a means to distinguish locations in our field from others. This method + * provides on direction of the conversion between field and program locations. + */ + @Override + public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum, + ProgramLocation loc) { + if (!(loc instanceof TaintFieldLocation)) { + return null; + } + TaintFieldLocation tfLoc = (TaintFieldLocation) loc; + return new FieldLocation(index, fieldNum, 0, tfLoc.getCharOffset()); + } + + /** + * {@inheritDoc} + * + *

+ * Because the core framework provides an API for accessing and manipulating the user's cursor, + * we have to provide a means to distinguish locations in our field from others. This method + * provides on direction of the conversion between field and program locations. + */ + @Override + public ProgramLocation getProgramLocation(int row, int col, ListingField bf) { + ProxyObj proxy = bf.getProxy(); + Object obj = proxy.getObject(); + if (!(obj instanceof CodeUnit)) { + return null; + } + CodeUnit cu = (CodeUnit) obj; + return new TaintFieldLocation(proxy.getListingLayoutModel().getProgram(), cu.getAddress(), + col); + } + + @Override + public boolean acceptsType(int category, Class proxyObjectClass) { + return (category == FieldFormatModel.INSTRUCTION_OR_DATA); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldLocation.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldLocation.java new file mode 100644 index 0000000000..35bca266f4 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/TaintFieldLocation.java @@ -0,0 +1,37 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.taint.gui.field; + +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.util.CodeUnitLocation; +import ghidra.program.util.ProgramLocation; + +/** + * This is a {@link ProgramLocation} for when the user's cursor is in our "Taint" field + * + *

+ * I used the "sample" module's {@code EntropyFieldLocation} for reference. + */ +public class TaintFieldLocation extends CodeUnitLocation { + public TaintFieldLocation(Program program, Address address, int charOffset) { + super(program, address, 0, 0, charOffset); + } + + // Need default for XML restore + public TaintFieldLocation() { + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/timeline/TimelineListener.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/package-info.java similarity index 57% rename from Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/timeline/TimelineListener.java rename to Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/package-info.java index 03ecd7a9bc..e9582ca3e2 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/docking/widgets/timeline/TimelineListener.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/gui/field/package-info.java @@ -13,14 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package docking.widgets.timeline; - -import com.google.common.collect.Range; - -public interface TimelineListener { - default void viewRangeChanged(Range viewRange) { - } - - default void itemActivated(int index) { - } -} +/** + * UI components for the Taint Analyzer + * + *

+ * This contains a few odds and ends for making the taint analyzer's machine state visible to the + * user. It provides a custom column for the Registers panel, and a custom field for the Listing + * panels. Both just render the taint markings using + * {@link ghidra.taint.model.TaintVec#toDisplay()}. There's no particular recommended reading order. + */ +package ghidra.taint.gui.field; diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintMark.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintMark.java new file mode 100644 index 0000000000..7fd877b23e --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintMark.java @@ -0,0 +1,151 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.taint.model; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * A taint mark + * + *

+ * This is essentially a symbol or variable, but we also include an immutable set of tags. A mark is + * the bottom-most component in a {@link TaintVec}. + */ +public class TaintMark { + private static final String SEP = ":"; + private static final String TAG_SEP = ","; + + /** + * Parse a mark from the given string + * + *

+ * A mark has the form "{@code name:tag1,tag2,...,tagN}". The tags are optional, so it may also + * take the form "{@code name}". + * + * @param string the string to parse + * @return the resulting mark + */ + public static TaintMark parse(String string) { + String[] parts = string.split(SEP); + if (parts.length == 1) { + return new TaintMark(parts[0], Set.of()); + } + return new TaintMark(parts[0], Set.of(parts[1].split(TAG_SEP))); + } + + private final String name; + private final Set tags; + private final int hashCode; + + /** + * Construct a new taint mark + * + *

+ * TODO: Validation that the name and tags do not contain any separators, so that + * {@link #parse(String)} and {@link #toString()} are proper inverses. + * + * @param name the name + * @param tags the tags + */ + public TaintMark(String name, Set tags) { + this.name = name; + this.tags = Set.copyOf(tags); // TODO: Optimize + this.hashCode = Objects.hash(name, tags); + } + + /** + * Render the mark as a string + * + * @see #parse(String) + */ + @Override + public String toString() { + if (tags.isEmpty()) { + return getName(); + } + return getName() + SEP + tags.stream().collect(Collectors.joining(TAG_SEP)); + } + + /** + * Check if two marks are equal + * + *

+ * Note that we distinguish between a mark without tags and another mark with the same name but + * having tags. Because we use tags to indicate, e.g., indirection, we want to allow a variable + * to be marked as tainted both directly and indirectly. Furthermore, if indirect taints are + * filtered, we would want to ensure such a variable is not removed, since it's also tainted + * directly. + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TaintMark)) { + return false; + } + TaintMark that = (TaintMark) obj; + if (!Objects.equals(this.name, that.name)) { + return false; + } + if (!Objects.equals(this.tags, that.tags)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return hashCode; + } + + /** + * Get the name of the mark + * + * @return the name + */ + public String getName() { + return name; + } + + /** + * Get the mark's tags + * + * @return the tags + */ + public Set getTags() { + return tags; + } + + /** + * Create a new mark with the given tag added + * + *

+ * Tags are a set, so this may return the same mark + * + * @param tag + * @return + */ + public TaintMark tagged(String tag) { + if (this.tags.contains(tag)) { + return this; + } + HashSet tags = new HashSet<>(this.tags); + tags.add(tag); + return new TaintMark(name, tags); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintSet.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintSet.java new file mode 100644 index 0000000000..b585441c94 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintSet.java @@ -0,0 +1,152 @@ +/* ### + * 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.taint.model; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * An immutable set of multiple taint marks + * + *

+ * A variable in an emulator could be tainted by multiple marks, so we must use vectors of sets, not + * vectors of marks. Please see {@link TaintMark#equals(Object)} regarding the equality of tagged + * marks. + */ +public class TaintSet { + private static final String SEP = ";"; + /** The empty set, the default for all state variables */ + public static final TaintSet EMPTY = new TaintSet(Set.of()); + + /** + * Parse a set of taint marks + * + *

+ * The form is a semicolon-separated list of taint marks, e.g., + * "{@code myVar:tag1,tag2;anotherVar;yetAnother}". + * + * @param string the string to parse + * @return the resulting set + */ + public static TaintSet parse(String string) { + return new TaintSet(Stream.of(string.split(SEP)) + .map(TaintMark::parse) + .collect(Collectors.toUnmodifiableSet())); + } + + /** + * Create a taint set of the given marks + * + * @param marks the marks + * @return the set + */ + public static TaintSet of(TaintMark... marks) { + return new TaintSet(Set.of(marks)); + } + + /** + * Create a taint set of the given marks + * + * @param marks the marks + * @return the set + */ + static TaintSet of(Set marks) { + return new TaintSet(Set.copyOf(marks)); + } + + final Set marks; + private final int hashCode; + + TaintSet(Set marks) { + this.marks = marks; // Must be immutable + this.hashCode = Objects.hashCode(marks); + } + + /** + * Convert the set to a string + * + * @see #parse(String) + */ + @Override + public String toString() { + return marks.stream().map(TaintMark::toString).collect(Collectors.joining(SEP)); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TaintSet)) { + return false; + } + TaintSet that = (TaintSet) obj; + return Objects.equals(this.marks, that.marks); + } + + @Override + public int hashCode() { + return hashCode; + } + + /** + * Get the marks in this set + * + * @return the marks + */ + public Set getMarks() { + return marks; + } + + /** + * Check if this set is empty + * + * @return the marks + */ + public boolean isEmpty() { + return marks.isEmpty(); + } + + /** + * Construct the taint set from the union of marks of this and the given taint set + * + * @param that another taint set + * @return the union + */ + public TaintSet union(TaintSet that) { + Set marks = new HashSet<>(); + // TODO: What's the most efficient data structure here? + marks.addAll(this.marks); + marks.addAll(that.marks); + return of(marks); + } + + /** + * Construct the taint set formed by tagging each mark in this set + * + * @param string the tag to add to each mark + * @return the new set + */ + public TaintSet tagged(String string) { + int size = this.marks.size(); + Set marks = new HashSet<>(size); + for (TaintMark m : this.marks) { + marks.add(m.tagged(string)); + } + return of(marks); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintVec.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintVec.java new file mode 100644 index 0000000000..b61d3dc8f2 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/TaintVec.java @@ -0,0 +1,503 @@ +/* ### + * 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.taint.model; + +import java.util.*; +import java.util.function.BinaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * A mutable, but fixed-size, buffer of taint sets + * + *

+ * This is the auxiliary type used by the Taint Analyzer's emulator. + * + *

+ * Regarding serialization, we do not serialize the vector for storage, but only for display. For + * storage, we instead serialize and store each taint set on an address-by-address basis. Thus, we + * do not (yet) have a {@code parse(String)} method. + */ +public class TaintVec { + /** + * Create a vector of empty taint sets + * + * @param size the length of the vector + * @return the new vector + */ + public static TaintVec empties(int size) { + return copies(TaintSet.EMPTY, size); + } + + /** + * Broadcast the given set into a new vector or the given length + * + * @param taint the taint set + * @param size the length of the vector + * @return the new vector + */ + public static TaintVec copies(TaintSet taint, int size) { + return new TaintVec(size).setCopies(taint); + } + + /** + * Create a taint vector representing a new tainted byte array, where each element is given a + * distinct name + * + *

+ * For example, the parameters {@code ("arr", 0, 4)} will produce the vector + * "{@code [arr_0][arr_1][arr_2][arr_3]}". Each element is a singleton set containing the mark + * for a byte in the tainted array. + * + * @param name the base for naming each element + * @param start the starting index for naming each element + * @param size the number of bytes, i.e., the length of the vector + * @return the new vector + */ + public static TaintVec array(String name, long start, int size) { + return new TaintVec(size).setArray(name, start); + } + + private TaintSet[] sets; + private List setsView; + public final int length; + + /** + * Create a new uninitialized taint vector of the given length + * + * @param length the length + */ + public TaintVec(int length) { + this.sets = new TaintSet[length]; + this.setsView = Collections.unmodifiableList(Arrays.asList(sets)); + this.length = sets.length; + } + + @Override + public String toString() { + return String.format("", toDisplay()); + } + + /** + * Convert the vector to a string suitable for display in the UI + * + * @return the string + */ + public String toDisplay() { + return Stream.of(sets).map(e -> "[" + e + "]").collect(Collectors.joining()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof TaintVec)) { + return false; + } + TaintVec that = (TaintVec) obj; + return Objects.equals(this.setsView, that.setsView); + } + + @Override + public int hashCode() { + return Objects.hash(setsView); + } + + /** + * Get the vector as a list + * + * @return the list + */ + public List getSets() { + return setsView; + } + + /** + * Get an element from the vector + * + * @param i the index + * @return the taint set + */ + public TaintSet get(int i) { + return sets[i]; + } + + /** + * Set an element in the vector + * + * @param i the index + * @param s the taint set + */ + public void set(int i, TaintSet s) { + sets[i] = s; + } + + /** + * Set several elements in the vector + * + *

+ * This is essentially just an array copy. The entire source {@code vec} is copied into this + * vector such that the first element of the source is placed at the start index of the + * destination. + * + * @param start the starting index + * @param vec the vector of sets + * @return this vector + */ + public TaintVec set(int start, TaintVec vec) { + for (int i = 0; i < vec.length; i++) { + sets[i + start] = vec.sets[i]; + } + return this; + } + + /** + * Perform an operation on each same-indexed element from this and another vector, forming a + * third result vector + * + *

+ * In essence return, a vector where {@code result[n] = this[n] op that[n]}. The two input + * vectors must match in length. + * + * @param that the other vector + * @param op the operation to apply + * @return the result + */ + private TaintVec zip(TaintVec that, BinaryOperator op) { + final int length = this.sets.length; + if (length != that.sets.length) { + throw new IllegalArgumentException("TaintVecs must match in length"); + } + TaintVec vec = new TaintVec(length); + for (int i = 0; i < length; i++) { + vec.sets[i] = op.apply(this.sets[i], that.sets[i]); + } + return vec; + } + + /** + * Perform an operation on a given taint set and each element from this array, forming a result + * vector + * + *

+ * In essence, return a vector where {@code result[n] = this[n] op set}. + * + * @param set the taint set + * @param op the operation to apply + * @return the result + */ + private TaintVec each(TaintSet set, BinaryOperator op) { + final int length = this.sets.length; + TaintVec vec = new TaintVec(length); + for (int i = 0; i < length; i++) { + vec.sets[i] = op.apply(this.sets[i], set); + } + return vec; + } + + /** + * Union each element with its corresponding element from another vector, forming a new result + * vector + * + * @param that the other vector + * @return the result + */ + public TaintVec zipUnion(TaintVec that) { + return zip(that, TaintSet::union); + } + + /** + * Union each element with the given set, forming a new result vector + * + * @param set the taint set + * @return the result + */ + public TaintVec eachUnion(TaintSet set) { + return each(set, TaintSet::union); + } + + /** + * Reduce this vector to a single taint set by union + * + * @return the resulting taint set + */ + public TaintSet union() { + Set result = new HashSet<>(); + for (int i = 0; i < sets.length; i++) { + result.addAll(sets[i].marks); + } + return TaintSet.of(result); + } + + /** + * Combine this and another taint vector to represent a tainted indirect read + * + *

+ * Because the all bytes of the address offset "affect" the value read, we first union all the + * taint sets of the that offset. We then tag each mark in that union with "{@code indR}". + * Finally we union that result with each element of this vector (this vector representing the + * bytes read from memory). + * + * @param offset the vector representing the bytes that encode the offset + * @return the vector representing the tainted bytes read from memory + */ + public TaintVec tagIndirectRead(TaintVec offset) { + TaintSet taintOffset = offset.union().tagged("indR"); + return eachUnion(taintOffset); + } + + /** + * Combine this and another taint vector to represent a tainted indirect write + * + *

+ * This works the same as {@link #tagIndirectRead(TaintVec)}, except with the tag "{@code indW}" + * and it occurs before the actual write. + * + * @param offset the vector representing the bytes that encode the offset + * @return the vector representing the tainted bytes to be written to memory + */ + public TaintVec tagIndirectWrite(TaintVec offset) { + TaintSet taintOffset = offset.union().tagged("indW"); + return eachUnion(taintOffset); + } + + /** + * Broadcast the given set over this vector, modifying it in place + * + * @param taint the taint set + * @return this vector + */ + public TaintVec setCopies(TaintSet taint) { + for (int i = 0; i < length; i++) { + sets[i] = taint; + } + return this; + } + + /** + * Broadcast the empty taint set over this vector, modifying it in place + * + * @return this vector + */ + public TaintVec setEmpties() { + return setCopies(TaintSet.EMPTY); + } + + /** + * Fill this vector as in {@link #array(String, long, int)}, modifying it in place + * + * @param name the base for naming each element + * @param start the starting index for naming each element + * @return this vector + */ + public TaintVec setArray(String name, long start) { + for (int i = 0; i < length; i++) { + sets[i] = TaintSet.of(new TaintMark(name + "_" + (start + i), Set.of())); + } + return this; + } + + /** + * Modify the vector so each element becomes the union of itself and all elements of lesser + * significance + * + *

+ * This should be used after {@link #zipUnion(TaintVec)} to model operations with carries. + * + * @param isBigEndian true if smaller indices have greater significance + * @return this vector + */ + public TaintVec setCascade(boolean isBigEndian) { + if (isBigEndian) { + for (int i = length - 2; i >= 0; i--) { + sets[i] = sets[i].union(sets[i + 1]); + } + } + for (int i = 0; i < length - 1; i++) { + sets[i + 1] = sets[i + 1].union(sets[i]); + } + return this; + } + + /** + * Modify the vector so each element becomes the union of itself and its neighbor + * + *

+ * This should be used to model shift operations. Both the shift direction and the endianness + * must be considered. + * + * @param right true to cause each greater index to be unioned in place with less-indexed + * neighbor + * @return this vector + */ + public TaintVec setBlur(boolean right) { + if (right) { + for (int i = length - 2; i >= 0; i--) { + sets[i + 1] = sets[i + 1].union(sets[i]); + } + } + for (int i = 0; i < length - 1; i++) { + sets[i] = sets[i].union(sets[i + 1]); + } + return this; + } + + public enum ShiftMode { + UNBOUNDED { + @Override + int adjustRight(int right, int length) { + return right; + } + + @Override + int adjustSrc(int src, int length) { + return src; + } + }, + REMAINDER { + @Override + int adjustRight(int right, int length) { + return right % length; + } + + @Override + int adjustSrc(int src, int length) { + return src; + } + }, + CIRCULAR { + @Override + int adjustRight(int right, int length) { + return right % length; + } + + @Override + int adjustSrc(int src, int length) { + int temp = src % length; + if (temp < 0) { + return temp + length; + } + return temp; + } + }; + + abstract int adjustRight(int right, int length); + + abstract int adjustSrc(int src, int length); + } + + /** + * Shift this vector some number of elements, in place + * + * @param right the number of elements to shift right, or negative for left + * @return this vector + */ + public TaintVec setShifted(int right, ShiftMode mode) { + right = mode.adjustRight(right, length); + if (right > length || -right > length) { + return setEmpties(); + } + if (right < 0) { + TaintSet start = sets[0]; + for (int i = 0; i < length; i++) { + int src = mode.adjustSrc(i - right, length); + if (src < 0 || src >= length) { + break; + } + sets[i] = src == 0 ? start : sets[src]; + } + } + else { + TaintSet start = sets[length - 1]; + for (int i = 0; i < length - 1; i++) { + int src = mode.adjustSrc(i - right, length); + if (src < 0 || src >= length) { + break; + } + sets[i] = src == length - 1 ? start : sets[src]; + } + } + return this; + } + + /** + * Drop all but length elements from this vector, creating a new vector + * + *

+ * Drops the most significant elements of this vector, as specified by the endianness + * + * @param length the length fo the new vector + * @param isBigEndian true to drop lower-indexed elements, false to drop higher-indexed elements + * @return the truncated vector + */ + public TaintVec truncated(int length, boolean isBigEndian) { + if (length > this.length) { + throw new IllegalArgumentException(); + } + TaintVec vec = new TaintVec(length); + int diff = isBigEndian ? this.length - length : 0; + for (int i = 0; i < length; i++) { + vec.sets[i] = vec.sets[i + diff]; + } + return vec; + } + + /** + * Create a copy of this vector + * + * @return the copy + */ + public TaintVec copy() { + TaintVec vec = new TaintVec(length); + for (int i = 0; i < length; i++) { + vec.sets[i] = sets[i]; + } + return vec; + } + + /** + * Extend this vector to create a new vector of the given length + * + *

+ * Elements are appended at the most significant end, as specified by the endianness. If signed, + * the appended elements are copies of the most significant element in this vector. Otherwise, + * they are empty taint sets. + * + * @param length the length of the new vector + * @param isBigEndian true to append to the lower-indexed end, false to append to the + * higher-indexed end + * @param isSigned true to append copies of the most significant element, false to append empty + * sets + * @return the new vector + */ + public TaintVec extended(int length, boolean isBigEndian, boolean isSigned) { + if (length < this.length) { + return truncated(length, isBigEndian); + } + TaintVec vec = new TaintVec(length); + int diff = isBigEndian ? length - this.length : 0; + for (int i = 0; i < this.length; i++) { + vec.sets[i + diff] = vec.sets[i]; + } + TaintSet ext = isSigned ? isBigEndian ? sets[0] : sets[this.length - 1] : TaintSet.EMPTY; + int start = isBigEndian ? 0 : this.length; + for (int i = 0; i < diff; i++) { + vec.sets[start + i] = ext; + } + return vec; + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/package-info.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/package-info.java new file mode 100644 index 0000000000..c64313fdff --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/model/package-info.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. + */ +/** + * The Taint domain package + * + *

+ * This package implements the domain of taint analysis. {@link TaintVec} models an array of bytes, + * each having a {@link TaintSet}. A {@link TaintSet} is in turn made of several {@link TaintMarks}. + * Each mark is a symbol with optional tags. We use the tags as a means of handling indirection, so + * that we don't have to decide up front whether tainted offsets taint the values read and written + * from memory. We allow them to be tainted, but add a tag to the mark, so they can be examined + * and/or filtered by the user. + * + *

+ * To facilitate storage and presentation of taint, we will need to implement some + * (de)serialization. Rather than use Java's notion, we'll just implement toString and a static + * parse method for sets and marks. + * + *

+ * We recommend you read the documentation and source from the bottom up: {@link TaintMark}, + * {@link TaintSet}, {@link TaintVec}. + */ +package ghidra.taint.model; diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/package-info.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/package-info.java new file mode 100644 index 0000000000..49b44fecd7 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/taint/package-info.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. + */ +/** + * The Taint Analysis module + * + *

+ * This serves as the archetype for custom emulators and the bells and whistles needed to make them + * accessible and useful. Because this is already a working solution, we won't provide a "tutorial," + * as those often require the presentation of intermediate solutions, working from simple to + * complex. Instead, we'll direct readers to read files in a certain order, as a "tour" working + * mostly in bottom-up fashion. + * + *

+ * Before even starting with the emulator, we must implement the domain of analysis. For some use + * cases, the domain may already be implemented by a 3rd-party library, so it's only necessary to + * add it to your module's dependencies. Our Taint Analyzer implements the domain itself, as its + * fairly simple, and it allows us to tailor it to our needs. For the implementation of the taint + * domain, see the {@link ghidra.taint.model} package. + * + *

+ * Next, we implement the stand-alone emulator using + * {@link ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory}. Technically, that interface + * requires more than necessary for a stand-alone emulator, but because a fully-integrated emulator + * is the goal, you can start with it and leave its methods stubbed until you actually need them. + * The implementation of each method will move our attention to each part necessary to construct the + * emulator. See the {@link ghidra.pcode.emu.taint.plain} package. The emulator itself + * {@link ghidra.pcode.emu.taint.plain.TaintPcodeEmulator} is trivially derived from + * {@link ghidra.pcode.emu.auxiliary.AuxPcodeEmulator} and our factory. + * + *

+ * Next, we implement the trace-integrated emulator. For this, we just implement two more methods: + * {@link ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory#createTraceSharedState(ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator, ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece)} + * and + * {@link ghidra.pcode.exec.debug.auxiliary.AuxDebuggerEmulatorPartsFactory#createTraceLocalState(ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator, ghidra.pcode.emu.PcodeThread, ghidra.pcode.exec.trace.BytesTracePcodeExecutorStatePiece)}. + * Then we derive {@link ghidra.pcode.emu.taint.trace.TaintTracePcodeEmulator} trivially from + * {@link ghidra.pcode.exec.trace.auxiliary.AuxTracePcodeEmulator} and our factory. See the + * {@link ghidra.pcode.emu.taint.trace} package. + * + *

+ * Next, in like fashion, we implement and derive the Debugger-integrated emulator. See the + * {@link ghidra.pcode.emu.taint.full} package. + * + *

+ * Finally, we add some UI components to make the emulator's machine state visible to the user. + * These are in the {@link ghidra.taint.gui.field} package. + * + *

+ * There is a not-yet-integrated user-op library for tainting file reads. See + * {@link ghidra.pcode.emu.taint.lib}. + */ +package ghidra.taint; diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java new file mode 100644 index 0000000000..92dec2d9ed --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/full/TaintDebuggerPcodeEmulatorTest.java @@ -0,0 +1,143 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.full; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; +import java.util.Set; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.Range; + +import ghidra.app.plugin.assembler.Assembler; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.core.debug.gui.AbstractGhidraHeadedDebuggerGUITest; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerEmulationServicePlugin; +import ghidra.app.plugin.core.debug.service.emulation.DebuggerPcodeMachine; +import ghidra.app.plugin.core.debug.service.modules.DebuggerStaticMappingServicePlugin; +import ghidra.app.services.DebuggerEmulationService; +import ghidra.app.services.DebuggerStaticMappingService; +import ghidra.pcode.emu.taint.trace.TaintTracePcodeEmulatorTest; +import ghidra.pcode.emu.taint.trace.TaintTracePcodeExecutorStatePiece; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.util.StringPropertyMap; +import ghidra.program.util.ProgramLocation; +import ghidra.trace.model.DefaultTraceLocation; +import ghidra.trace.model.property.TracePropertyMap; +import ghidra.trace.model.property.TracePropertyMapRegisterSpace; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.schedule.TraceSchedule; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.task.TaskMonitor; + +public class TaintDebuggerPcodeEmulatorTest extends AbstractGhidraHeadedDebuggerGUITest { + private DebuggerStaticMappingService mappingService; + private DebuggerEmulationService emuService; + + @Before + public void setUpTaintTest() throws Throwable { + mappingService = addPlugin(tool, DebuggerStaticMappingServicePlugin.class); + emuService = addPlugin(tool, DebuggerEmulationServicePlugin.class); + } + + @Test + public void testFactoryDiscovered() { + assertEquals(1, + emuService.getEmulatorFactories() + .stream() + .filter(f -> f instanceof TaintDebuggerPcodeEmulatorFactory) + .count()); + } + + @Test + public void testFactoryCreate() throws Exception { + emuService.setEmulatorFactory(new TaintDebuggerPcodeEmulatorFactory()); + + createAndOpenTrace(); + + try (UndoableTransaction tid = tb.startTransaction()) { + tb.getOrAddThread("Threads[0]", 0); + } + + traceManager.activateTrace(tb.trace); + + TraceSchedule time = TraceSchedule.parse("0:t0-1"); + emuService.emulate(tb.trace, time, TaskMonitor.DUMMY); + traceManager.activateTime(time); + + DebuggerPcodeMachine emu = emuService.getCachedEmulator(tb.trace, time); + assertTrue(emu instanceof TaintDebuggerPcodeEmulator); + } + + @Test + public void testReadsProgramUsrProperties() throws Exception { + emuService.setEmulatorFactory(new TaintDebuggerPcodeEmulatorFactory()); + + createAndOpenTrace("x86:LE:64:default"); + createProgramFromTrace(); + + intoProject(program); + intoProject(tb.trace); + + programManager.openProgram(program); + + AddressSpace rs = tb.language.getAddressFactory().getRegisterSpace(); + TraceThread thread; + try (UndoableTransaction tid = tb.startTransaction()) { + mappingService.addMapping( + new DefaultTraceLocation(tb.trace, null, Range.atLeast(0L), tb.addr(0x55550000)), + new ProgramLocation(program, tb.addr(0x00400000)), 0x1000, false); + thread = tb.getOrAddThread("Threads[0]", 0); + tb.exec(0, 0, thread, List.of( + "RIP = 0x55550000;")); + } + waitForDomainObject(tb.trace); + waitForPass(() -> assertEquals(new ProgramLocation(program, tb.addr(0x00400000)), + mappingService.getOpenMappedLocation( + new DefaultTraceLocation(tb.trace, null, Range.singleton(0L), + tb.addr(0x55550000))))); + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Assemble")) { + program.getMemory() + .createInitializedBlock(".text", tb.addr(0x00400000), 0x1000, (byte) 0, + TaskMonitor.DUMMY, false); + StringPropertyMap progTaintMap = program.getUsrPropertyManager() + .createStringPropertyMap(TaintTracePcodeExecutorStatePiece.NAME); + progTaintMap.add(tb.addr(0x00400800), "test_0"); + Assembler asm = Assemblers.getAssembler(program); + + // TODO: I should be able to make this use a RIP-relative address + asm.assemble(tb.addr(0x00400000), + "MOV RAX, [0x55550800]"); // was [0x00400800], but fixed address is a problem. + } + + TraceSchedule time = TraceSchedule.parse("0:t0-1"); + long scratch = emuService.emulate(tb.trace, time, TaskMonitor.DUMMY); + + TracePropertyMap traceTaintMap = tb.trace.getAddressPropertyManager() + .getPropertyMap(TaintTracePcodeExecutorStatePiece.NAME, String.class); + TracePropertyMapRegisterSpace taintRegSpace = + traceTaintMap.getPropertyMapRegisterSpace(thread, 0, false); + + assertEquals(TaintTracePcodeEmulatorTest.makeTaintEntries(tb.trace, + Range.closed(scratch, -1L), rs, Set.of(0L), "test_0"), + Set.copyOf(taintRegSpace.getEntries(Range.singleton(scratch), tb.reg("RAX")))); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulatorTest.java new file mode 100644 index 0000000000..597d004a84 --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/plain/TaintPcodeEmulatorTest.java @@ -0,0 +1,301 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.plain; + +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.*; + +import ghidra.app.plugin.assembler.*; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.linux.AbstractEmuLinuxSyscallUseropLibrary; +import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibraryTest; +import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibraryTest.Syscall; +import ghidra.pcode.emu.sys.EmuProcessExitedException; +import ghidra.pcode.emu.taint.lib.TaintEmuUnixFileSystem; +import ghidra.pcode.emu.taint.lib.TaintFileReadsLinuxAmd64SyscallLibrary; +import ghidra.pcode.exec.PcodeUseropLibrary; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.taint.model.TaintSet; +import ghidra.taint.model.TaintVec; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.util.database.UndoableTransaction; +import ghidra.util.task.TaskMonitor; + +public class TaintPcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTest { + protected final class LinuxAmd64TaintPcodeEmulator extends TaintPcodeEmulator { + public LinuxAmd64TaintPcodeEmulator() { + super(program.getLanguage()); + } + + @Override + protected PcodeUseropLibrary> createUseropLibrary() { + return super.createUseropLibrary() + .compose(new TaintFileReadsLinuxAmd64SyscallLibrary(this, fs, program)); + } + } + + protected static final byte[] BYTES_HW = "Hello, World!\n".getBytes(); + + private Program program; + private Language language; + private AddressSpace space; + private Address start; + private int size; + private MemoryBlock block; + private Assembler asm; + + private TaintEmuUnixFileSystem fs; + private TaintPcodeEmulator emulator; + + @Before + public void setUpTaintTest() throws Exception { + program = createDefaultProgram("HelloTaint", "x86:LE:64:default", "gcc", this); + language = program.getLanguage(); + space = program.getAddressFactory().getDefaultAddressSpace(); + start = space.getAddress(0x00400000); + size = 0x1000; + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { + block = program.getMemory() + .createInitializedBlock(".text", start, size, (byte) 0, TaskMonitor.DUMMY, + false); + + EmuLinuxAmd64SyscallUseropLibraryTest.SYSCALL_HELPER.bootstrapProgram(program); + } + + asm = Assemblers.getAssembler(program); + + fs = new TaintEmuUnixFileSystem(); + emulator = new LinuxAmd64TaintPcodeEmulator(); + } + + @After + public void tearDownTaintTest() throws Exception { + if (program != null) { + program.release(this); + } + } + + public void prepareEmulator() throws Exception { + // The emulator is not itself bound to the program or a trace, so copy bytes in + byte[] buf = new byte[size]; + assertEquals(size, block.getBytes(start, buf)); + emulator.getSharedState().getLeft().setVar(space, start.getOffset(), size, true, buf); + } + + public PcodeThread launchThread(Address pc) { + PcodeThread thread = emulator.newThread(); + thread.overrideCounter(start); + thread.overrideContextWithDefault(); + thread.reInitialize(); + return thread; + } + + public void execute(PcodeThread thread) { + try { + thread.stepInstruction(1000); + fail(); + } + catch (EmuProcessExitedException e) { + } + } + + @Test + public void testZeroByXor() + throws AssemblySyntaxException, AssemblySemanticException, IOException { + PcodeThread> thread = emulator.newThread(); + AddressSpace dyn = language.getDefaultSpace(); + + Address entry = dyn.getAddress(0x00400000); + Assembler asm = Assemblers.getAssembler(language); + AssemblyBuffer buffer = new AssemblyBuffer(asm, entry); + buffer.assemble("XOR RAX, RAX"); + byte[] prog = buffer.getBytes(); + + Register regRAX = language.getRegister("RAX"); + Pair initRAX = + Pair.of(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, TaintVec.array("RAX", 0, 8)); + thread.getState().setVar(regRAX, initRAX); + + emulator.getSharedState().getLeft().setVar(dyn, 0x00400000, prog.length, true, prog); + + thread.overrideCounter(entry); + thread.overrideContextWithDefault(); + thread.stepInstruction(); + + Pair endRAX = thread.getState().getVar(regRAX); + assertEquals(0, + Utils.bytesToLong(endRAX.getLeft(), regRAX.getNumBytes(), language.isBigEndian())); + assertEquals(TaintVec.empties(regRAX.getNumBytes()), endRAX.getRight()); + } + + @Test + public void testTaintFileReads() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize")) { + asm.assemble(start, + "MOV RAX," + Syscall.OPEN.number, + "LEA RDI,[0x00400880]", + "MOV RSI," + (AbstractEmuLinuxSyscallUseropLibrary.O_RDONLY), + "MOV RDX," + (0600), + "SYSCALL", + "MOV RBP, RAX", + + "MOV RAX," + Syscall.READ.number, + "MOV RDI,RBP", + "LEA RSI,[0x00400800]", + "MOV RDX," + BYTES_HW.length, + "SYSCALL", + + "MOV RAX," + Syscall.CLOSE.number, + "MOV RDI,RBP", + + "MOV RAX," + Syscall.GROUP_EXIT.number, + "MOV RDI,0", + "SYSCALL"); + block.putBytes(space.getAddress(0x00400880), "myfile\0".getBytes()); + } + + fs.putTaintedFile("myfile", BYTES_HW); + + prepareEmulator(); + PcodeThread thread = launchThread(start); + execute(thread); + + Pair buf = + emulator.getSharedState().getVar(space, 0x00400800, BYTES_HW.length, true); + assertArrayEquals(BYTES_HW, buf.getLeft()); + assertEquals(TaintVec.array("myfile", 0, BYTES_HW.length), buf.getRight()); + } + + @Test + public void testTaintViaSleigh() throws Exception { + prepareEmulator(); + PcodeThread thread = launchThread(start); + thread.getExecutor().executeSleighLine("*:8 0x00400000:8 = taint_arr(*:8 0x004000000:8)"); + + Pair taintVal = + emulator.getSharedState().getVar(space, 0x00400000, 8, true); + assertArrayEquals(new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }, taintVal.getLeft()); + assertEquals(TaintVec.array("arr_0", 0, 8), taintVal.getRight()); + } + + @Test + public void testTaintIndirectRead() throws Exception { + PcodeThread> thread = emulator.newThread(); + AddressSpace dyn = language.getDefaultSpace(); + + Address entry = dyn.getAddress(0x00400000); + Assembler asm = Assemblers.getAssembler(language); + AssemblyBuffer buffer = new AssemblyBuffer(asm, entry); + buffer.assemble("MOV RBX, qword ptr [RAX]"); + byte[] prog = buffer.getBytes(); + + Register regRAX = language.getRegister("RAX"); + Register regRBX = language.getRegister("RBX"); + Pair initRAX = + Pair.of(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, TaintVec.array("RAX", 0, 8)); + thread.getState().setVar(regRAX, initRAX); + Pair initMem = + Pair.of(new byte[] { 9, 10, 11, 12, 13, 14, 15, 16 }, TaintVec.array("mem", 0, 8)); + emulator.getSharedState().setVar(dyn, 0x0807060504030201L, 8, true, initMem); + emulator.getSharedState().getLeft().setVar(dyn, 0x00400000, prog.length, true, prog); + + thread.overrideCounter(entry); + thread.overrideContextWithDefault(); + thread.stepInstruction(); + + Pair endRBX = thread.getState().getVar(regRBX); + assertEquals(0x100f0e0d0c0b0a09L, + Utils.bytesToLong(endRBX.getLeft(), regRBX.getNumBytes(), language.isBigEndian())); + TaintSet fromIndirect = TaintVec.array("RAX", 0, 8).union().tagged("indR"); + TaintVec exp = TaintVec.array("mem", 0, 8).eachUnion(fromIndirect); + assertEquals(exp, endRBX.getRight()); + } + + @Test + public void testTaintIndrectWrite() throws Exception { + + PcodeThread> thread = emulator.newThread(); + AddressSpace dyn = language.getDefaultSpace(); + + Address entry = dyn.getAddress(0x00400000); + Assembler asm = Assemblers.getAssembler(language); + AssemblyBuffer buffer = new AssemblyBuffer(asm, entry); + buffer.assemble("MOV qword ptr [RAX], RBX"); + byte[] prog = buffer.getBytes(); + + Register regRAX = language.getRegister("RAX"); + Register regRBX = language.getRegister("RBX"); + Pair initRAX = + Pair.of(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, TaintVec.array("RAX", 0, 8)); + thread.getState().setVar(regRAX, initRAX); + Pair initRBX = + Pair.of(new byte[] { 9, 10, 11, 12, 13, 14, 15, 16 }, TaintVec.array("RBX", 0, 8)); + thread.getState().setVar(regRBX, initRBX); + emulator.getSharedState().getLeft().setVar(dyn, 0x00400000, prog.length, true, prog); + + thread.overrideCounter(entry); + thread.overrideContextWithDefault(); + thread.stepInstruction(); + + Pair endMem = + emulator.getSharedState().getVar(dyn, 0x0807060504030201L, 8, true); + assertEquals(0x100f0e0d0c0b0a09L, + Utils.bytesToLong(endMem.getLeft(), regRBX.getNumBytes(), language.isBigEndian())); + TaintSet fromIndirect = TaintVec.array("RAX", 0, 8).union().tagged("indW"); + TaintVec exp = TaintVec.array("RBX", 0, 8).eachUnion(fromIndirect); + assertEquals(exp, endMem.getRight()); + } + + @Test + public void testMovReplaces() + throws AssemblySyntaxException, AssemblySemanticException, IOException { + PcodeThread> thread = emulator.newThread(); + AddressSpace dyn = language.getDefaultSpace(); + + Address entry = dyn.getAddress(0x00400000); + Assembler asm = Assemblers.getAssembler(language); + AssemblyBuffer buffer = new AssemblyBuffer(asm, entry); + buffer.assemble("MOV RAX, RBX"); + byte[] prog = buffer.getBytes(); + + Register regRAX = language.getRegister("RAX"); + Pair initRAX = + Pair.of(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, TaintVec.array("RAX", 0, 8)); + thread.getState().setVar(regRAX, initRAX); + + emulator.getSharedState().getLeft().setVar(dyn, 0x00400000, prog.length, true, prog); + + thread.overrideCounter(entry); + thread.overrideContextWithDefault(); + thread.stepInstruction(); + + Pair endRAX = thread.getState().getVar(regRAX); + assertEquals(0, + Utils.bytesToLong(endRAX.getLeft(), regRAX.getNumBytes(), language.isBigEndian())); + assertEquals(TaintVec.empties(regRAX.getNumBytes()), endRAX.getRight()); + } +} diff --git a/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java new file mode 100644 index 0000000000..24e108ac6f --- /dev/null +++ b/Ghidra/Debug/TaintAnalysis/src/test/java/ghidra/pcode/emu/taint/trace/TaintTracePcodeEmulatorTest.java @@ -0,0 +1,213 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.taint.trace; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import java.util.*; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.Test; + +import com.google.common.collect.Range; + +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.trace.AbstractTracePcodeEmulatorTest; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +import ghidra.taint.model.*; +import ghidra.trace.database.ToyDBTraceBuilder; +import ghidra.trace.model.*; +import ghidra.trace.model.property.TracePropertyMap; +import ghidra.trace.model.property.TracePropertyMapRegisterSpace; +import ghidra.trace.model.thread.TraceThread; +import ghidra.util.database.UndoableTransaction; + +public class TaintTracePcodeEmulatorTest extends AbstractTracePcodeEmulatorTest { + + public static Map.Entry makeTaintEntry(Trace trace, + Range span, AddressSpace space, long offset, String taint) { + Address addr = space.getAddress(offset); + return Map.entry(new ImmutableTraceAddressSnapRange(new AddressRangeImpl(addr, addr), span), + taint); + } + + public static Set> makeTaintEntries(Trace trace, + Range span, AddressSpace space, Set offs, String taint) { + return offs.stream() + .map(o -> makeTaintEntry(trace, span, space, o, taint)) + .collect(Collectors.toSet()); + } + + /** + * Test that state is properly read from trace memory + * + *

+ * We isolate exactly a read by executing sleigh. + */ + @Test + public void testReadStateMemory() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + TraceThread thread = initTrace(tb, List.of(), List.of()); + + try (UndoableTransaction tid = tb.startTransaction()) { + TracePropertyMap taintMap = tb.trace.getAddressPropertyManager() + .getOrCreatePropertyMap("Taint", String.class); + taintMap.set(Range.atLeast(0L), tb.range(0x00400000, 0x00400003), "test_0"); + } + + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + PcodeThread> emuThread = emu.newThread(thread.getPath()); + emuThread.getExecutor().executeSleighLine("RAX = *0x00400000:8"); + + Pair valRAX = + emuThread.getState().getVar(tb.language.getRegister("RAX")); + TaintVec exp = TaintVec.empties(8); + TaintSet testTaint = TaintSet.of(new TaintMark("test_0", Set.of())); + for (int i = 0; i < 4; i++) { + exp.set(i, testTaint); + } + assertEquals(exp, valRAX.getRight()); + } + } + + @Test + public void testReadStateRegister() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + TraceThread thread = initTrace(tb, List.of(), List.of()); + Register regRAX = tb.language.getRegister("RAX"); + Register regEBX = tb.language.getRegister("EBX"); + + try (UndoableTransaction tid = tb.startTransaction()) { + TracePropertyMap taintMap = tb.trace.getAddressPropertyManager() + .getOrCreatePropertyMap("Taint", String.class); + TracePropertyMapRegisterSpace mapSpace = + taintMap.getPropertyMapRegisterSpace(thread, 0, true); + mapSpace.set(Range.atLeast(0L), regEBX, "test_0"); + } + + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + PcodeThread> emuThread = emu.newThread(thread.getPath()); + emuThread.getExecutor().executeSleighLine("RAX = RBX"); + + Pair valRAX = + emuThread.getState().getVar(regRAX); + TaintVec exp = TaintVec.empties(8); + TaintSet testTaint = TaintSet.of(new TaintMark("test_0", Set.of())); + for (int i = 0; i < 4; i++) { + exp.set(i, testTaint); + } + assertEquals(exp, valRAX.getRight()); + } + } + + @Test + public void testWriteStateMemory() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + initTrace(tb, List.of(), List.of()); + + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + TaintVec taintVal = TaintVec.empties(8); + TaintSet testTaint = TaintSet.of(new TaintMark("test_0", Set.of())); + for (int i = 0; i < 4; i++) { + taintVal.set(i, testTaint); + } + emu.getSharedState() + .setVar(tb.addr(0x00400000), 8, true, + Pair.of(tb.arr(0, 0, 0, 0, 0, 0, 0, 0), taintVal)); + + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(tb.trace, 1, 0); + } + TracePropertyMap taintMap = + tb.trace.getAddressPropertyManager().getPropertyMap("Taint", String.class); + assertEquals("test_0", taintMap.get(1, tb.addr(0x00400000))); + assertEquals("test_0", taintMap.get(1, tb.addr(0x00400003))); + assertNull(taintMap.get(1, tb.addr(0x00400004))); + } + } + + @Test + public void testWriteStateRegister() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + AddressSpace rs = tb.language.getAddressFactory().getRegisterSpace(); + TraceThread thread = initTrace(tb, List.of(), List.of()); + + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + PcodeThread> emuThread = emu.newThread(thread.getPath()); + TaintVec taintVal = TaintVec.empties(8); + TaintSet testTaint = TaintSet.of(new TaintMark("test_0", Set.of())); + for (int i = 0; i < 4; i++) { + taintVal.set(i, testTaint); + } + emuThread.getState().setVar(tb.reg("EAX"), Pair.of(tb.arr(0, 0, 0, 0), taintVal)); + + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(tb.trace, 1, 0); + } + TracePropertyMap taintMap = + tb.trace.getAddressPropertyManager().getPropertyMap("Taint", String.class); + TracePropertyMapRegisterSpace mapSpace = + taintMap.getPropertyMapRegisterSpace(thread, 0, false); + // TODO: Might be nice to coalesce identical values + // Becomes the 2D cover optimization problem. Still could do some easy cases. + assertEquals( + makeTaintEntries(tb.trace, Range.atLeast(1L), rs, Set.of(0L, 1L, 2L, 3L), "test_0"), + Set.copyOf(mapSpace.getEntries(Range.singleton(1L), tb.reg("RAX")))); + } + } + + @Test + public void testEmptyTaintClears() throws Throwable { + try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { + AddressSpace ram = tb.language.getAddressFactory().getDefaultAddressSpace(); + TraceThread thread = initTrace(tb, + List.of( + "RIP = 0x00400000;"), + List.of( + "MOV qword ptr [0x00600000], RAX", + "MOV qword ptr [0x00600000], RBX")); + + TaintTracePcodeEmulator emu = new TaintTracePcodeEmulator(tb.trace, 0); + PcodeThread> emuThread = emu.newThread(thread.getPath()); + emuThread.getState() + .setVar(tb.reg("RAX"), Pair.of( + tb.arr(0, 0, 0, 0, 0, 0, 0, 0), + TaintVec.copies(TaintSet.parse("test_0"), 8))); + + emuThread.stepInstruction(); + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(tb.trace, 1, 0); + } + emuThread.stepInstruction(); + try (UndoableTransaction tid = tb.startTransaction()) { + emu.writeDown(tb.trace, 2, 0); + } + + TracePropertyMap taintMap = + tb.trace.getAddressPropertyManager().getPropertyMap("Taint", String.class); + + assertEquals(makeTaintEntries(tb.trace, Range.singleton(1L), ram, Set.of( + 0x00600000L, 0x00600001L, 0x00600002L, 0x00600003L, + 0x00600004L, 0x00600005L, 0x00600006L, 0x00600007L), + "test_0"), + Set.copyOf(taintMap.getEntries( + Range.singleton(1L), tb.range(0x00600000, 0x00600007)))); + } + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblyBuffer.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblyBuffer.java new file mode 100644 index 0000000000..ed6020307a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblyBuffer.java @@ -0,0 +1,109 @@ +/* ### + * 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.assembler; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; + +/** + * A convenience for accumulating bytes output by an {@link Assembler} + * + *

+ * This is most useful when there is not a {@link Program} available for assembly. If a program is + * available, consider using {@link Assembler#assemble(Address, String...)} and reading the bytes + * from the program. If not, or the program should not be modified, then the pattern of use is + * generally: + * + *

+ * Address start = space.getAdddress(0x00400000);
+ * Assembler asm = Assemblers.getAssembler(...);
+ * AssemblyBuffer buffer = new AssemblyBuffer(asm, start);
+ * 
+ * buffer.assemble("PUSH R15");
+ * buffer.assemble("PUSH R14");
+ * buffer.assemble("PUSH R13");
+ * ...
+ * byte[] bytes = buffer.getBytes();
+ * state.setVar(start, bytes.length, true, bytes);
+ * 
+ */ +public class AssemblyBuffer { + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + private final Assembler asm; + private final Address entry; + + /** + * Create a buffer with the given assembler starting at the given entry + * + * @param asm the assembler + * @param entry the starting address where the resulting code will be located + */ + public AssemblyBuffer(Assembler asm, Address entry) { + this.asm = asm; + this.entry = entry; + } + + /** + * Get the address of the "cursor" where the next instruction will be assembled + * + * @return the address + */ + public Address getNext() { + return entry.add(baos.size()); + } + + /** + * Assemble a line and append it to the buffer + * + * @param line the line + * @return the resulting bytes for the assembled instruction + * @throws AssemblySyntaxException if the instruction cannot be parsed + * @throws AssemblySemanticException if the instruction cannot be encoded + * @throws IOException if the buffer cannot be written + */ + public byte[] assemble(String line) + throws AssemblySyntaxException, AssemblySemanticException, IOException { + return emit(asm.assembleLine(getNext(), line)); + } + + /** + * Append arbitrary bytes to the buffer + * + * @param bytes the bytes to append + * @return bytes + * @throws IOException if the bufgfer cannot be written + */ + public byte[] emit(byte[] bytes) throws IOException { + baos.write(bytes); + return bytes; + } + + /** + * Get the complete buffer of bytes + * + *

+ * However used, the bytes should be placed at the {@code entry} given at construction, unless + * the client is certain the code is position independent. + * + * @return the bytes + */ + public byte[] getBytes() { + return baos.toByteArray(); + } +}

+ * If every part of every input to the expression is {@link TraceMemoryState#KNOWN}, then the + * expression's value will be marked {@link TraceMemoryState#KNOWN}. Otherwise, it's marked + * {@link TraceMemoryState#UNKNOWN}. + * + * @return the paired executor state + */ + public PcodeExecutorStatePiece> withMemoryState() { + return new PairedPcodeExecutorStatePiece<>(this, + new TraceMemoryStatePcodeExecutorStatePiece(trace, snap, thread, frame)); + } + + /** + * Get the trace + * + * @return the trace + */ public Trace getTrace() { return trace; } + /** + * Re-bind this state to another snap + * + * @param snap the new snap + */ public void setSnap(long snap) { this.snap = snap; this.viewport.setSnap(snap); } + /** + * Get the current snap + * + * @return the snap + */ public long getSnap() { return snap; } + /** + * Re-bind this state to another thread + * + * @param thread the new thread + */ public void setThread(TraceThread thread) { if (thread != null & thread.getTrace() != trace) { throw new IllegalArgumentException("Thread, if given, must be part of the same trace"); @@ -103,28 +134,33 @@ public class TraceBytesPcodeExecutorState this.thread = thread; } + /** + * Get the current thread + * + * @return the thread + */ public TraceThread getThread() { return thread; } + /** + * Re-bind this state to another frame + * + * @param frame the new frame + */ public void setFrame(int frame) { this.frame = frame; } + /** + * Get the current frame + * + * @return the frame + */ public int getFrame() { return frame; } - @Override - public long offsetToLong(byte[] offset) { - return Utils.bytesToLong(offset, offset.length, language.isBigEndian()); - } - - @Override - public byte[] longToOffset(AddressSpace space, long l) { - return arithmetic.fromConst(l, space.getPointerSize()); - } - @Override protected void setUnique(long offset, int size, byte[] val) { assert size == val.length; @@ -164,7 +200,7 @@ public class TraceBytesPcodeExecutorState } @Override - public MemBuffer getConcreteBuffer(Address address) { + public MemBuffer getConcreteBuffer(Address address, Purpose purpose) { return trace.getMemoryManager().getBufferAt(snap, address); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java new file mode 100644 index 0000000000..a20d2018d9 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorState.java @@ -0,0 +1,55 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.exec.PairedPcodeExecutorState; +import ghidra.trace.model.Trace; +import ghidra.trace.model.thread.TraceThread; + +/** + * A trace-bound state composed of another trace-bound state and a piece + * + * @param the type of values for the left state + * @param the type of values for the right piece + * @see PairedPcodeExecutorState + */ +public class PairedTracePcodeExecutorState extends PairedPcodeExecutorState + implements TracePcodeExecutorState> { + + private final TracePcodeExecutorStatePiece left; + private final TracePcodeExecutorStatePiece right; + + public PairedTracePcodeExecutorState(PairedTracePcodeExecutorStatePiece piece) { + super(piece); + this.left = piece.getLeft(); + this.right = piece.getRight(); + } + + public PairedTracePcodeExecutorState(TracePcodeExecutorState left, + TracePcodeExecutorStatePiece right) { + super(left, right); + this.left = left; + this.right = right; + } + + @Override + public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { + left.writeDown(trace, snap, thread, frame); + right.writeDown(trace, snap, thread, frame); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java new file mode 100644 index 0000000000..8e6f96ec96 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/PairedTracePcodeExecutorStatePiece.java @@ -0,0 +1,70 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import org.apache.commons.lang3.tuple.Pair; + +import ghidra.pcode.exec.PairedPcodeExecutorStatePiece; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.trace.model.Trace; +import ghidra.trace.model.thread.TraceThread; + +/** + * A trace-bound state piece composed of two other trace-bound pieces sharing the same address type + * + * @see PairedPcodeExecutorStatePiece + * @param the type of addresses + * @param the type of values for the left piece + * @param the type of values for the right piece + */ +public class PairedTracePcodeExecutorStatePiece + extends PairedPcodeExecutorStatePiece + implements TracePcodeExecutorStatePiece> { + + protected final TracePcodeExecutorStatePiece left; + protected final TracePcodeExecutorStatePiece right; + + public PairedTracePcodeExecutorStatePiece(TracePcodeExecutorStatePiece left, + TracePcodeExecutorStatePiece right) { + super(left, right); + this.left = left; + this.right = right; + } + + public PairedTracePcodeExecutorStatePiece(TracePcodeExecutorStatePiece left, + TracePcodeExecutorStatePiece right, PcodeArithmetic addressArithmetic, + PcodeArithmetic> arithmetic) { + super(left, right, addressArithmetic, arithmetic); + this.left = left; + this.right = right; + } + + @Override + public void writeDown(Trace trace, long snap, TraceThread thread, int frame) { + left.writeDown(trace, snap, thread, frame); + right.writeDown(trace, snap, thread, frame); + } + + @Override + public TracePcodeExecutorStatePiece getLeft() { + return left; + } + + @Override + public TracePcodeExecutorStatePiece getRight() { + return right; + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java index fea268383a..9aff7932ad 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorState.java @@ -15,31 +15,26 @@ */ package ghidra.pcode.exec.trace; -import com.google.common.collect.Range; - -import ghidra.pcode.exec.AccessPcodeExecutionException; -import ghidra.program.model.address.AddressSetView; import ghidra.trace.model.Trace; -import ghidra.trace.model.memory.TraceMemorySpace; -import ghidra.trace.model.memory.TraceMemoryState; import ghidra.trace.model.thread.TraceThread; +/** + * A state composing a single {@link RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece} + */ public class RequireHasKnownTraceCachedWriteBytesPcodeExecutorState - extends RequireIsKnownTraceCachedWriteBytesPcodeExecutorState { + extends DefaultTracePcodeExecutorState { + /** + * Create the state + * + * @param trace the trace from which to load state + * @param snap the snap from which to load state + * @param thread if applicable, the thread identifying the register space + * @param frame if applicable, the frame identifying the register space + */ public RequireHasKnownTraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) { - super(trace, snap, thread, frame); - } - - @Override - protected AddressSetView getKnown(TraceMemorySpace source) { - return source.getAddressesWithState(Range.closed(0L, snap), - s -> s == TraceMemoryState.KNOWN); - } - - @Override - protected AccessPcodeExecutionException excFor(AddressSetView unknown) { - throw new AccessPcodeExecutionException("Memory at " + unknown + " has never been known."); + super(new RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece(trace, snap, thread, + frame)); } } diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java new file mode 100644 index 0000000000..48785910e7 --- /dev/null +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireHasKnownTraceCachedWriteBytesPcodeExecutorStatePiece.java @@ -0,0 +1,53 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec.trace; + +import com.google.common.collect.Range; + +import ghidra.pcode.exec.AccessPcodeExecutionException; +import ghidra.program.model.address.AddressSetView; +import ghidra.trace.model.Trace; +import ghidra.trace.model.memory.TraceMemorySpace; +import ghidra.trace.model.memory.TraceMemoryState; +import ghidra.trace.model.thread.TraceThread; + +/** + * A relaxation of {@link RequireIsKnownTraceCachedWriteBytesPcodeExecutorStatePiece} that permits + * reads of stale addresses + * + *