From b33800ecba40d50718a651776152f67a03941241 Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 20 May 2022 11:05:53 -0400 Subject: [PATCH] GP-1208: Implement linux-x86/-amd64 system call simulators --- .../DebuggerEmuExampleScript.java | 211 ++ .../DemoPcodeUseropLibrary.java | 131 ++ .../ghidra_scripts/DemoSyscallLibrary.java | 211 ++ .../StandAloneEmuExampleScript.java | 179 ++ .../StandAloneStructuredSleighScript.java | 112 + .../StandAloneSyscallEmuExampleScript.java | 233 ++ .../debug/gui/action/DebuggerGoToTrait.java | 4 +- .../plugin/core/debug/gui/watch/WatchRow.java | 4 +- ...AbstractReadsTargetPcodeExecutorState.java | 12 +- .../ReadsTargetMemoryPcodeExecutorState.java | 6 +- .../ghidra/pcode/exec/AsyncPcodeExecutor.java | 12 +- .../exec/AsyncWrappedPcodeArithmetic.java | 5 + .../DebuggerPcodeStepperProviderTest.java | 2 +- .../exec/TraceRecorderAsyncPcodeExecTest.java | 6 +- ...aceCachedWriteBytesPcodeExecutorState.java | 16 +- ...aceCachedWriteBytesPcodeExecutorState.java | 6 +- ...aceCachedWriteBytesPcodeExecutorState.java | 209 +- .../TraceMemoryStatePcodeArithmetic.java | 5 + .../pcode/exec/trace/TracePcodeEmulator.java | 13 +- .../pcode/exec/trace/TraceSleighUtils.java | 8 +- .../listing/DBTraceCodeUnitAdapter.java | 4 +- .../database/memory/DBTraceMemBuffer.java | 2 +- .../trace/model/time/schedule/PatchStep.java | 2 +- .../exec/trace/TracePcodeEmulatorTest.java | 26 +- .../exec/trace/TraceSleighUtilsTest.java | 4 +- .../trace/database/ToyDBTraceBuilder.java | 4 +- .../time/schedule/TraceScheduleTest.java | 11 +- .../emu/AbstractModifiedPcodeThread.java | 42 +- .../pcode/emu/AbstractPcodeEmulator.java | 35 - .../pcode/emu/AbstractPcodeMachine.java | 94 +- .../ghidra/pcode/emu/BytesPcodeThread.java | 14 + .../ghidra/pcode/emu/DefaultPcodeThread.java | 162 +- .../ghidra/pcode/emu/InstructionDecoder.java | 5 +- .../java/ghidra/pcode/emu/PcodeEmulator.java | 143 ++ .../java/ghidra/pcode/emu/PcodeMachine.java | 16 +- .../pcode/emu/PcodeStateInitializer.java | 10 +- .../java/ghidra/pcode/emu/PcodeThread.java | 98 +- .../pcode/emu/SleighInstructionDecoder.java | 21 +- .../pcode/emu/ThreadPcodeExecutorState.java | 29 + .../AbstractEmuLinuxSyscallUseropLibrary.java | 109 + .../EmuLinuxAmd64SyscallUseropLibrary.java | 104 + .../EmuLinuxX86SyscallUseropLibrary.java | 127 ++ .../sys/AnnotatedEmuSyscallUseropLibrary.java | 180 ++ .../ghidra/pcode/emu/sys/EmuIOException.java | 37 + .../sys/EmuInvalidSystemCallException.java | 39 + .../emu/sys/EmuProcessExitedException.java | 63 + .../pcode/emu/sys/EmuSyscallLibrary.java | 268 +++ .../pcode/emu/sys/EmuSystemException.java | 36 + .../emu/sys/UseropEmuSyscallDefinition.java | 121 + .../pcode/emu/unix/AbstractEmuUnixFile.java | 65 + .../emu/unix/AbstractEmuUnixFileSystem.java | 74 + .../AbstractEmuUnixSyscallUseropLibrary.java | 312 +++ .../unix/AbstractStreamEmuUnixFileHandle.java | 62 + .../emu/unix/BytesEmuUnixFileSystem.java | 92 + .../emu/unix/DefaultEmuUnixFileHandle.java | 133 ++ .../pcode/emu/unix/EmuUnixException.java | 65 + .../ghidra/pcode/emu/unix/EmuUnixFile.java | 126 ++ .../pcode/emu/unix/EmuUnixFileDescriptor.java | 81 + .../pcode/emu/unix/EmuUnixFileStat.java | 83 + .../pcode/emu/unix/EmuUnixFileSystem.java | 167 ++ .../ghidra/pcode/emu/unix/EmuUnixUser.java | 43 + .../emu/unix/IOStreamEmuUnixFileHandle.java | 120 + .../exec/AbstractBytesPcodeExecutorState.java | 162 ++ .../AbstractLongOffsetPcodeExecutorState.java | 7 +- ...ractLongOffsetPcodeExecutorStatePiece.java | 74 + ...ctOffsetTransformedPcodeExecutorState.java | 18 + .../pcode/exec/AddressOfPcodeArithmetic.java | 18 + .../exec/AddressOfPcodeExecutorState.java | 15 + .../exec/AnnotatedPcodeUseropLibrary.java | 557 +++++ .../exec/AnnotatedSleighUseropLibrary.java | 169 -- .../pcode/exec/BigIntegerPcodeArithmetic.java | 12 + .../pcode/exec/BytesPcodeArithmetic.java | 33 +- .../pcode/exec/BytesPcodeExecutorState.java | 40 + .../exec/BytesPcodeExecutorStateSpace.java | 173 ++ .../exec/ComposedPcodeUseropLibrary.java | 70 + .../exec/ComposedSleighUseropLibrary.java | 46 - .../InterruptPcodeExecutionException.java | 6 + .../pcode/exec/PairedPcodeArithmetic.java | 31 +- .../pcode/exec/PairedPcodeExecutorState.java | 24 + .../exec/PairedPcodeExecutorStatePiece.java | 18 + .../ghidra/pcode/exec/PcodeArithmetic.java | 110 +- .../pcode/exec/PcodeExecutionException.java | 35 + .../java/ghidra/pcode/exec/PcodeExecutor.java | 40 +- .../ghidra/pcode/exec/PcodeExecutorState.java | 21 + .../pcode/exec/PcodeExecutorStatePiece.java | 104 + .../ghidra/pcode/exec/PcodeExpression.java | 80 + .../java/ghidra/pcode/exec/PcodeFrame.java | 117 +- .../java/ghidra/pcode/exec/PcodeProgram.java | 38 +- .../ghidra/pcode/exec/PcodeUseropLibrary.java | 161 ++ .../ghidra/pcode/exec/SleighExpression.java | 53 - .../pcode/exec/SleighLinkException.java | 7 + .../exec/SleighPcodeUseropDefinition.java | 221 ++ .../pcode/exec/SleighProgramCompiler.java | 239 +- .../pcode/exec/SleighUseropLibrary.java | 88 - .../SuspendedPcodeExecutionException.java | 6 + .../ghidra/pcode/struct/AbstractStmt.java | 107 + .../ghidra/pcode/struct/ArithBinExpr.java | 55 + .../java/ghidra/pcode/struct/AssignStmt.java | 65 + .../java/ghidra/pcode/struct/BinExpr.java | 47 + .../java/ghidra/pcode/struct/BlockStmt.java | 73 + .../java/ghidra/pcode/struct/BreakStmt.java | 29 + .../java/ghidra/pcode/struct/CallExpr.java | 71 + .../java/ghidra/pcode/struct/CmpExpr.java | 146 ++ .../ghidra/pcode/struct/ConditionalStmt.java | 30 + .../ghidra/pcode/struct/ContinueStmt.java | 29 + .../java/ghidra/pcode/struct/DeclStmt.java | 36 + .../pcode/struct/DefaultUseropDecl.java | 69 + .../java/ghidra/pcode/struct/DefaultVar.java | 120 + .../java/ghidra/pcode/struct/DerefExpr.java | 54 + .../main/java/ghidra/pcode/struct/Expr.java | 43 + .../java/ghidra/pcode/struct/FieldExpr.java | 53 + .../java/ghidra/pcode/struct/ForStmt.java | 51 + .../main/java/ghidra/pcode/struct/IfStmt.java | 65 + .../java/ghidra/pcode/struct/IndexExpr.java | 52 + .../java/ghidra/pcode/struct/InvExpr.java | 29 + .../ghidra/pcode/struct/LValInternal.java | 65 + .../java/ghidra/pcode/struct/LangVar.java} | 14 +- .../java/ghidra/pcode/struct/LiteralExpr.java | 45 + .../ghidra/pcode/struct/LiteralFloatExpr.java | 32 + .../ghidra/pcode/struct/LiteralLongExpr.java | 29 + .../java/ghidra/pcode/struct/LocalVar.java | 24 + .../java/ghidra/pcode/struct/LoopStmt.java | 29 + .../ghidra/pcode/struct/LoopTruncateStmt.java | 43 + .../java/ghidra/pcode/struct/NotExpr.java | 29 + .../ghidra/pcode/struct/RValInternal.java | 363 +++ .../java/ghidra/pcode/struct/RawExpr.java | 43 + .../java/ghidra/pcode/struct/RawStmt.java | 32 + .../java/ghidra/pcode/struct/ResultStmt.java | 43 + .../java/ghidra/pcode/struct/ReturnStmt.java | 33 + .../java/ghidra/pcode/struct/RoutineStmt.java | 45 + .../ghidra/pcode/struct/StructuredSleigh.java | 1938 +++++++++++++++++ .../main/java/ghidra/pcode/struct/UnExpr.java | 45 + .../ghidra/pcode/struct/VoidExprStmt.java | 59 + .../java/ghidra/pcode/struct/WhileStmt.java | 39 + .../program/model/mem}/MemBufferAdapter.java | 2 +- .../utilities/util/AnnotationUtilities.java | 70 + ...EmuLinuxAmd64SyscallUseropLibraryTest.java | 444 ++++ .../EmuLinuxX86SyscallUseropLibraryTest.java | 444 ++++ .../sys/EmuAmd64SyscallUseropLibraryTest.java | 215 ++ .../exec/AnnotatedPcodeUseropLibraryTest.java | 430 ++++ .../ghidra/pcode/exec/PcodeFrameTest.java | 4 +- .../struct/sub/StructuredSleighTest.java | 240 ++ .../pcodeCPort/slgh_compile/PcodeCompile.java | 52 +- .../program/model/lang/PcodeParser.java | 34 +- 144 files changed, 12712 insertions(+), 804 deletions(-) create mode 100644 Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java create mode 100644 Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java create mode 100644 Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java create mode 100644 Ghidra/Debug/Debugger/ghidra_scripts/StandAloneEmuExampleScript.java create mode 100644 Ghidra/Debug/Debugger/ghidra_scripts/StandAloneStructuredSleighScript.java create mode 100644 Ghidra/Debug/Debugger/ghidra_scripts/StandAloneSyscallEmuExampleScript.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeEmulator.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeEmulator.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/AbstractEmuLinuxSyscallUseropLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuIOException.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuInvalidSystemCallException.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSystemException.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFileSystem.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractStreamEmuUnixFileHandle.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/BytesEmuUnixFileSystem.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixException.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFile.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileDescriptor.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileStat.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileSystem.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixUser.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/IOStreamEmuUnixFileHandle.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorState.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedSleighUseropLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ComposedPcodeUseropLibrary.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ComposedSleighUseropLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExpression.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighExpression.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java delete mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighUseropLibrary.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/AbstractStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ArithBinExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/AssignStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BinExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BlockStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BreakStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/CallExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/CmpExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ConditionalStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ContinueStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DeclStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DefaultUseropDecl.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DefaultVar.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DerefExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/Expr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/FieldExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ForStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/IfStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/IndexExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/InvExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LValInternal.java rename Ghidra/Debug/{Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesPcodeExecutorStateMixin.java => ProposedUtils/src/main/java/ghidra/pcode/struct/LangVar.java} (72%) create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralFloatExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralLongExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LocalVar.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LoopStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LoopTruncateStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/NotExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RValInternal.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RawExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RawStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ResultStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ReturnStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RoutineStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/StructuredSleigh.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/UnExpr.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/VoidExprStmt.java create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/WhileStmt.java rename Ghidra/Debug/{Framework-TraceModeling/src/main/java/ghidra/trace/util => ProposedUtils/src/main/java/ghidra/program/model/mem}/MemBufferAdapter.java (98%) create mode 100644 Ghidra/Debug/ProposedUtils/src/main/java/utilities/util/AnnotationUtilities.java create mode 100644 Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibraryTest.java create mode 100644 Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibraryTest.java create mode 100644 Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/EmuAmd64SyscallUseropLibraryTest.java create mode 100644 Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java create mode 100644 Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java new file mode 100644 index 0000000000..8ff1d657f2 --- /dev/null +++ b/Ghidra/Debug/Debugger/ghidra_scripts/DebuggerEmuExampleScript.java @@ -0,0 +1,211 @@ +/* ### + * 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. + */ +//An example emulation script that integrates well with the Debgger UI. +//It provides the set-up code and then demonstrates some use cases. +//It should work with any x64 program, but some snippets may require specific conditions. +//It should be easily ported to other platforms just by adjusting register names. +//@author +//@category Emulation +//@keybinding +//@menupath +//@toolbar + +import java.nio.charset.Charset; +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.ProgramEmulationUtils; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.script.GhidraScript; +import ghidra.app.services.DebuggerTraceManagerService; +import ghidra.app.services.ProgramManager; +import ghidra.framework.plugintool.PluginTool; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.trace.TraceSleighUtils; +import ghidra.pcode.utils.Utils; +import ghidra.program.database.ProgramDB; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.listing.InstructionIterator; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.trace.model.Trace; +import ghidra.trace.model.thread.TraceThread; +import ghidra.trace.model.time.TraceSnapshot; +import ghidra.trace.model.time.TraceTimeManager; +import ghidra.util.database.UndoableTransaction; + +public class DebuggerEmuExampleScript extends GhidraScript { + private final static Charset UTF8 = Charset.forName("utf8"); + + @Override + protected void run() throws Exception { + /* + * First, get all the services and stuff: + */ + PluginTool tool = state.getTool(); + ProgramManager programManager = tool.getService(ProgramManager.class); + DebuggerTraceManagerService traceManager = + tool.getService(DebuggerTraceManagerService.class); + SleighLanguage language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default")); + + /* + * I'll generate a new program, because I don't want to require the user to pick something + * specific. + */ + Address entry; + Address injectHere; + Program program = null; + try { + program = + new ProgramDB("emu_example", language, language.getDefaultCompilerSpec(), this); + // Save the program into the project so it has a URL for the trace's static mapping + tool.getProject() + .getProjectData() + .getRootFolder() + .createFile("emu_example", program, monitor); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) { + AddressSpace space = program.getAddressFactory().getDefaultAddressSpace(); + entry = space.getAddress(0x00400000); + Address dataEntry = space.getAddress(0x00600000); + Memory memory = program.getMemory(); + memory.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Assembler asm = Assemblers.getAssembler(program); + InstructionIterator ii = asm.assemble(entry, + "MOV RCX, 0x" + dataEntry, + "MOV RAX, 1", + "SYSCALL", + "MOV RAX, 2", + "SYSCALL"); + ii.next(); // drop MOV RCX + injectHere = ii.next().getAddress(); + memory.createInitializedBlock(".data", dataEntry, 0x1000, (byte) 0, monitor, false); + memory.setBytes(dataEntry, "Hello, World!\n".getBytes(UTF8)); + } + program.save("Init", monitor); + // Display the program in the UI + programManager.openProgram(program); + } + finally { + if (program != null) { + program.release(this); + } + } + + /* + * Now, load the program into a trace. This doesn't copy any bytes, it just sets up a static + * mapping. The emulator will know how to read through to the mapped program. We use a + * utility, which is the same used by the "Emulate Program" action in the UI. It will load + * the program, allocate a stack, and initialize the first thread to the given entry. + */ + Trace trace = null; + try { + trace = ProgramEmulationUtils.launchEmulationTrace(program, entry, this); + // Display the trace in the UI + traceManager.openTrace(trace); + traceManager.activateTrace(trace); + } + finally { + if (trace != null) { + trace.release(this); + } + } + // Get the initial thread + TraceThread traceThread = trace.getThreadManager().getAllThreads().iterator().next(); + traceManager.activateThread(traceThread); + + /* + * Instead of using the UI's emulator, this script will create its own with a custom + * 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) { + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this); + } + }; + // Conventionally, emulator threads are named after their trace thread's path. + PcodeThread thread = emulator.getThread(traceThread.getPath(), true); + + /* + * Inject a call to our custom print userop. Otherwise, the language itself will never + * invoke it. + */ + emulator.inject(injectHere, List.of( + "print_utf8(RCX);", + "emu_exec_decoded();")); + + /* + * Run the experiment: This should interrupt on the second SYSCALL, because any value other + * than 1 calls emu_swi. + * + * For demonstration, we'll record a trace snapshot for every step of emulation. This is not + * ordinarily recommended except for very small experiments. A more reasonable approach in + * practice may be to snapshot on specific breakpoints. + */ + TraceTimeManager time = trace.getTimeManager(); + TraceSnapshot snapshot = time.getSnapshot(0, true); + try (UndoableTransaction tid = UndoableTransaction.start(trace, "Emulate", true)) { + for (int i = 0; i < 10; i++) { + println("Executing: " + thread.getCounter()); + thread.stepInstruction(); + snapshot = + time.createSnapshot("Stepped to " + thread.getCounter()); + emulator.writeDown(trace, snapshot.getKey(), 0, false); + } + printerr("We should not have completed 10 steps!"); + } + catch (InterruptPcodeExecutionException e) { + println("Terminated via interrupt. Good."); + } + // Display the final snapshot in the UI + traceManager.activateSnap(snapshot.getKey()); + + /* + * Inspect the machine. You can always do this by accessing the state directly, but for + * anything other than simple variables, you may find compiling an expression more + * convenient. + * + * This works the same as in the stand-alone case. + */ + println("RCX = " + + Utils.bytesToLong(thread.getState().getVar(language.getRegister("RCX")), 8, + language.isBigEndian())); + + println("RCX = " + Utils.bytesToLong( + SleighProgramCompiler.compileExpression(language, "RCX").evaluate(thread.getExecutor()), + 8, language.isBigEndian())); + + println("RCX+4 = " + + Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RCX+4") + .evaluate(thread.getExecutor()), + 8, language.isBigEndian())); + + /* + * To evaluate a Sleigh expression against the trace: The result is the same as evaluating + * directly against the emulator, but these work with any trace, no matter the original data + * source (live target, emulated, imported, etc.) It's also built into utilities, making it + * easier to use. + */ + println("RCX+4 (trace) = " + + TraceSleighUtils.evaluate("RCX+4", trace, snapshot.getKey(), traceThread, 0)); + } +} diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java b/Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java new file mode 100644 index 0000000000..fb83ec0de4 --- /dev/null +++ b/Ghidra/Debug/Debugger/ghidra_scripts/DemoPcodeUseropLibrary.java @@ -0,0 +1,131 @@ +/* ### + * 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. + */ +import java.nio.charset.Charset; +import java.util.List; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.script.GhidraScript; +import ghidra.pcode.exec.*; +import ghidra.pcode.struct.StructuredSleigh; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.CompilerSpec; +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. + * + *

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

+ * Methods in this class (not including those in its nested classes) are implemented as Java + * callbacks. + */ +public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary { + private final static Charset UTF8 = Charset.forName("utf8"); + + private final SleighLanguage language; + private final GhidraScript script; + private final AddressSpace space; + + public DemoPcodeUseropLibrary(SleighLanguage language, GhidraScript script) { + this.language = language; + this.script = script; + this.space = language.getDefaultSpace(); + + new DemoStructuredPart(language.getDefaultCompilerSpec()).generate(ops); + } + + /** + * Treats the input as an offset to a C-style string and prints it to the console + * + *

+ * Because we want to dereference start, we will need access to the emulator's state, so we + * employ the {@link OpState} annotation. {@code start} takes the one input we expect. Because + * its type is the value type rather than {@link Varnode}, we will get the input's value. + * Similarly, we can just return the resulting value, and the emulator will place that into the + * output variable for us. + * + * @param state the calling thread's state + * @param start the offset of the first character + * @return the length of the string in bytes + */ + @PcodeUserop + public byte[] print_utf8(@OpState PcodeExecutorStatePiece state, + byte[] start) { + long offset = Utils.bytesToLong(start, start.length, language.isBigEndian()); + long end = offset; + while (state.getVar(space, end, 1, true)[0] != 0) { + end++; + } + if (end == offset) { + script.println(""); + return Utils.longToBytes(0, Long.BYTES, language.isBigEndian()); + } + byte[] bytes = state.getVar(space, offset, (int) (end - offset), true); + String str = new String(bytes, UTF8); + script.println(str); + return Utils.longToBytes(end - offset, Long.BYTES, language.isBigEndian()); + } + + /** + * Methods in this class are implemented using p-code compiled from Structured Sleigh + */ + public class DemoStructuredPart extends StructuredSleigh { + final Var RAX = lang("RAX", type("long")); + final Var RCX = lang("RAX", type("byte *")); + final UseropDecl emu_swi = userop(type("void"), "emu_swi", List.of()); + + protected DemoStructuredPart(CompilerSpec cs) { + super(cs); + } + + /** + * Not really a syscall dispatcher + * + *

+ * In cases where the userop expects parameters, you would annotate them with {@link Param} + * and use them just like other {@link Var}s. See the javadocs. + * + *

+ * This is just a cheesy demo: If RAX is 1, then this method computes the number of bytes in + * the C-style string pointed to by RCX and stores the result in RAX. Otherwise, interrupt + * the emulator. See {@link DemoSyscallLibrary} for actual system call simulation. + */ + @StructuredUserop + public void syscall() { + _if(RAX.eq(1), () -> { + Var i = local("i", RCX); + _while(i.deref().neq(0), () -> { + i.inc(); + }); + RAX.set(i.subi(RAX)); + })._else(() -> { + emu_swi.call(); + }); + } + } +} diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java b/Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java new file mode 100644 index 0000000000..9d2ef94f41 --- /dev/null +++ b/Ghidra/Debug/Debugger/ghidra_scripts/DemoSyscallLibrary.java @@ -0,0 +1,211 @@ +/* ### + * 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. + */ +import java.nio.charset.Charset; +import java.util.Collection; + +import ghidra.app.script.GhidraScript; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.linux.EmuLinuxAmd64SyscallUseropLibrary; +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.struct.StructuredSleigh; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Program; + +/** + * A userop library that includes system call simulation + * + *

+ * Such a library needs to implement {@link EmuSyscallLibrary}. Here we extend + * {@link AnnotatedEmuSyscallUseropLibrary}, which allows us to implement it using annotated + * methods. {@link EmuSyscallLibrary#syscall(PcodeExecutor, PcodeUseropLibrary)} is the system call + * dispatcher, and it requires that each system call implement {@link EmuSyscallDefinition}. System + * 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. + * + *

+ * Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in + * {@link EmuLinuxX86SyscallUseropLibrary} and {@link EmuLinuxAmd64SyscallUseropLibrary}, + * respectively. The type hierarchy is designed to facilitate the implementation of related systems + * without (too much) code duplication. Because they derive from the annotation-based + * implementations, you can add missing system calls by extending one and adding annotated methods + * as needed. + * + *

+ * For demonstration, this will implement one from scratch for no particular operating system, but + * it will borrow many conventions from linux-amd64. + */ +public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary { + private final static Charset UTF8 = Charset.forName("utf8"); + + // Implement all the required plumbing first: + + /** + * An exception type for "user errors." These errors should be communicated back to the target + * program rather than causing the emulator to interrupt. This is a bare minimum implementation. + * In practice more information should be communicated internally, in case things go further + * wrong. Also, a hierarchy of exceptions may be appropriate. + */ + static class UserError extends PcodeExecutionException { + private final int errno; + + public UserError(int errno) { + super("errno: " + errno); + this.errno = errno; + } + } + + private final Register regRAX; + private final GhidraScript script; + + /** + * 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 + * {@link #handleError(PcodeExecutor, PcodeExecutionException)}. + * + * @param machine the emulator + * @param program the program being emulated + */ + public DemoSyscallLibrary(PcodeMachine machine, Program program, GhidraScript script) { + super(machine, program); + this.script = script; + this.regRAX = machine.getLanguage().getRegister("RAX"); + if (regRAX == null) { + throw new AssertionError("This library only works on x64 targets"); + } + } + + /** + * The dispatcher doesn't know where the system call number is stored. It relies on this method + * to read that number from the state. Here we'll assume the target is x64 and RAX contains the + * syscall number. + */ + @Override + public long readSyscallNumber(PcodeExecutorStatePiece state) { + return Utils.bytesToLong(state.getVar(regRAX), regRAX.getNumBytes(), + machine.getLanguage().isBigEndian()); + } + + /** + * If the error is a user error, put the errno into the machine as expected by the target + * program. Here we negate the errno and put it into RAX. If it's not a user error, we return + * false letting the dispatcher know it should interrupt the emulator. + */ + @Override + public boolean handleError(PcodeExecutor executor, PcodeExecutionException err) { + if (err instanceof UserError) { + executor.getState() + .setVar(regRAX, executor.getArithmetic() + .fromConst(-((UserError) err).errno, regRAX.getNumBytes())); + return true; + } + return false; + } + + /** + * Support for Structured Sleigh is built-in. To enable it, override this method and instantiate + * the appropriate (usually nested) class. + */ + @Override + protected StructuredPart newStructuredPart() { + return new DemoStructuredPart(); + } + + @Override + protected Collection getAdditionalArchives() { + // Add platform-specific data type archives, if needed + return super.getAdditionalArchives(); + } + + // Now, implement some system calls! + + // First, a Java callback example + + /** + * Write a buffer of utf-8 characters to the console + * + *

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

+ * For demonstration, we will export this as a system call, though that is not required for + * {@link DemoStructuredPart#demo_console(StructuredSleigh.Var)} to invoke it. It does need to + * be a userop, but it doesn't need to be a syscall. + * + * @param str a pointer to the start of the buffer + * @param end a pointer to the end (exclusive) of the buffer + */ + @PcodeUserop + @EmuSyscall("write") + public void demo_write(byte[] str, byte[] end) { + AddressSpace space = machine.getLanguage().getDefaultSpace(); + /** + * Because we have concrete {@code byte[]}, we could use Utils.bytesToLong, but for + * demonstration, here's how it can be done if we extended + * {@link AnnotatedEmuSyscallUseropLibrary}{@code } instead. If the value cannot be made + * concrete, an exception will be thrown. For abstract types, it's a good idea to save a + * 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(); + + byte[] stringBytes = + machine.getSharedState().getVar(space, strLong, (int) (endLong - strLong), true); + String string = new String(stringBytes, UTF8); + script.println(string); + } + + // 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()}. + */ + public class DemoStructuredPart extends StructuredPart { + UseropDecl write = userop(type("void"), "demo_write", types("char *", "char *")); + + /** + * Write a C-style string to the console + * + * @param str the null-terminated utf-8 string + */ + @StructuredUserop + @EmuSyscall("console") + public void demo_console(@Param(type = "char *") Var str) { + // Measure the string's length and then invoke write + Var end = local("end", type("char *")); + _for(end.set(str), end.deref().neq(0), end.inc(), () -> { + }); + write.call(str, end); + } + } +} diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneEmuExampleScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneEmuExampleScript.java new file mode 100644 index 0000000000..1c56e080eb --- /dev/null +++ b/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneEmuExampleScript.java @@ -0,0 +1,179 @@ +/* ### + * 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. + */ +//An example emulation script that uses a stand-alone emulator. +//It provides the set-up code and then demonstrates some use cases. +//@author +//@category Emulation +//@keybinding +//@menupath +//@toolbar + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; + +import ghidra.app.plugin.assembler.*; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.script.GhidraScript; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.exec.*; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.LanguageID; + +public class StandAloneEmuExampleScript extends GhidraScript { + private final static Charset UTF8 = Charset.forName("utf8"); + private SleighLanguage language; + private PcodeEmulator emulator; + + @Override + protected void run() throws Exception { + /* + * Create an emulator and start a thread + */ + language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default")); + emulator = new PcodeEmulator(language) { + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return new DemoPcodeUseropLibrary(language, StandAloneEmuExampleScript.this); + } + + // Uncomment this to see instructions printed as they are decoded + /* + protected BytesPcodeThread createThread(String name) { + return new BytesPcodeThread(name, this) { + @Override + protected SleighInstructionDecoder createInstructionDecoder( + PcodeExecutorState sharedState) { + return new SleighInstructionDecoder(language, sharedState) { + @Override + public Instruction decodeInstruction(Address address, + RegisterValue context) { + Instruction instruction = super.decodeInstruction(address, context); + println("Decoded " + address + ": " + instruction); + return instruction; + } + }; + } + }; + } + */ + }; + PcodeThread thread = emulator.newThread(); + // The emulator composes the full library for each thread + PcodeUseropLibrary library = thread.getUseropLibrary(); + AddressSpace dyn = language.getDefaultSpace(); + + /* + * Assemble a little test program and write it into the emulator + * + * We're not really going to implement system calls here. We're just using it to demonstrate + * the implementation of a language-defined userop. + */ + Address entry = dyn.getAddress(0x00400000); + Assembler asm = Assemblers.getAssembler(language); + CodeBuffer buffer = new CodeBuffer(asm, entry); + buffer.assemble("MOV RCX, 0xdeadbeef"); + Address injectHere = buffer.getNext(); + buffer.assemble("MOV RAX, 1"); + buffer.assemble("SYSCALL"); + buffer.assemble("MOV RAX, 2"); // Induce the interrupt we need to terminate + buffer.assemble("SYSCALL"); + byte[] code = buffer.getBytes(); + emulator.getSharedState().setVar(dyn, entry.getOffset(), code.length, true, code); + + /* + * Initialize other parts of the emulator and thread state. Note the use of the L suffix on + * 0xdeadbeefL, because Java with sign extend the (negative) int to a long otherwise. + */ + byte[] hw = "Hello, World!\n".getBytes(UTF8); + emulator.getSharedState().setVar(dyn, 0xdeadbeefL, hw.length, true, hw); + PcodeProgram init = SleighProgramCompiler.compileProgram(language, "init", List.of( + "RIP = 0x" + entry + ";", + "RSP = 0x00001000;"), + library); + thread.getExecutor().execute(init, library); + thread.overrideContextWithDefault(); + thread.reInitialize(); + + /* + * Inject a call to our custom print userop. Otherwise, the language itself will never + * invoke it. + */ + emulator.inject(injectHere, List.of( + "print_utf8(RCX);", + "emu_exec_decoded();")); + + /* + * Run the experiment: This should interrupt on the second SYSCALL, because any value other + * than 1 calls emu_swi. + */ + try { + thread.stepInstruction(10); + printerr("We should not have completed 10 steps!"); + } + catch (InterruptPcodeExecutionException e) { + println("Terminated via interrupt. Good."); + } + + /* + * Inspect the machine. You can always do this by accessing the state directly, but for + * anything other than simple variables, you may find compiling an expression more + * convenient. + */ + println("RCX = " + + Utils.bytesToLong(thread.getState().getVar(language.getRegister("RCX")), 8, + language.isBigEndian())); + + println("RCX = " + Utils.bytesToLong( + SleighProgramCompiler.compileExpression(language, "RCX").evaluate(thread.getExecutor()), + 8, language.isBigEndian())); + + println("RCX+4 = " + + Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RCX+4") + .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/ghidra_scripts/StandAloneStructuredSleighScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneStructuredSleighScript.java new file mode 100644 index 0000000000..c0f7963622 --- /dev/null +++ b/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneStructuredSleighScript.java @@ -0,0 +1,112 @@ +/* ### + * 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. + */ +//An example script for using Structured Sleigh stand alone +//@author +//@category Sleigh +//@keybinding +//@menupath +//@toolbar + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.util.Map; +import java.util.stream.Collectors; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.script.GhidraScript; +import ghidra.pcode.exec.SleighPcodeUseropDefinition; +import ghidra.pcode.struct.StructuredSleigh; +import ghidra.program.model.lang.LanguageID; + +public class StandAloneStructuredSleighScript extends GhidraScript { + private SleighLanguage language; + + /** + * This exists mostly so we can access the methods of anonymous nested classes deriving from + * this one. The "compiler" will need to be able to access the methods, and that's not + * ordinarily allowed since anonymous classes are implicitly "private." Conveniently, it also + * allows us to implement a default constructor, so that can be elided where used, too. + */ + class LookupStructuredSleigh extends StructuredSleigh { + protected LookupStructuredSleigh() { + super(language.getDefaultCompilerSpec()); + } + + @Override + protected Lookup getMethodLookup() { + return MethodHandles.lookup(); + } + } + + @Override + protected void run() throws Exception { + /* + * If you have a target language in mind, perhaps use it, but DATA provides a minimal + * context + */ + language = (SleighLanguage) getLanguage(new LanguageID("DATA:BE:64:default")); + + Map> ops = new LookupStructuredSleigh() { + /** + * Add two in-memory vectors of 16 longs and store the result in memory + * + * @param d pointer to the destination vector + * @param s1 pointer to the first operand vector + * @param s2 pointer to the second operand vector + */ + @StructuredUserop + public void vector_add( + @Param(name = "d", type = "int *") Var d, + @Param(name = "s1", type = "int *") Var s1, + @Param(name = "s2", type = "int *") Var s2) { + // Use Java's "for" to generate an unrolled loop + // We could choose a Sleigh loop, instead. Consider both emu and analysis tradeoffs + for (int i = 0; i < 16; i++) { + // This will generate +0 on the first elements, but whatever + d.index(i).deref().set(s1.index(i).deref().addi(s2.index(i).deref())); + } + } + + @StructuredUserop + public void memcpy( + @Param(name = "d", type = "void *") Var d, + @Param(name = "s", type = "void *") Var s, + @Param(name = "n", type = "long") Var n) { // size_t is not built-in + Var i = local("i", type("long")); + // Note that these 2 casts don't generate Sleigh statements + Var db = d.cast(type("byte *")); + Var sb = s.cast(type("byte *")); + // Must use a Sleigh loop here + _for(i.set(0), i.ltiu(n), i.inc(), () -> { + db.index(i).deref().set(sb.index(i).deref()); + }); + } + }.generate(); + + /* + * Now, dump the generated Sleigh source + */ + for (SleighPcodeUseropDefinition userop : ops.values()) { + print(userop.getName() + "("); + print(userop.getInputs().stream().collect(Collectors.joining(","))); + print(") {\n"); + for (String line : userop.getLines()) { + print(line); + } + print("}\n\n"); + } + } +} diff --git a/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneSyscallEmuExampleScript.java b/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneSyscallEmuExampleScript.java new file mode 100644 index 0000000000..0236347e74 --- /dev/null +++ b/Ghidra/Debug/Debugger/ghidra_scripts/StandAloneSyscallEmuExampleScript.java @@ -0,0 +1,233 @@ +/* ### + * 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. + */ +//An example emulation script that uses a stand-alone emulator with syscalls. +//It provides the set-up code and then demonstrates some use cases. +//@author +//@category Emulation +//@keybinding +//@menupath +//@toolbar + +import java.nio.charset.Charset; +import java.util.List; + +import ghidra.app.plugin.assembler.Assembler; +import ghidra.app.plugin.assembler.Assemblers; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.app.script.GhidraScript; +import ghidra.pcode.emu.PcodeEmulator; +import ghidra.pcode.emu.PcodeThread; +import ghidra.pcode.emu.sys.EmuInvalidSystemCallException; +import ghidra.pcode.emu.sys.EmuSyscallLibrary; +import ghidra.pcode.exec.*; +import ghidra.pcode.utils.Utils; +import ghidra.program.database.ProgramDB; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.data.PointerDataType; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.database.UndoableTransaction; + +public class StandAloneSyscallEmuExampleScript extends GhidraScript { + private final static Charset UTF8 = Charset.forName("utf8"); + + Program program = null; + + @Override + protected void run() throws Exception { + /* + * First, get all the services and stuff: + */ + SleighLanguage language = (SleighLanguage) getLanguage(new LanguageID("x86:LE:64:default")); + + /* + * I'll generate a new program, because I don't want to require the user to pick something + * specific. It won't be displayed, though, so we'll just release it when we're done. + */ + Address entry; + try { + /* + * "gcc" is the name of the compiler spec, but we're really interested in the Linux + * syscall calling conventions. + */ + program = + new ProgramDB("syscall_example", language, + language.getCompilerSpecByID(new CompilerSpecID("gcc")), this); + try (UndoableTransaction tid = UndoableTransaction.start(program, "Init", true)) { + AddressSpace space = program.getAddressFactory().getDefaultAddressSpace(); + entry = space.getAddress(0x00400000); + Address dataEntry = space.getAddress(0x00600000); + Memory memory = program.getMemory(); + memory.createInitializedBlock(".text", entry, 0x1000, (byte) 0, monitor, false); + Assembler asm = Assemblers.getAssembler(program); + asm.assemble(entry, + "MOV RDI, 0x" + dataEntry, + "MOV RAX, 1", + "SYSCALL", + "MOV RAX, 20", + "SYSCALL"); + memory.createInitializedBlock(".data", dataEntry, 0x1000, (byte) 0, monitor, false); + memory.setBytes(dataEntry, "Hello, World!\n".getBytes(UTF8)); + + /* + * Because "pointer" is a built-in type, and the emulator does not modify the + * program, we must ensure it has been resolved on the program's data type manager. + */ + program.getDataTypeManager() + .resolve(PointerDataType.dataType, DataTypeConflictHandler.DEFAULT_HANDLER); + + /* + * We must also populate the system call numbering map. Ordinarily, this would be done + * using the system call analyzer or another script. Here, we'll just fake it out. + */ + AddressSpace other = + program.getAddressFactory().getAddressSpace(SpaceNames.OTHER_SPACE_NAME); + MemoryBlock blockSyscall = program.getMemory() + .createUninitializedBlock(EmuSyscallLibrary.SYSCALL_SPACE_NAME, + other.getAddress(0), 0x1000, true); + blockSyscall.setPermissions(true, false, true); + + AddressSpace syscall = program.getAddressFactory() + .getAddressSpace(EmuSyscallLibrary.SYSCALL_SPACE_NAME); + /* + * The system call names must match those from the EmuSyscall annotations in the + * system call library, in our case from DemoSyscallLibrary. Because the x64 + * compiler specs define a "syscall" convention, we'll apply it. The syscall + * dispatcher will use that convention to fetch the parameters out of the machine + * state, pass them into the system call defintion, and store the result back into + * the machine. + */ + // Map system call 0 to "write" + program.getFunctionManager() + .createFunction("write", syscall.getAddress(0), + new AddressSet(syscall.getAddress(0)), SourceType.USER_DEFINED) + .setCallingConvention(EmuSyscallLibrary.SYSCALL_CONVENTION_NAME); + // Map system call 1 to "console" + program.getFunctionManager() + .createFunction("console", syscall.getAddress(1), + new AddressSet(syscall.getAddress(1)), SourceType.USER_DEFINED) + .setCallingConvention(EmuSyscallLibrary.SYSCALL_CONVENTION_NAME); + } + + /* + * Create an emulator and start a thread + */ + PcodeEmulator emulator = new PcodeEmulator(language) { + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return new DemoSyscallLibrary(this, program, + StandAloneSyscallEmuExampleScript.this); + } + + // Uncomment this to see instructions printed as they are decoded + /* + @Override + protected BytesPcodeThread createThread(String name) { + return new BytesPcodeThread(name, this) { + @Override + protected SleighInstructionDecoder createInstructionDecoder( + PcodeExecutorState sharedState) { + return new SleighInstructionDecoder(language, sharedState) { + @Override + public Instruction decodeInstruction(Address address, + RegisterValue context) { + Instruction instruction = super.decodeInstruction(address, context); + println("Decoded " + address + ": " + instruction); + return instruction; + } + }; + } + }; + } + */ + }; + PcodeThread thread = emulator.newThread(); + // The emulator composes the full library for each thread + PcodeUseropLibrary library = thread.getUseropLibrary(); + + /* + * The library has a reference to the program and uses it to derive types and the system + * call numbering. However, the emulator itself does not have access to the program. If we + * followed the pattern in DebuggerEmuExampleScript, the emulator would have its state bound + * (indirectly) to the program. We'll need to copy the bytes in. Because we created blocks + * that were 0x1000 bytes in size, we can be fast and loose with our buffer. Ordinarily, you + * may want to copy in chunks rather than taking entire memory blocks at a time. + */ + byte[] data = new byte[0x1000]; + for (MemoryBlock block : program.getMemory().getBlocks()) { + if (!block.isInitialized()) { + continue; // Skip the syscall/OTHER block + } + Address addr = block.getStart(); + block.getBytes(addr, data); + emulator.getSharedState() + .setVar(addr.getAddressSpace(), addr.getOffset(), data.length, true, data); + } + + /* + * Initialize the thread + */ + PcodeProgram init = SleighProgramCompiler.compileProgram(language, "init", List.of( + "RIP = 0x" + entry + ";", + "RSP = 0x00001000;"), + library); + thread.getExecutor().execute(init, library); + thread.overrideContextWithDefault(); + thread.reInitialize(); + + /* + * Run the experiment: This should interrupt on the second SYSCALL, because we didn't + * provide a system call name in OTHER space for 20. + */ + try { + thread.stepInstruction(10); + printerr("We should not have completed 10 steps!"); + } + catch (EmuInvalidSystemCallException e) { + println("Terminated via invalid syscall. Good."); + } + + /* + * Inspect the machine. You can always do this by accessing the state directly, but for + * anything other than simple variables, you may find compiling an expression more + * convenient. + */ + println("RDI = " + + Utils.bytesToLong(thread.getState().getVar(language.getRegister("RDI")), 8, + language.isBigEndian())); + + println("RDI = " + Utils.bytesToLong( + SleighProgramCompiler.compileExpression(language, "RDI") + .evaluate(thread.getExecutor()), + 8, language.isBigEndian())); + + println("RDI+4 = " + + Utils.bytesToLong(SleighProgramCompiler.compileExpression(language, "RDI+4") + .evaluate(thread.getExecutor()), + 8, language.isBigEndian())); + + } + finally { + if (program != null) { + program.release(this); + } + } + } +} 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 b0bce6a90a..f77fa44de7 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 @@ -88,11 +88,11 @@ public abstract class DebuggerGoToTrait { if (space == null) { throw new IllegalArgumentException("No such address space: " + spaceName); } - SleighExpression expr = SleighProgramCompiler.compileExpression(slang, expression); + PcodeExpression expr = SleighProgramCompiler.compileExpression(slang, expression); return goToSleigh(space, expr); } - public CompletableFuture goToSleigh(AddressSpace space, SleighExpression expression) { + public CompletableFuture goToSleigh(AddressSpace space, PcodeExpression expression) { AsyncPcodeExecutor executor = TracePcodeUtils.executorForCoordinates(current); CompletableFuture result = expression.evaluate(executor); return result.thenApply(offset -> { 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 54e9720a7c..31c17531e5 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 @@ -58,7 +58,7 @@ public class WatchRow { private String typePath; private DataType dataType; - private SleighExpression compiled; + private PcodeExpression compiled; private TraceMemoryState state; private Address address; private AddressSet reads; @@ -208,7 +208,7 @@ public class WatchRow { @Override public PcodeFrame execute(PcodeProgram program, - SleighUseropLibrary> library) { + PcodeUseropLibrary> library) { depsState.reset(); return super.execute(program, library); } 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/AbstractReadsTargetPcodeExecutorState.java index dbe9adcda2..a533a9fd41 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/AbstractReadsTargetPcodeExecutorState.java @@ -36,8 +36,8 @@ public abstract class AbstractReadsTargetPcodeExecutorState abstract class AbstractReadsTargetCachedSpace extends CachedSpace { public AbstractReadsTargetCachedSpace(Language language, AddressSpace space, - TraceMemorySpace source, long snap) { - super(language, space, source, snap); + TraceMemorySpace backing, long snap) { + super(language, space, backing, snap); } protected abstract void fillUninitialized(AddressSet uninitialized); @@ -47,15 +47,15 @@ public abstract class AbstractReadsTargetPcodeExecutorState } protected AddressSet computeUnknown(AddressSet uninitialized) { - return uninitialized.subtract(source.getAddressesWithState(snap, uninitialized, + return uninitialized.subtract(backing.getAddressesWithState(snap, uninitialized, s -> s != null && s != TraceMemoryState.UNKNOWN)); } @Override public byte[] read(long offset, int size) { - if (source != null) { + if (backing != null) { AddressSet uninitialized = - addrSet(cache.getUninitialized(offset, offset + size - 1)); + addrSet(bytes.getUninitialized(offset, offset + size - 1)); if (uninitialized.isEmpty()) { return super.read(offset, size); } @@ -63,7 +63,7 @@ public abstract class AbstractReadsTargetPcodeExecutorState fillUninitialized(uninitialized); AddressSet unknown = - computeUnknown(addrSet(cache.getUninitialized(offset, offset + size - 1))); + computeUnknown(addrSet(bytes.getUninitialized(offset, offset + size - 1))); if (!unknown.isEmpty()) { warnUnknown(unknown); } 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 04f080fcef..c8aee6c97d 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 @@ -40,8 +40,8 @@ public class ReadsTargetMemoryPcodeExecutorState protected class ReadsTargetMemoryCachedSpace extends AbstractReadsTargetCachedSpace { public ReadsTargetMemoryCachedSpace(Language language, AddressSpace space, - TraceMemorySpace source, long snap) { - super(language, space, source, snap); + TraceMemorySpace backing, long snap) { + super(language, space, backing, snap); } @Override @@ -108,7 +108,7 @@ public class ReadsTargetMemoryPcodeExecutorState " bytes"); } // write(lower - shift, data, 0 ,read); - cache.putData(lower - shift, data, 0, read); + bytes.putData(lower - shift, data, 0, read); } catch (MemoryAccessException | AddressOutOfBoundsException e) { throw new AssertionError(e); 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 7bf23aa13b..1261552b27 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 @@ -28,7 +28,7 @@ import ghidra.program.model.pcode.Varnode; * An executor which can perform (some of) its work asynchronously * *

- * Note that a future returned from, e.g., {@link #executeAsync(SleighProgram, SleighUseropLibrary)} + * Note that a future returned from, e.g., {@link #executeAsync(SleighProgram, PcodeUseropLibrary)} * may complete before the computation has actually been performed. They complete when all of the * operations have been scheduled, and the last future has been written into the state. (This * typically happens when any branch conditions have completed). Instead, a caller should read from @@ -46,7 +46,7 @@ public class AsyncPcodeExecutor extends PcodeExecutor> { } public CompletableFuture stepOpAsync(PcodeOp op, PcodeFrame frame, - SleighUseropLibrary> library) { + PcodeUseropLibrary> library) { if (op.getOpcode() == PcodeOp.CBRANCH) { return executeConditionalBranchAsync(op, frame); } @@ -55,7 +55,7 @@ public class AsyncPcodeExecutor extends PcodeExecutor> { } public CompletableFuture stepAsync(PcodeFrame frame, - SleighUseropLibrary> library) { + PcodeUseropLibrary> library) { try { return stepOpAsync(frame.nextOp(), frame, library); } @@ -80,12 +80,12 @@ public class AsyncPcodeExecutor extends PcodeExecutor> { } public CompletableFuture executeAsync(PcodeProgram program, - SleighUseropLibrary> library) { + PcodeUseropLibrary> library) { return executeAsync(program.code, program.useropNames, library); } protected CompletableFuture executeAsyncLoop(PcodeFrame frame, - SleighUseropLibrary> library) { + PcodeUseropLibrary> library) { if (frame.isFinished()) { return AsyncUtils.NIL; } @@ -94,7 +94,7 @@ public class AsyncPcodeExecutor extends PcodeExecutor> { } public CompletableFuture executeAsync(List code, - Map useropNames, SleighUseropLibrary> library) { + Map useropNames, PcodeUseropLibrary> library) { PcodeFrame frame = new PcodeFrame(language, code, useropNames); return executeAsyncLoop(frame, library); } diff --git a/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java b/Ghidra/Debug/Debugger/src/main/java/ghidra/pcode/exec/AsyncWrappedPcodeArithmetic.java index 097df3e6a6..03750efb7e 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 @@ -83,4 +83,9 @@ public class AsyncWrappedPcodeArithmetic implements PcodeArithmetic sizeOf(CompletableFuture value) { + return value.thenApply(v -> arithmetic.sizeOf(v)); + } } 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 d645853ba4..d90195a8e6 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 @@ -169,7 +169,7 @@ public class DebuggerPcodeStepperProviderTest extends AbstractGhidraHeadedDebugg protected List format(List sleigh) { SleighLanguage language = (SleighLanguage) getToyBE64Language(); PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", sleigh, - SleighUseropLibrary.nil()); + PcodeUseropLibrary.nil()); PcodeExecutor executor = new PcodeExecutor<>(language, PcodeArithmetic.BYTES_BE, null); PcodeFrame frame = executor.begin(prog); 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 abd69d2592..cab0c1166d 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 @@ -55,7 +55,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge Trace trace = recorder.getTrace(); SleighLanguage language = (SleighLanguage) trace.getBaseLanguage(); - SleighExpression expr = SleighProgramCompiler + PcodeExpression expr = SleighProgramCompiler .compileExpression(language, "r0 + r1"); Register r0 = language.getRegister("r0"); @@ -99,7 +99,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge SleighLanguage language = (SleighLanguage) trace.getBaseLanguage(); PcodeProgram prog = SleighProgramCompiler.compileProgram(language, "test", - List.of("r2 = r0 + r1;"), SleighUseropLibrary.NIL); + List.of("r2 = r0 + r1;"), PcodeUseropLibrary.NIL); Register r0 = language.getRegister("r0"); Register r1 = language.getRegister("r1"); @@ -119,7 +119,7 @@ public class TraceRecorderAsyncPcodeExecTest extends AbstractGhidraHeadedDebugge AsyncPcodeExecutor executor = new AsyncPcodeExecutor<>( language, AsyncWrappedPcodeArithmetic.forLanguage(language), asyncState); - waitOn(executor.executeAsync(prog, SleighUseropLibrary.nil())); + waitOn(executor.executeAsync(prog, PcodeUseropLibrary.nil())); waitOn(asyncState.getVar(language.getRegister("r2"))); assertEquals(BigInteger.valueOf(11), new BigInteger(1, regs.regVals.get("r2"))); 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/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java index 495b85a103..a539f0db9a 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/AbstractCheckedTraceCachedWriteBytesPcodeExecutorState.java @@ -36,16 +36,12 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState @Override public byte[] read(long offset, int size) { RangeSet uninitialized = - cache.getUninitialized(offset, offset + size - 1); - + bytes.getUninitialized(offset, offset + size - 1); if (!uninitialized.isEmpty()) { - size = checkUninitialized(source, space.getAddress(offset), size, + size = checkUninitialized(backing, space.getAddress(offset), size, addrSet(uninitialized)); - if (source != null) { - readUninitializedFromSource(uninitialized); - } } - return readCached(offset, size); + return super.read(offset, size); } } @@ -55,10 +51,10 @@ public abstract class AbstractCheckedTraceCachedWriteBytesPcodeExecutorState } @Override - protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace source, long snap) { - return new CheckedCachedSpace(language, space, source, snap); + protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { + return new CheckedCachedSpace(language, space, backing, snap); } - protected abstract int checkUninitialized(TraceMemorySpace source, Address start, int size, + 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/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/RequireIsKnownTraceCachedWriteBytesPcodeExecutorState.java index 3a1f69bc16..201fbf989b 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 @@ -39,16 +39,16 @@ public class RequireIsKnownTraceCachedWriteBytesPcodeExecutorState } @Override - protected int checkUninitialized(TraceMemorySpace source, Address start, int size, + protected int checkUninitialized(TraceMemorySpace backing, Address start, int size, AddressSet uninitialized) { - if (source == null) { + 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(source)); + AddressSetView unknown = uninitialized.subtract(getKnown(backing)); if (unknown.isEmpty()) { return size; } 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/TraceCachedWriteBytesPcodeExecutorState.java index 6a4ccac589..817cecaeb4 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/TraceCachedWriteBytesPcodeExecutorState.java @@ -16,27 +16,20 @@ package ghidra.pcode.exec.trace; import java.nio.ByteBuffer; -import java.util.*; import com.google.common.collect.*; import com.google.common.primitives.UnsignedLong; -import ghidra.generic.util.datastruct.SemisparseByteArray; -import ghidra.pcode.exec.AbstractLongOffsetPcodeExecutorState; -import ghidra.pcode.exec.BytesPcodeArithmetic; +import ghidra.pcode.exec.AbstractBytesPcodeExecutorState; +import ghidra.pcode.exec.BytesPcodeExecutorStateSpace; import ghidra.pcode.exec.trace.TraceCachedWriteBytesPcodeExecutorState.CachedSpace; -import ghidra.pcode.utils.Utils; -import ghidra.program.model.address.*; +import ghidra.program.model.address.AddressSet; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; -import ghidra.program.model.lang.Register; -import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.mem.Memory; import ghidra.trace.model.Trace; import ghidra.trace.model.memory.TraceMemorySpace; import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.util.MemBufferAdapter; import ghidra.util.MathUtilities; -import ghidra.util.Msg; /** * A state which reads bytes from a trace, but caches writes internally. @@ -47,41 +40,7 @@ import ghidra.util.Msg; * later time. */ public class TraceCachedWriteBytesPcodeExecutorState - extends AbstractLongOffsetPcodeExecutorState { - - protected class StateMemBuffer implements MemBufferAdapter { - protected final Address address; - protected final CachedSpace source; - - public StateMemBuffer(Address address, CachedSpace source) { - this.address = address; - this.source = source; - } - - @Override - public Address getAddress() { - return address; - } - - @Override - public Memory getMemory() { - return null; - } - - @Override - public boolean isBigEndian() { - return trace.getBaseLanguage().isBigEndian(); - } - - @Override - public int getBytes(ByteBuffer buffer, int addressOffset) { - byte[] data = source.read(address.getOffset() + addressOffset, buffer.remaining()); - buffer.put(data); - return data.length; - } - } - - protected final Map spaces = new HashMap<>(); + extends AbstractBytesPcodeExecutorState { protected final Trace trace; protected final long snap; @@ -90,136 +49,53 @@ public class TraceCachedWriteBytesPcodeExecutorState public TraceCachedWriteBytesPcodeExecutorState(Trace trace, long snap, TraceThread thread, int frame) { - super(trace.getBaseLanguage(), BytesPcodeArithmetic.forLanguage(trace.getBaseLanguage())); + super(trace.getBaseLanguage()); this.trace = trace; this.snap = snap; this.thread = thread; this.frame = frame; } - protected static class CachedSpace { - protected final SemisparseByteArray cache = new SemisparseByteArray(); + public static class CachedSpace extends BytesPcodeExecutorStateSpace { protected final RangeSet written = TreeRangeSet.create(); - protected final Language language; // For logging diagnostic - protected final AddressSpace space; - protected final TraceMemorySpace source; protected final long snap; - public CachedSpace(Language language, AddressSpace space, TraceMemorySpace source, + public CachedSpace(Language language, AddressSpace space, TraceMemorySpace backing, long snap) { - this.language = language; - this.space = space; - this.source = source; + super(language, space, backing); this.snap = snap; } - public void write(long offset, byte[] buffer, int srcOffset, int length) { - cache.putData(offset, buffer, srcOffset, length); + @Override + public void write(long offset, byte[] val, int srcOffset, int length) { + super.write(offset, val, srcOffset, length); UnsignedLong uLoc = UnsignedLong.fromLongBits(offset); UnsignedLong uEnd = UnsignedLong.fromLongBits(offset + length); written.add(Range.closedOpen(uLoc, uEnd)); } - public static long lower(Range rng) { - return rng.lowerBoundType() == BoundType.CLOSED - ? rng.lowerEndpoint().longValue() - : rng.lowerEndpoint().longValue() + 1; - } - - public static long upper(Range rng) { - return rng.upperBoundType() == BoundType.CLOSED - ? rng.upperEndpoint().longValue() - : rng.upperEndpoint().longValue() - 1; - } - - protected void readUninitializedFromSource(RangeSet uninitialized) { + @Override + protected void readUninitializedFromBacking(RangeSet uninitialized) { if (!uninitialized.isEmpty()) { + // TODO: Warn or bail when reading UNKNOWN bytes + // NOTE: Read without regard to gaps + // NOTE: Cannot write those gaps, though!!! Range toRead = uninitialized.span(); assert toRead.hasUpperBound() && toRead.hasLowerBound(); long lower = lower(toRead); long upper = upper(toRead); ByteBuffer buf = ByteBuffer.allocate((int) (upper - lower + 1)); - source.getBytes(snap, space.getAddress(lower), buf); + backing.getBytes(snap, space.getAddress(lower), buf); for (Range rng : uninitialized.asRanges()) { long l = lower(rng); long u = upper(rng); - cache.putData(l, buf.array(), (int) (l - lower), (int) (u - l + 1)); + bytes.putData(l, buf.array(), (int) (l - lower), (int) (u - l + 1)); } } } - protected byte[] readCached(long offset, int size) { - byte[] data = new byte[size]; - cache.getData(offset, data); - return data; - } - - protected AddressRange addrRng(Range rng) { - Address start = space.getAddress(lower(rng)); - Address end = space.getAddress(upper(rng)); - return new AddressRangeImpl(start, end); - } - - protected AddressSet addrSet(RangeSet set) { - AddressSet result = new AddressSet(); - for (Range rng : set.asRanges()) { - result.add(addrRng(rng)); - } - return result; - } - - protected Set getRegs(AddressSet set) { - Set regs = new TreeSet<>(); - for (AddressRange rng : set) { - Register r = language.getRegister(rng.getMinAddress(), (int) rng.getLength()); - if (r != null) { - regs.add(r); - } - else { - regs.addAll(Arrays.asList(language.getRegisters(rng.getMinAddress()))); - } - } - return regs; - } - - protected void warnState(AddressSet set, String message) { - Set regs = getRegs(set); - if (regs.isEmpty()) { - Msg.warn(this, message + ": " + set); - } - else { - Msg.warn(this, message + ": " + set + " (registers " + regs + ")"); - } - } - - protected void warnUninit(RangeSet uninit) { - AddressSet uninitialized = addrSet(uninit); - Set regs = getRegs(uninitialized); - if (regs.isEmpty()) { - Msg.warn(this, "Emulator read from uninitialized state: " + uninit); - } - Msg.warn(this, "Emulator read from uninitialized state: " + uninit + - " (includes registers: " + regs + ")"); - } - protected void warnUnknown(AddressSet unknown) { - Set regs = getRegs(unknown); - Msg.warn(this, "Emulator state initialized from UNKNOWN: " + unknown + - "(includes registers: " + regs + ")"); - } - - public byte[] read(long offset, int size) { - if (source != null) { - // TODO: Warn or bail when reading UNKNOWN bytes - // NOTE: Read without regard to gaps - // NOTE: Cannot write those gaps, though!!! - readUninitializedFromSource(cache.getUninitialized(offset, offset + size - 1)); - } - RangeSet stillUninit = cache.getUninitialized(offset, offset + size - 1); - if (!stillUninit.isEmpty()) { - warnUninit(stillUninit); - } - return readCached(offset, size); + warnAddressSet("Emulator state initialized from UNKNOWN", unknown); } // Must already have started a transaction @@ -238,7 +114,7 @@ public class TraceCachedWriteBytesPcodeExecutorState long fullLen = range.upperEndpoint().longValue() - lower; while (fullLen > 0) { int len = MathUtilities.unsignedMin(data.length, fullLen); - cache.getData(lower, data, 0, len); + bytes.getData(lower, data, 0, len); buf.position(0); buf.limit(len); mem.putBytes(snap, space.getAddress(lower), buf); @@ -288,47 +164,12 @@ public class TraceCachedWriteBytesPcodeExecutorState } @Override - protected long offsetToLong(byte[] offset) { - return Utils.bytesToLong(offset, offset.length, language.isBigEndian()); + protected TraceMemorySpace getBacking(AddressSpace space) { + return TraceSleighUtils.getSpaceForExecution(space, trace, thread, frame, false); } @Override - public byte[] longToOffset(AddressSpace space, long l) { - return arithmetic.fromConst(l, space.getPointerSize()); - } - - protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace source, long snap) { - return new CachedSpace(language, space, source, snap); - } - - @Override - protected CachedSpace getForSpace(AddressSpace space, boolean toWrite) { - return spaces.computeIfAbsent(space, s -> { - TraceMemorySpace tms = s.isUniqueSpace() ? null - : TraceSleighUtils.getSpaceForExecution(s, trace, thread, frame, false); - return newSpace(s, tms, snap); - }); - } - - @Override - protected void setInSpace(CachedSpace space, long offset, int size, byte[] val) { - assert size == val.length; - space.write(offset, val, 0, val.length); - } - - @Override - protected byte[] getFromSpace(CachedSpace space, long offset, int size) { - byte[] read = space.read(offset, size); - if (read.length != size) { - Address addr = space.space.getAddress(offset); - throw new UnknownStatePcodeExecutionException("Incomplete read (" + read.length + - " of " + size + " bytes)", language, addr.add(read.length), size - read.length); - } - return read; - } - - @Override - public MemBuffer getConcreteBuffer(Address address) { - return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false)); + protected CachedSpace newSpace(AddressSpace space, TraceMemorySpace backing) { + return new CachedSpace(language, space, backing, snap); } } 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 c8a65cda85..b86dd8183d 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 @@ -59,4 +59,9 @@ public enum TraceMemoryStatePcodeArithmetic implements PcodeArithmetic library) { - super(assertSleigh(trace.getBaseLanguage()), library); + public TracePcodeEmulator(Trace trace, long snap) { + super(assertSleigh(trace.getBaseLanguage())); this.trace = trace; this.snap = snap; } - public TracePcodeEmulator(Trace trace, long snap) { - this(trace, snap, SleighUseropLibrary.nil()); - } - protected PcodeExecutorState newState(TraceThread thread) { return new TraceCachedWriteBytesPcodeExecutorState(trace, snap, thread, 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 e23d015048..7d65f6293d 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 @@ -73,7 +73,7 @@ public enum TraceSleighUtils { paired); } - public static byte[] evaluateBytes(SleighExpression expr, Trace trace, long snap, + public static byte[] evaluateBytes(PcodeExpression expr, Trace trace, long snap, TraceThread thread, int frame) { SleighLanguage language = expr.getLanguage(); if (trace.getBaseLanguage() != language) { @@ -84,14 +84,14 @@ public enum TraceSleighUtils { return expr.evaluate(executor); } - public static BigInteger evaluate(SleighExpression expr, Trace trace, long snap, + public static BigInteger evaluate(PcodeExpression expr, Trace trace, long snap, TraceThread thread, int frame) { byte[] bytes = evaluateBytes(expr, trace, snap, thread, frame); return Utils.bytesToBigInteger(bytes, bytes.length, expr.getLanguage().isBigEndian(), false); } - public static Pair evaluateBytesWithState(SleighExpression expr, + public static Pair evaluateBytesWithState(PcodeExpression expr, Trace trace, long snap, TraceThread thread, int frame) { SleighLanguage language = expr.getLanguage(); if (trace.getBaseLanguage() != language) { @@ -104,7 +104,7 @@ public enum TraceSleighUtils { return expr.evaluate(executor); } - public static Pair evaluateWithState(SleighExpression expr, + public static Pair evaluateWithState(PcodeExpression expr, Trace trace, long snap, TraceThread thread, int frame) { Pair bytesPair = evaluateBytesWithState(expr, trace, snap, thread, frame); 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 0fdfb09632..7a9ad3595a 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 @@ -28,8 +28,7 @@ import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressRangeImpl; import ghidra.program.model.lang.Register; import ghidra.program.model.listing.CodeUnit; -import ghidra.program.model.mem.Memory; -import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.mem.*; import ghidra.program.model.symbol.*; import ghidra.trace.database.DBTrace; import ghidra.trace.database.symbol.DBTraceReference; @@ -40,7 +39,6 @@ import ghidra.trace.model.program.TraceProgramView; import ghidra.trace.model.symbol.TraceReference; import ghidra.trace.model.symbol.TraceSymbol; import ghidra.trace.model.thread.TraceThread; -import ghidra.trace.util.MemBufferAdapter; import ghidra.util.LockHold; import ghidra.util.Saveable; import ghidra.util.exception.NoValueException; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemBuffer.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemBuffer.java index 6e4930f880..00de19f8b2 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemBuffer.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/database/memory/DBTraceMemBuffer.java @@ -20,8 +20,8 @@ import java.nio.ByteOrder; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressOverflowException; +import ghidra.program.model.mem.MemBufferAdapter; import ghidra.program.model.mem.Memory; -import ghidra.trace.util.MemBufferAdapter; public class DBTraceMemBuffer implements MemBufferAdapter { private final DBTraceMemorySpace space; diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java index 894b708c6e..aef947e2b6 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/model/time/schedule/PatchStep.java @@ -370,7 +370,7 @@ public class PatchStep implements Step { protected Map getPatches(Language language) { PcodeProgram prog = SleighProgramCompiler.compileProgram((SleighLanguage) language, - "schedule", List.of(sleigh + ";"), SleighUseropLibrary.nil()); + "schedule", List.of(sleigh + ";"), PcodeUseropLibrary.nil()); // SemisparseArray is a bit overkill, no? Map result = new TreeMap<>(); for (PcodeOp op : prog.getCode()) { 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/TracePcodeEmulatorTest.java index 14bebcfc91..8405873dbd 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/TracePcodeEmulatorTest.java @@ -94,8 +94,8 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes TraceSleighUtils.buildByteExecutor(tb.trace, 0, thread, 0); PcodeProgram initProg = SleighProgramCompiler.compileProgram( (SleighLanguage) tb.language, "test", stateInit, - SleighUseropLibrary.nil()); - exec.execute(initProg, SleighUseropLibrary.nil()); + PcodeUseropLibrary.nil()); + exec.execute(initProg, PcodeUseropLibrary.nil()); } return thread; } @@ -476,13 +476,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes public void testInject() throws Throwable { try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { final StringBuilder dumped = new StringBuilder(); - SleighUseropLibrary library = new AnnotatedSleighUseropLibrary() { + PcodeUseropLibrary hexLib = new AnnotatedPcodeUseropLibrary() { @Override protected Lookup getMethodLookup() { return MethodHandles.lookup(); } - @SleighUserop + @PcodeUserop public void hexdump(byte[] in) { dumped.append(NumericUtilities.convertBytesToString(in)); } @@ -495,7 +495,12 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0, library); + TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return hexLib; + } + }; emu.inject(tb.addr(0x00400006), List.of("hexdump(RSP);")); PcodeThread emuThread = emu.newThread(thread.getPath()); emuThread.overrideContextWithDefault(); @@ -519,13 +524,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes public void testInjectedInterrupt() throws Throwable { try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) { final StringBuilder dumped = new StringBuilder(); - SleighUseropLibrary library = new AnnotatedSleighUseropLibrary() { + PcodeUseropLibrary hexLib = new AnnotatedPcodeUseropLibrary() { @Override protected Lookup getMethodLookup() { return MethodHandles.lookup(); } - @SleighUserop + @PcodeUserop public void hexdump(byte[] in) { dumped.append(NumericUtilities.convertBytesToString(in)); } @@ -538,7 +543,12 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes "PUSH 0xdeadbeef", "PUSH 0xbaadf00d")); - TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0, library); + TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0) { + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return hexLib; + } + }; emu.inject(tb.addr(0x00400006), List.of( "hexdump(RSP);", "emu_swi();", 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 adf651b136..7102a6d127 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 @@ -214,7 +214,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest "", " r1 = 7;", ""), - SleighUseropLibrary.NIL); + PcodeUseropLibrary.NIL); TraceThread thread; try (UndoableTransaction tid = b.startTransaction()) { thread = b.getOrAddThread("Thread1", 0); @@ -222,7 +222,7 @@ public class TraceSleighUtilsTest extends AbstractGhidraHeadlessIntegrationTest new PcodeExecutor<>(sp.getLanguage(), BytesPcodeArithmetic.forLanguage(b.language), new TraceBytesPcodeExecutorState(b.trace, 0, thread, 0)); - sp.execute(executor, SleighUseropLibrary.nil()); + sp.execute(executor, PcodeUseropLibrary.nil()); } Register r1 = b.language.getRegister("r1"); 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 99c489073d..3f9aa4c654 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 @@ -85,9 +85,9 @@ public class ToyDBTraceBuilder implements AutoCloseable { public void exec(long snap, int frame, TraceThread thread, List sleigh) { PcodeProgram program = SleighProgramCompiler.compileProgram((SleighLanguage) language, - "builder", sleigh, SleighUseropLibrary.nil()); + "builder", sleigh, PcodeUseropLibrary.nil()); TraceSleighUtils.buildByteExecutor(trace, snap, thread, frame) - .execute(program, SleighUseropLibrary.nil()); + .execute(program, PcodeUseropLibrary.nil()); } public Address addr(AddressSpace space, long offset) { diff --git a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java index dc18779568..b5ff70cb7e 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java +++ b/Ghidra/Debug/Framework-TraceModeling/src/test/java/ghidra/trace/model/time/schedule/TraceScheduleTest.java @@ -367,7 +367,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { @Override public PcodeExecutor getExecutor() { return new PcodeExecutor<>(TOY_BE_64_LANG, machine.getArithmetic(), getState()) { - public PcodeFrame execute(PcodeProgram program, SleighUseropLibrary library) { + public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary library) { machine.record.add("x:" + name); // TODO: Verify the actual effect return null; //super.execute(program, library); @@ -376,7 +376,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { } @Override - public SleighUseropLibrary getUseropLibrary() { + public PcodeUseropLibrary getUseropLibrary() { return null; } @@ -402,7 +402,7 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { protected final List record = new ArrayList<>(); public TestMachine() { - super(TOY_BE_64_LANG, null, null); + super(TOY_BE_64_LANG, null); } @Override @@ -419,6 +419,11 @@ public class TraceScheduleTest extends AbstractGhidraHeadlessIntegrationTest { protected PcodeExecutorState createLocalState(PcodeThread thread) { return null; } + + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return PcodeUseropLibrary.nil(); + } } @Test diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractModifiedPcodeThread.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractModifiedPcodeThread.java index 66c7b06dae..1b18322b8d 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractModifiedPcodeThread.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractModifiedPcodeThread.java @@ -17,6 +17,7 @@ package ghidra.pcode.emu; 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.*; @@ -29,9 +30,23 @@ import ghidra.util.Msg; /** * A p-code thread which incorporates per-architecture state modifiers on concrete bytes + * + *

+ * For a complete example of a p-code emulator, see {@link PcodeEmulator}. + * + *

+ * TODO: "State modifiers" are a feature of the older {@link Emulator}. They are crudely + * 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 { + /** + * Glue for incorporating state modifiers + * + *

+ * This allows the modifiers to change the context and counter of the thread. + */ protected class GlueEmulate extends Emulate { public GlueEmulate(SleighLanguage lang, MemoryState s, BreakTable b) { super(lang, s, b); @@ -63,6 +78,12 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< } } + /** + * Glue for incorporating state modifiers + * + *

+ * This allows the modifiers to access the thread's state (memory and registers). + */ protected class GlueMemoryState extends MemoryState { public GlueMemoryState(Language language) { super(language); @@ -85,6 +106,12 @@ 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) { @@ -93,7 +120,7 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< @Override public void executeCallother(PcodeOp op, PcodeFrame frame, - SleighUseropLibrary library) { + PcodeUseropLibrary library) { // Prefer one in the library. Fall-back to state modifier's impl try { super.executeCallother(op, frame, library); @@ -112,12 +139,19 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< protected Address savedCounter; + /** + * Construct a new thread with the given name belonging to the given machine + * + * @see PcodeMachine#newThread(String) + * @param name the name of the new thread + * @param machine the machine to which the new thread belongs + */ public AbstractModifiedPcodeThread(String name, AbstractPcodeMachine machine) { super(name, machine); /** * These two exist as a way to integrate the language-specific injects that are already - * written for the established concrete emulator. + * written for {@link Emulator}. */ emulate = new GlueEmulate(language, new GlueMemoryState(language), new BreakTableCallBack(language)); @@ -162,7 +196,7 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< } /** - * Called by the legacy state modifier to retrieve concrete bytes from the thread's state + * Called by a state modifier to read concrete bytes from the thread's state * * @see {@link MemoryState#getChunk(byte[], AddressSpace, long, int, boolean)} */ @@ -170,7 +204,7 @@ public abstract class AbstractModifiedPcodeThread extends DefaultPcodeThread< boolean stopOnUnintialized); /** - * Called by the legacy state modifier to set concrete bytes in the thread's state + * Called by a state modifier to write concrete bytes to the thread's state * * @see {@link MemoryState#setChunk(byte[], AddressSpace, long, int)} */ diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeEmulator.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeEmulator.java deleted file mode 100644 index 8b19aaf84f..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/AbstractPcodeEmulator.java +++ /dev/null @@ -1,35 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.pcode.emu; - -import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcode.exec.BytesPcodeArithmetic; -import ghidra.pcode.exec.SleighUseropLibrary; - -/** - * A p-code machine which executes on concrete bytes and incorporates per-architecture state - * modifiers - */ -public abstract class AbstractPcodeEmulator extends AbstractPcodeMachine { - public AbstractPcodeEmulator(SleighLanguage language, SleighUseropLibrary library) { - super(language, BytesPcodeArithmetic.forLanguage(language), library); - } - - @Override - protected BytesPcodeThread createThread(String name) { - return new BytesPcodeThread(name, this); - } -} 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 e7ed80b25a..648797ea27 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 @@ -25,13 +25,16 @@ 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}. */ public abstract class AbstractPcodeMachine implements PcodeMachine { protected final SleighLanguage language; protected final PcodeArithmetic arithmetic; - protected final SleighUseropLibrary library; + protected final PcodeUseropLibrary library; - protected final SleighUseropLibrary stubLibrary; + protected final PcodeUseropLibrary stubLibrary; /* for abstract thread access */ PcodeStateInitializer initializer; private PcodeExecutorState sharedState; @@ -41,12 +44,17 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { protected final Map injects = new HashMap<>(); - public AbstractPcodeMachine(SleighLanguage language, PcodeArithmetic arithmetic, - SleighUseropLibrary library) { + /** + * 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; - this.library = library; + this.library = createUseropLibrary(); this.stubLibrary = createThreadStubLibrary().compose(library); /** @@ -57,6 +65,13 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { this.initializer = getPluggableInitializer(language); } + /** + * A factory method to create the userop library shared by all threads in this machine + * + * @return the library + */ + protected abstract PcodeUseropLibrary createUseropLibrary(); + @Override public SleighLanguage getLanguage() { return language; @@ -68,26 +83,50 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { } @Override - public SleighUseropLibrary getUseropLibrary() { + public PcodeUseropLibrary getUseropLibrary() { return library; } @Override - public SleighUseropLibrary getStubUseropLibrary() { + public PcodeUseropLibrary getStubUseropLibrary() { return stubLibrary; } + /** + * A factory method to create the (memory) state shared by all threads in this machine + * + * @return the shared state + */ protected abstract PcodeExecutorState createSharedState(); + /** + * A factory method to create the (register) state local to the given thread + * + * @param thread the thread + * @return the thread-local state + */ protected abstract PcodeExecutorState createLocalState(PcodeThread thread); - protected SleighUseropLibrary createThreadStubLibrary() { - return new DefaultPcodeThread.SleighEmulationLibrary(null); + /** + * A factory method to create a stub library for compiling thread-local SLEIGH source + * + *

+ * Because threads may introduce p-code userops using libraries unique to that thread, it + * becomes necessary to at least export stub symbols, so that p-code programs can be compiled + * from SLEIGH source before the thread has necessarily been created. A side effect of this + * strategy is that all threads, though they may have independent libraries, must export + * identically-named symbols. + * + * @return the stub library for all threads + */ + protected PcodeUseropLibrary createThreadStubLibrary() { + return new DefaultPcodeThread.PcodeEmulationLibrary(null); } /** - * Extension point to override construction of this machine's threads + * A factory method to create a new thread in this machine * + * @see #newThread(String) * @param name the name of the new thread * @return the new thread */ @@ -95,6 +134,26 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { return new DefaultPcodeThread<>(name, this); } + /** + * Search the classpath for an applicable state initializer + * + *

+ * If found, the initializer is executed immediately upon creating this machine's shared state + * and upon creating each thread. + * + *

+ * TODO: This isn't really being used. At one point in development it was used to initialize + * x86's FS_OFFSET and GS_OFFSET registers. Those only exist in p-code, not the real processor, + * and replace what might have been {@code segment(FS)}. There seems more utility in detecting + * when those registers are uninitialized, requiring the user to initialize them, than it is to + * silently initialize them to 0. Unless we find utility in this, it will likely be removed in + * the near future. + * + * @see #doPluggableInitialization() + * @see DefaultPcodeThread#doPluggableInitialization() + * @param language the language requiring pluggable initialization + * @return the initializer + */ protected static PcodeStateInitializer getPluggableInitializer(Language language) { for (PcodeStateInitializer init : ClassSearcher.getInstances(PcodeStateInitializer.class)) { if (init.isApplicable(language)) { @@ -104,6 +163,11 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { return null; } + /** + * Execute the initializer upon this machine, if applicable + * + * @see #getPluggableInitializer(Language) + */ protected void doPluggableInitialization() { if (initializer != null) { initializer.initializeMachine(this); @@ -148,6 +212,12 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { return sharedState; } + /** + * Check for a p-code injection (override) at the given address + * + * @param address the address, usually the program counter + * @return the injected program, most likely {@code null} + */ protected PcodeProgram getInject(Address address) { return injects.get(address); } @@ -184,7 +254,9 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { /** * TODO: The template build idea is probably more pertinent here. If a user places a * breakpoint with the purpose of single-stepping the p-code of that instruction, it won't - * work, because that p-code is occluded by emu_exec_decoded(). + * work, because that p-code is occluded by emu_exec_decoded(). I suppose this could also be + * addressed by formalizing and better exposing the notion of p-code stacks (of p-code + * frames) */ PcodeProgram pcode = compileSleigh("breakpoint:" + address, List.of( "if (!(" + sleighCondition + ")) goto ;", 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 d348ccfb84..7950fe2494 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 @@ -17,7 +17,21 @@ package ghidra.pcode.emu; import ghidra.program.model.address.AddressSpace; +/** + * A simple p-code thread that operates on concrete bytes + * + *

+ * 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 { + /** + * Construct a new thread + * + * @see PcodeMachine#newThread(String) + * @param name the thread's name + * @param machine the machine to which the thread belongs + */ public BytesPcodeThread(String name, AbstractPcodeMachine machine) { super(name, machine); } 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 573a78dcb5..251a100bf0 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 @@ -18,11 +18,11 @@ package ghidra.pcode.emu; import java.math.BigInteger; import java.util.*; +import ghidra.app.emulator.Emulator; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.exec.*; import ghidra.program.model.address.Address; -import ghidra.program.model.lang.Register; -import ghidra.program.model.lang.RegisterValue; +import ghidra.program.model.lang.*; import ghidra.program.model.listing.Instruction; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.util.ProgramContextImpl; @@ -30,16 +30,51 @@ 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 + * 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. + * + *

+ * This class implements the control-flow logic of the target machine, cooperating with the p-code + * program flow implemented by the {@link PcodeExecutor}. This implementation exists primarily in + * {@link #beginInstructionOrInject()} and {@link #advanceAfterFinished()}. */ public class DefaultPcodeThread implements PcodeThread { - protected static class SleighEmulationLibrary extends AnnotatedSleighUseropLibrary { + + /** + * A userop library exporting some methods for emulated thread control + * + *

+ * TODO: Since p-code userops can now receive the executor, it may be better to receive it, cast + * it, and obtain the thread, rather than binding a library to each thread. + * + * @param no particular type, except to match the thread's + */ + public static class PcodeEmulationLibrary extends AnnotatedPcodeUseropLibrary { private final DefaultPcodeThread thread; - public SleighEmulationLibrary(DefaultPcodeThread thread) { + /** + * Construct a library to control the given thread + * + * @param thread the thread + */ + public PcodeEmulationLibrary(DefaultPcodeThread thread) { this.thread = thread; } - @SleighUserop + /** + * Execute the actual machine instruction at the current program counter + * + *

+ * Because "injects" override the machine instruction, injects which need to defer to the + * machine instruction must invoke this userop. + * + * @see #emu_skip_decoded() + */ + @PcodeUserop public void emu_exec_decoded() { /** * TODO: This idea of "pushing" a frame could be formalized, and the full stack made @@ -53,7 +88,18 @@ public class DefaultPcodeThread implements PcodeThread { thread.frame = saved; } - @SleighUserop + /** + * Advance the program counter beyond the current machine instruction + * + *

+ * Because "injects" override the machine instruction, they must specify the effect on the + * program counter, lest the thread become caught in an infinite loop on the inject. To + * emulate fall-through without executing the machine instruction, the inject must invoke + * this userop. + * + * @see #emu_exec_decoded() + */ + @PcodeUserop public void emu_skip_decoded() { PcodeFrame saved = thread.frame; thread.dropInstruction(); @@ -61,22 +107,46 @@ public class DefaultPcodeThread implements PcodeThread { thread.frame = saved; } - @SleighUserop + /** + * Interrupt execution + * + *

+ * This immediately throws an {@link InterruptPcodeExecutionException}. To implement + * out-of-band breakpoints, inject an invocation of this userop at the desired address. + * + * @see PcodeMachine#addBreakpoint(Address, String) + */ + @PcodeUserop public void emu_swi() { throw new InterruptPcodeExecutionException(null, null); } } - protected class PcodeThreadExecutor extends PcodeExecutor { + /** + * An executor for the p-code thread + * + *

+ * This executor checks for thread suspension and updates the program counter register upon + * execution of (external) branches. + */ + public class PcodeThreadExecutor extends PcodeExecutor { volatile boolean suspended = false; + /** + * Construct the executor + * + * @see DefaultPcodeThread#createExecutor() + * @param language the language of the containing machine + * @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); } @Override - public void stepOp(PcodeOp op, PcodeFrame frame, SleighUseropLibrary library) { + public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary library) { if (suspended) { throw new SuspendedPcodeExecutionException(frame, null); } @@ -87,6 +157,10 @@ public class DefaultPcodeThread implements PcodeThread { protected void branchToAddress(Address target) { overrideCounter(target); } + + public Instruction getInstruction() { + return instruction; + } } private final String name; @@ -95,7 +169,7 @@ public class DefaultPcodeThread implements PcodeThread { protected final PcodeArithmetic arithmetic; protected final ThreadPcodeExecutorState state; protected final InstructionDecoder decoder; - protected final SleighUseropLibrary library; + protected final PcodeUseropLibrary library; protected final PcodeThreadExecutor executor; protected final Register pc; @@ -110,6 +184,13 @@ public class DefaultPcodeThread implements PcodeThread { protected final ProgramContextImpl defaultContext; protected final Map injects = new HashMap<>(); + /** + * Construct a new thread + * + * @see AbstractPcodeMachine#createThread(String) + * @param name the name of the thread + * @param machine the machine containing the thread + */ public DefaultPcodeThread(String name, AbstractPcodeMachine machine) { this.name = name; this.machine = machine; @@ -136,14 +217,34 @@ public class DefaultPcodeThread implements PcodeThread { this.reInitialize(); } + /** + * A factory method for the instruction decoder + * + * @param sharedState the machine's shared (memory state) + * @return + */ protected SleighInstructionDecoder createInstructionDecoder(PcodeExecutorState sharedState) { return new SleighInstructionDecoder(language, sharedState); } - protected SleighUseropLibrary createUseropLibrary() { - return new SleighEmulationLibrary<>(this).compose(machine.library); + /** + * A factory method to create the complete userop library for this thread + * + *

+ * The returned library must compose the containing machine's shared userop library. See + * {@link PcodeUseropLibrary#compose(PcodeUseropLibrary)}. + * + * @return the thread's complete userop library + */ + protected PcodeUseropLibrary createUseropLibrary() { + return new PcodeEmulationLibrary<>(this).compose(machine.library); } + /** + * A factory method to create the executor for this thread + * + * @return the executor + */ protected PcodeThreadExecutor createExecutor() { return new PcodeThreadExecutor(language, arithmetic, state); } @@ -203,6 +304,11 @@ public class DefaultPcodeThread implements PcodeThread { } } + /** + * Execute the initializer upon this thread, if applicable + * + * @see AbstractPcodeMachine#getPluggableInitializer(Language) + */ protected void doPluggableInitialization() { if (machine.initializer != null) { machine.initializer.initializeThread(this); @@ -258,6 +364,9 @@ public class DefaultPcodeThread implements PcodeThread { } } + /** + * Start execution of the instruction or inject at the program counter + */ protected void beginInstructionOrInject() { PcodeProgram inj = getInject(counter); if (inj != null) { @@ -271,6 +380,9 @@ public class DefaultPcodeThread implements PcodeThread { } } + /** + * Resolve a finished instruction, advancing the program counter if necessary + */ protected void advanceAfterFinished() { if (instruction == null) { // Frame resulted from an inject frame = null; @@ -297,12 +409,19 @@ public class DefaultPcodeThread implements PcodeThread { return instruction; } + /** + * A sanity-checking measure: Cannot start a new instruction while one is still being executed + */ protected void assertCompletedInstruction() { if (frame != null) { throw new IllegalStateException("The current instruction or inject has not finished."); } } + /** + * A sanity-checking measure: Cannot finish an instruction unless one is currently being + * executed + */ protected void assertMidInstruction() { if (frame == null) { throw new IllegalStateException("There is no current instruction to finish."); @@ -311,6 +430,10 @@ public class DefaultPcodeThread implements PcodeThread { /** * An extension point for hooking instruction execution before the fact + * + *

+ * 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 @@ -318,6 +441,10 @@ public class DefaultPcodeThread implements PcodeThread { /** * An extension point for hooking instruction execution after the fact + * + *

+ * 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 @@ -380,7 +507,7 @@ public class DefaultPcodeThread implements PcodeThread { } @Override - public SleighUseropLibrary getUseropLibrary() { + public PcodeUseropLibrary getUseropLibrary() { return library; } @@ -389,6 +516,15 @@ public class DefaultPcodeThread implements PcodeThread { return state; } + /** + * Check for a p-code injection (override) at the given address + * + *

+ * This checks this thread's particular injects and then defers to the machine's injects. + * + * @param address the address, usually the program counter + * @return the injected program, most likely {@code null} + */ protected PcodeProgram getInject(Address address) { PcodeProgram inj = injects.get(address); if (inj != null) { diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/InstructionDecoder.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/InstructionDecoder.java index d25bf85e3f..78dba86cc6 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/InstructionDecoder.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/InstructionDecoder.java @@ -19,9 +19,12 @@ import ghidra.program.model.address.Address; import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.listing.Instruction; +/** + * A means of decoding machine instructions from the bytes contained in the machine state + */ public interface InstructionDecoder { /** - * Decode the instruction at the given address using the given context + * Decode the instruction starting at the given address using the given context * *

* This method cannot return null. If a decode error occurs, it must throw an exception. 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 new file mode 100644 index 0000000000..3842e7e7e1 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeEmulator.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; + +import java.util.List; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.emu.sys.EmuSyscallLibrary; +import ghidra.pcode.exec.*; +import ghidra.program.model.address.Address; + +/** + * A p-code machine which executes on concrete bytes and incorporates per-architecture state + * modifiers + * + *

+ * This is a simple concrete bytes emulator suitable for unit testing and scripting. More complex + * use cases likely benefit by extending this or one of its super types. Likewise, the factory + * methods will likely instantiate classes which extend the default or one of its super types. When + * creating such an extension, it helps to refer to this default implementation to understand the + * overall architecture of an emulator. The emulator was designed using hierarchies of abstract + * classes each extension incorporating more complexity (and restrictions) finally culminating here. + * 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. + * + *

+ * emulator      : PcodeMachine
+ *  - language     : SleighLanguage
+ *  - arithmetic   : PcodeArithmetic
+ *  - sharedState  : PcodeExecutorState
+ *  - library      : PcodeUseropLibrary
+ *  - injects      : Map
+ *  - threads      : List>
+ *    - [0]          : PcodeThread
+ *      - decoder      : InstructionDecoder
+ *      - executor     : PcodeExecutor
+ *      - frame        : PcodeFrame
+ *      - localState   : PcodeExecutorState
+ *      - library      : PcodeUseropLibrary
+ *      - injects      : Map
+ *    - [1] ...
+ * 
+ * + *

+ * The root object of an emulator is the {@link PcodeEmulator}, usually ascribed the type + * {@link PcodeMachine}. At the very least, it must know the language of the processor it emulates. + * It then derives appropriate arithmetic definitions, a shared (memory) state, and a shared userop + * library. Initially, the machine has no threads. For many use cases creating a single + * {@link PcodeThread} suffices; however, this default implementation models multi-threaded + * execution "out of the box." Upon creation, each thread is assigned a local (register) state, and + * a userop library for controlling that particular thread. The thread's full state and userop + * library are composed from the machine's shared components and that thread's particular + * 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. + * + *

+ * Each thread creates an {@link InstructionDecoder} and a {@link PcodeExecutor}, providing the + * kernel of p-code emulation for that thread. That executor is bound to the thread's composed + * state, and to the machine's arithmetic. Together, the state and the arithmetic "define" all the + * p-code ops that the executor can invoke. Unsurprisingly, arithmetic operations are delegated to + * the {@link PcodeArithmetic}, and state operations (including memory operations and temporary + * variable access) are delegated to the {@link PcodeExecutorState}. The core execution loop easily + * 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}. + * + *

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

+ * Alternatively, if the target program never invokes system calls directly, but rather via + * system-provided APIs, then it may suffice to stub out those imports. Typically, Ghidra will place + * a "thunk" at each import address with the name of the import. Stubbing an import is accomplished + * by injecting p-code at the import address. See {@link PcodeMachine#inject(Address, List)}. The + * inject will need to replicate the semantics of that call to the desired fidelity. + * IMPORTANT: The inject must also return control to the calling function, usually by + * replicating the conventions of the target platform. + */ +public class PcodeEmulator extends AbstractPcodeMachine { + /** + * Construct a new concrete emulator + * + *

+ * Yes, it is customary to invoke this constructor directly. + * + * @param language the language of the target processor + */ + public PcodeEmulator(SleighLanguage language) { + super(language, BytesPcodeArithmetic.forLanguage(language)); + } + + @Override + protected BytesPcodeThread createThread(String name) { + return new BytesPcodeThread(name, this); + } + + @Override + protected PcodeExecutorState createSharedState() { + return new BytesPcodeExecutorState(language); + } + + @Override + protected PcodeExecutorState createLocalState(PcodeThread thread) { + return new BytesPcodeExecutorState(language); + } + + @Override + protected PcodeUseropLibrary createUseropLibrary() { + return PcodeUseropLibrary.nil(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeMachine.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeMachine.java index acb5dc7a82..e99ed2a991 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeMachine.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeMachine.java @@ -19,7 +19,7 @@ import java.util.Collection; import java.util.List; import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcode.emu.DefaultPcodeThread.SleighEmulationLibrary; +import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary; import ghidra.pcode.exec.*; import ghidra.program.model.address.Address; @@ -48,16 +48,16 @@ public interface PcodeMachine { * Get the userop library common to all threads in the machine. * *

- * Note that threads may have larger libraries, but each should contain all the userops in this + * Note that threads may have larger libraries, but each contains all the userops in this * library. * * @return the userop library */ - SleighUseropLibrary getUseropLibrary(); + PcodeUseropLibrary getUseropLibrary(); /** - * Get a userop library which at least declares all userops available in thread userop - * libraries. + * Get a userop library which at least declares all userops available in each thread userop + * library. * *

* Thread userop libraries may have more userops than are defined in the machine's userop @@ -69,7 +69,7 @@ public interface PcodeMachine { * * @return the stub library */ - SleighUseropLibrary getStubUseropLibrary(); + PcodeUseropLibrary getStubUseropLibrary(); /** * Create a new thread with a default name in this machine @@ -134,7 +134,7 @@ public interface PcodeMachine { * will inject it at the given address. The resulting p-code replaces that which would * be executed by decoding the instruction at the given address. The means the machine will not * decode, nor advance its counter, unless the SLEIGH causes it. In most cases, the SLEIGH will - * call {@link SleighEmulationLibrary#emu_exec_decoded()} to cause the machine to decode and + * call {@link PcodeEmulationLibrary#emu_exec_decoded()} to cause the machine to decode and * execute the overridden instruction. * *

@@ -165,7 +165,7 @@ public interface PcodeMachine { *

* Breakpoints are implemented at the p-code level using an inject, without modification to the * emulated image. As such, it cannot coexist with another inject. A client needing to break - * during an inject must use {@link SleighEmulationLibrary#emu_swi()} in the injected SLEIGH. + * during an inject must use {@link PcodeEmulationLibrary#emu_swi()} in the injected SLEIGH. * * @param address the address at which to break * @param sleighCondition a SLEIGH expression which controls the breakpoint diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeStateInitializer.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeStateInitializer.java index 6ed69b4f6d..8f71749332 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeStateInitializer.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/PcodeStateInitializer.java @@ -23,7 +23,7 @@ import ghidra.util.classfinder.ExtensionPoint; * *

* As much as possible, it's highly-recommended to use SLEIGH execution to perform any - * modifications. This will help it remain portable to various state types. + * modifications. This will help it remain agnostic to various state types. * *

* TODO: Implement annotation-based {@link #isApplicable(Language)}? @@ -39,8 +39,8 @@ public interface PcodeStateInitializer extends ExtensionPoint { boolean isApplicable(Language language); /** - * The machine's memory state has just been initialized from a "real" target, and additional - * initialization is needed for SLEIGH execution + * The machine's memory state has just been initialized, and additional initialization is needed + * for SLEIGH execution * *

* There's probably not much preparation of memory @@ -52,8 +52,8 @@ public interface PcodeStateInitializer extends ExtensionPoint { } /** - * The thread's register state has just been initialized from a "real" target, and additional - * initialization is needed for SLEIGH execution + * The thread's register state has just been initialized, and additional initialization is + * needed for SLEIGH execution * *

* Initialization generally consists of setting "virtual" registers using data from the real 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 97c53d2158..5e5bb25198 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 @@ -18,9 +18,10 @@ package ghidra.pcode.emu; import java.util.List; import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcode.emu.DefaultPcodeThread.SleighEmulationLibrary; +import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary; import ghidra.pcode.exec.*; import ghidra.program.model.address.Address; +import ghidra.program.model.lang.Register; import ghidra.program.model.lang.RegisterValue; import ghidra.program.model.listing.Instruction; @@ -48,6 +49,7 @@ public interface PcodeThread { /** * Set the emulator's counter without writing to its machine state * + * @see #overrideCounter(Address) * @param counter the new target address */ void setCounter(Address counter); @@ -62,23 +64,25 @@ public interface PcodeThread { /** * Set the emulator's counter and write the PC of its machine state * + * @see #setCounter(Address) * @param counter the new target address */ void overrideCounter(Address counter); /** - * Adjust the emulator's parsing context without writing to its machine state + * Adjust the emulator's decoding context without writing to its machine state * + *

+ * As in {@link RegisterValue#assign(Register, RegisterValue)}, only those bits having a value + * in the given context are applied to the current context. + * + * @see #overrideContext(RegisterValue) * @param context the new context */ void assignContext(RegisterValue context); /** - * Adjust the emulator's parsing context without writing to its machine state - * - * @param context the new context void assignContext(RegisterValue context); - * - * /** Get the emulator's parsing context + * Get the emulator's decoding context * * @return the context */ @@ -87,6 +91,7 @@ public interface PcodeThread { /** * Adjust the emulator's parsing context and write the contextreg of its machine state * + * @see #assignContext(RegisterValue) * @param context the new context */ void overrideContext(RegisterValue context); @@ -114,12 +119,23 @@ 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 them. It and its - * slots are executed in a single "step." Stepping individual p-code ops which comprise the - * delay-slotted instruction is possible using {@link #stepPcodeOp(PcodeFrame)}. + * 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)}. */ void stepInstruction(); + /** + * Repeat {@link #stepInstruction()} count times + * + * @param count the number of instructions to step + */ + default void stepInstruction(long count) { + for (long i = 0; i < count; i++) { + stepInstruction(); + } + } + /** * Step emulation a single p-code operation * @@ -130,19 +146,44 @@ public interface PcodeThread { * completed, the machine's program counter is advanced and the current frame is removed. * *

- * In order to provide the most flexibility, there is no enforcement of various emulation state - * on this method. Expect strange behavior for strange call sequences. For example, the caller - * should ensure that the given frame was in fact generated from the emulators current - * instruction. Doing otherwise may cause the emulator to advance in strange ways. + * Consider the case of a fall-through instruction: The first p-code step decodes the + * instruction and sets up the p-code frame. The second p-code step executes the first p-code op + * of the frame. Each subsequent p-code step executes the next p-code op until no ops remain. + * The final p-code step detects the fall-through result, advances the counter, and disposes the + * frame. The next p-code step is actually the first p-code step of the next instruction. + * + *

+ * Consider the case of a branching instruction: The first p-code step decodes the instruction + * and sets up the p-code frame. The second p-code step executes the first p-code op of the + * frame. Each subsequent p-code step executes the next p-code op until an (external) branch is + * executed. That branch itself sets the program counter appropriately. The final p-code step + * detects the branch result and simply disposes the frame. The next p-code step is actually the + * first p-code step of the next instruction. + * + *

+ * The decode step in both examples is subject to p-code injections. In order to provide the + * most flexibility, there is no enforcement of various emulation state on this method. Expect + * strange behavior for strange call sequences. * *

* While this method heeds injects, such injects will obscure the p-code of the instruction * itself. If the inject executes the instruction, the entire instruction will be executed when - * stepping the {@link SleighEmulationLibrary#emu_exec_decoded()} userop, since there is not + * stepping the {@link PcodeEmulationLibrary#emu_exec_decoded()} userop, since there is not * (currently) any way to "step into" a userop. */ void stepPcodeOp(); + /** + * Repeat {@link #stepPcodeOp()} count times + * + * @param count the number of p-code operations to step + */ + default void stepPcodeOp(long count) { + for (long i = 0; i < count; i++) { + stepPcodeOp(); + } + } + /** * Get the current frame, if present * @@ -169,9 +210,9 @@ public interface PcodeThread { * Execute the next instruction, ignoring injects * *

- * This method should likely only be used internally. It steps the current instruction, but - * without any consideration for user injects, e.g., breakpoints. Most clients should call - * {@link #stepInstruction()} instead. + * WARNING: This method should likely only be used internally. It steps the current + * instruction, but without any consideration for user injects, e.g., breakpoints. Most clients + * should call {@link #stepInstruction()} instead. * * @throws IllegalStateException if the emulator is still in the middle of an instruction. That * can happen if the machine is interrupted, or if the client has called @@ -201,10 +242,11 @@ public interface PcodeThread { * If there is a current instruction, drop its frame of execution * *

- * This does not revert any state changes caused by a partially-executed instruction. It is up - * to the client to revert the underlying machine state if desired. Note the thread's program - * counter will not be advanced. Likely, the next call to {@link #stepInstruction()} will - * re-start the same instruction. If there is no current instruction, this method has no effect. + * WARNING: This does not revert any state changes caused by a partially-executed + * instruction. It is up to the client to revert the underlying machine state if desired. Note + * the thread's program counter will not be advanced. Likely, the next call to + * {@link #stepInstruction()} will re-start the same instruction. If there is no current + * instruction, this method has no effect. */ void dropInstruction(); @@ -216,7 +258,7 @@ public interface PcodeThread { * instruction is finished. By calling this method, you are "donating" the current Java thread * to the emulator. This method will not likely return, but instead only terminates via * exception, e.g., hitting a user breakpoint or becoming suspended. Depending on the use case, - * this method might be invoked from a dedicated Java thread. + * this method might be invoked from a Java thread dedicated to this emulated thread. */ void run(); @@ -226,8 +268,8 @@ 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 - * desired, then upon catching the exception, the dedicated thread should un-suspend the machine - * and call {@link #finishInstruction()}. + * desired, then upon catching the exception, un-suspend the p-code thread and call + * {@link #finishInstruction()} or {@link #dropInstruction()}. */ void setSuspended(boolean suspended); @@ -264,11 +306,12 @@ public interface PcodeThread { PcodeExecutor getExecutor(); /** - * Get the userop library for controlling this thread's execution + * Get the complete userop library for this thread, including userops for controlling this + * thread * * @return the library */ - SleighUseropLibrary getUseropLibrary(); + PcodeUseropLibrary getUseropLibrary(); /** * Get the thread's memory and register state @@ -283,6 +326,7 @@ public interface PcodeThread { /** * 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. * Where there is both a machine-level and thread-level inject the thread inject takes * precedence. Furthermore, the machine-level inject cannot be accessed by the thread-level 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 516e5fc919..c7f4b42ad2 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 @@ -25,9 +25,15 @@ import ghidra.program.model.listing.Instruction; import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; +/** + * The default instruction decoder, based on SLEIGH + * + *

+ * This simply uses a {@link Disassembler} on the machine's memory state. + */ public class SleighInstructionDecoder implements InstructionDecoder { // TODO: Some sort of instruction decode caching? - // Not as imported for stepping small distances + // Not as important for stepping small distances // Could become important when dealing with "full system emulation," if we get there. private static final String DEFAULT_ERROR = "Unknown disassembly error"; @@ -43,6 +49,14 @@ public class SleighInstructionDecoder implements InstructionDecoder { private Instruction instruction; + /** + * Construct a SLEIGH instruction decoder + * + * @see {@link DefaultPcodeThread#createInstructionDecoder(PcodeExecutorState)} + * @param language the language to decoder + * @param state the state containing the target program, probably the shared state of the p-code + * machine. It must be possible to obtain concrete buffers on this state. + */ public SleighInstructionDecoder(Language language, PcodeExecutorState state) { this.state = state; addrFactory = language.getAddressFactory(); @@ -67,6 +81,11 @@ public class SleighInstructionDecoder implements InstructionDecoder { return instruction; } + /** + * Compute the "length" of an instruction, including any delay-slotted instructions that follow + * + * @return the length + */ protected int computeLength() { int length = instruction.getLength(); int slots = instruction.getDelaySlotDepth(); 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 ef149cd8e9..58fac6cd77 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 @@ -20,16 +20,35 @@ 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 + * + * @param the type of values stored in the states + */ public class ThreadPcodeExecutorState implements PcodeExecutorState { protected final PcodeExecutorState sharedState; protected final PcodeExecutorState localState; + /** + * Create a multiplexed state + * + * @see {@link DefaultPcodeThread#DefaultPcodeThread(String, AbstractPcodeMachine)} + * @param sharedState the shared part of the state + * @param localState the thread-local part of the state + */ public ThreadPcodeExecutorState(PcodeExecutorState sharedState, PcodeExecutorState localState) { this.sharedState = sharedState; this.localState = localState; } + /** + * Decide whether or not access to the given space is directed to thread-local state + * + * @param space the space + * @return true for thread-local state, false for shared state + */ protected boolean isThreadLocalSpace(AddressSpace space) { return space.isRegisterSpace() || space.isUniqueSpace(); } @@ -71,10 +90,20 @@ public class ThreadPcodeExecutorState implements PcodeExecutorState { return sharedState.getConcreteBuffer(address); } + /** + * Get the shared state + * + * @return the shared state + */ public PcodeExecutorState getSharedState() { return sharedState; } + /** + * Get the thread-local state + * + * @return the thread-local state + */ public PcodeExecutorState getLocalState() { return localState; } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/AbstractEmuLinuxSyscallUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/AbstractEmuLinuxSyscallUseropLibrary.java new file mode 100644 index 0000000000..f5e33bfdaa --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/AbstractEmuLinuxSyscallUseropLibrary.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.pcode.emu.linux; + +import java.util.*; + +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.unix.*; +import ghidra.pcode.emu.unix.EmuUnixFileSystem.OpenFlag; +import ghidra.program.model.listing.Program; + +/** + * An abstract library of Linux system calls, suitable for use with any processor + * + * @param the type of values processed by the library + */ +public abstract class AbstractEmuLinuxSyscallUseropLibrary + extends AbstractEmuUnixSyscallUseropLibrary { + public static final int O_MASK_RDWR = 0x3; + public static final int O_RDONLY = 0x0; + public static final int O_WRONLY = 0x1; + public static final int O_RDWR = 0x2; + public static final int O_CREAT = 0x40; + public static final int O_TRUNC = 0x200; + public static final int O_APPEND = 0x400; + + /** + * TODO: A map from simulator-defined errno to Linux-defined errno + * + *

+ * TODO: These may be applicable to all Linux, not just amd64.... + */ + protected static final Map ERRNOS = Map.ofEntries( + Map.entry(Errno.EBADF, 9)); + + /** + * Construct a new library + * + * @param machine the machine emulating the hardware + * @param fs the file system to export to the user-space program + * @param program a program containing the syscall definitions and conventions, likely the + * target program + */ + public AbstractEmuLinuxSyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, + Program program) { + super(machine, fs, program); + } + + /** + * Construct a new library + * + * @param machine the machine emulating the hardware + * @param fs the file system to export to the user-space program + * @param program a program containing the syscall definitions and conventions, likely the + * target program + * @param user the "current user" to simulate + */ + public AbstractEmuLinuxSyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, + Program program, EmuUnixUser user) { + super(machine, fs, program, user); + } + + @Override + protected Set convertFlags(int flags) { + EnumSet result = EnumSet.noneOf(OpenFlag.class); + int rdwr = flags & O_MASK_RDWR; + if (rdwr == O_RDONLY) { + result.add(OpenFlag.O_RDONLY); + } + if (rdwr == O_WRONLY) { + result.add(OpenFlag.O_WRONLY); + } + if (rdwr == O_RDWR) { + result.add(OpenFlag.O_RDWR); + } + if ((flags & O_CREAT) != 0) { + result.add(OpenFlag.O_CREAT); + } + if ((flags & O_TRUNC) != 0) { + result.add(OpenFlag.O_TRUNC); + } + if ((flags & O_APPEND) != 0) { + result.add(OpenFlag.O_APPEND); + } + return result; + } + + @Override + protected int getErrno(Errno err) { + Integer errno = ERRNOS.get(err); + if (errno == null) { + throw new AssertionError("Do not know errno value for " + err); + } + return errno; + } +} 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 new file mode 100644 index 0000000000..1287eb9af4 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java @@ -0,0 +1,104 @@ +/* ### + * 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.linux; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import generic.jar.ResourceFile; +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.PcodeExecutor; +import ghidra.pcode.exec.PcodeExecutorStatePiece; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.FileDataTypeManager; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Program; + +/** + * A system call library simulating Linux for amd64 / x86_64 + * + * @param the type of values processed by the library + */ +public class EmuLinuxAmd64SyscallUseropLibrary extends AbstractEmuLinuxSyscallUseropLibrary { + + protected final Register regRAX; + + protected FileDataTypeManager clib64; + + /** + * Construct the system call library for Linux-amd64 + * + * @param machine the machine emulating the hardware + * @param fs the file system to export to the user-space program + * @param program a program containing syscall definitions and conventions, likely the target + * program + */ + public EmuLinuxAmd64SyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, + Program program) { + super(machine, fs, program); + regRAX = machine.getLanguage().getRegister("RAX"); + } + + /** + * Construct the system call library for Linux-amd64 + * + * @param machine the machine emulating the hardware + * @param fs the file system to export to the user-space program + * @param program a program containing syscall definitions and conventions, likely the target + * program + * @param user the "current user" to simulate + */ + public EmuLinuxAmd64SyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, + Program program, EmuUnixUser user) { + super(machine, fs, program, user); + regRAX = machine.getLanguage().getRegister("RAX"); + } + + @Override + protected Collection getAdditionalArchives() { + try { + ResourceFile file = + Application.findDataFileInAnyModule("typeinfo/generic/generic_clib_64.gdt"); + clib64 = FileDataTypeManager.openFileArchive(file, false); + return List.of(clib64); + } + catch (IOException e) { + throw new AssertionError(e); + } + } + + @Override + protected void disposeAdditionalArchives() { + clib64.close(); + } + + @Override + public long readSyscallNumber(PcodeExecutorStatePiece state) { + return machine.getArithmetic().toConcrete(state.getVar(regRAX)).longValue(); + } + + @Override + protected boolean returnErrno(PcodeExecutor executor, int errno) { + executor.getState() + .setVar(regRAX, + executor.getArithmetic().fromConst(-errno, regRAX.getMinimumByteSize())); + return true; + } +} 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 new file mode 100644 index 0000000000..c27f03c0c7 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java @@ -0,0 +1,127 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.linux; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; + +import generic.jar.ResourceFile; +import ghidra.framework.Application; +import ghidra.pcode.emu.DefaultPcodeThread; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.unix.EmuUnixFileSystem; +import ghidra.pcode.emu.unix.EmuUnixUser; +import ghidra.pcode.exec.*; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.data.FileDataTypeManager; +import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.Program; + +/** + * A system call library simulating Linux for x86 (32-bit) + * + * @param the type of values processed by the library + */ +public class EmuLinuxX86SyscallUseropLibrary extends AbstractEmuLinuxSyscallUseropLibrary { + protected final Register regEIP; + protected final Register regEAX; + + protected FileDataTypeManager clib32; + + /** + * Construct the system call library for Linux-x86 + * + * @param machine the machine emulating the hardware + * @param fs the file system to export to the user-space program + * @param program a program containing syscall definitions and conventions, likely the target + * program + */ + public EmuLinuxX86SyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, + Program program) { + this(machine, fs, program, EmuUnixUser.DEFAULT_USER); + } + + /** + * Construct the system call library for Linux-x86 + * + * @param machine the machine emulating the hardware + * @param fs the file system to export to the user-space program + * @param program a program containing syscall definitions and conventions, likely the target + * program + * @param user the "current user" to simulate + */ + public EmuLinuxX86SyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, + Program program, EmuUnixUser user) { + super(machine, fs, program, user); + regEIP = machine.getLanguage().getRegister("EIP"); + regEAX = machine.getLanguage().getRegister("EAX"); + } + + @Override + protected Collection getAdditionalArchives() { + try { + ResourceFile file = + Application.findDataFileInAnyModule("typeinfo/generic/generic_clib.gdt"); + clib32 = FileDataTypeManager.openFileArchive(file, false); + return List.of(clib32); + } + catch (IOException e) { + throw new AssertionError(e); + } + } + + @Override + protected void disposeAdditionalArchives() { + clib32.close(); + } + + @Override + public long readSyscallNumber(PcodeExecutorStatePiece state) { + return machine.getArithmetic().toConcrete(state.getVar(regEAX)).longValue(); + } + + @Override + protected boolean returnErrno(PcodeExecutor executor, int errno) { + executor.getState() + .setVar(regEAX, + executor.getArithmetic().fromConst(-errno, regEAX.getMinimumByteSize())); + return true; + } + + @PcodeUserop + public T swi(@OpExecutor PcodeExecutor executor, @OpLibrary PcodeUseropLibrary library, + T number) { + PcodeArithmetic arithmetic = executor.getArithmetic(); + long intNo = arithmetic.toConcrete(number).longValue(); + 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; + int pcSize = regEIP.getNumBytes(); + int iLen = te.getInstruction().getLength(); + next = arithmetic.binaryOp(PcodeArithmetic.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)); + } + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java new file mode 100644 index 0000000000..bcb1e5267a --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java @@ -0,0 +1,180 @@ +/* ### + * 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 java.lang.annotation.*; +import java.lang.reflect.Method; +import java.util.*; + +import org.apache.commons.collections4.BidiMap; +import org.apache.commons.collections4.bidimap.DualHashBidiMap; + +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary; +import ghidra.pcode.struct.StructuredSleigh; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeManager; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.lang.PrototypeModel; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import utilities.util.AnnotationUtilities; + +/** + * A syscall library wherein Java methods are exported via a special annotated + * + *

+ * This library is both a system call and a sleigh userop library. To export a system call, it must + * also be exported as a sleigh userop. This is more conventional, as the system call dispatcher + * does not require it, however, this library uses a wrapping technique that does require it. In + * general, exporting system calls as userops will make developers and users lives easier. To avoid + * naming collisions, system calls can be exported with customized names. + * + * @param the type of data processed by the library, typically {@code byte[]} + */ +public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcodeUseropLibrary + implements EmuSyscallLibrary { + public static final String SYSCALL_SPACE_NAME = "syscall"; + + protected static final Map, Set> CACHE_BY_CLASS = new HashMap<>(); + + private static Set collectSyscalls(Class cls) { + return AnnotationUtilities.collectAnnotatedMethods(EmuSyscall.class, cls); + } + + /** + * An annotation to export a method as a system call in the library. + * + *

+ * The method must also be exported in the userop library, likely via {@link PcodeUserop}. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface EmuSyscall { + String value(); + } + + private final SyscallPcodeUseropDefinition syscallUserop = + new SyscallPcodeUseropDefinition<>(this); + + protected final PcodeMachine machine; + protected final CompilerSpec cSpec; + + protected final Program program; + protected final DataType dtMachineWord; + protected final Map> syscallMap = new HashMap<>(); + + protected final Collection additionalArchives; + + /** + * Construct a new library including the "syscall" userop + * + * @param machine the machine using this library + * @param program a program from which to derive syscall configuration, conventions, etc. + */ + public AnnotatedEmuSyscallUseropLibrary(PcodeMachine machine, Program program) { + this.machine = machine; + this.program = program; + + this.cSpec = program.getCompilerSpec(); + // TODO: Take signatures / types from database + this.dtMachineWord = UseropEmuSyscallDefinition.requirePointerDataType(program); + mapAndBindSyscalls(); + + additionalArchives = getAdditionalArchives(); + StructuredSleigh structured = newStructuredPart(); + structured.generate(ops); + disposeAdditionalArchives(); + mapAndBindSyscalls(structured.getClass()); + } + + protected Collection getAdditionalArchives() { + return List.of(); + } + + protected void disposeAdditionalArchives() { + } + + /** + * Create the structured-sleigh part of this library + * + * @return the structured part + */ + protected StructuredPart newStructuredPart() { + return new StructuredPart(); + } + + /** + * Export a userop as a system call + * + * @param opdef the userop + * @return the syscall definition + */ + public UseropEmuSyscallDefinition newBoundSyscall(PcodeUseropDefinition opdef, + PrototypeModel convention) { + return new UseropEmuSyscallDefinition<>(opdef, program, convention, dtMachineWord); + } + + protected void mapAndBindSyscalls(Class cls) { + BidiMap mapNames = + new DualHashBidiMap<>(EmuSyscallLibrary.loadSyscallNumberMap(program)); + Map mapConventions = + EmuSyscallLibrary.loadSyscallConventionMap(program); + Set methods = collectSyscalls(cls); + for (Method m : methods) { + String name = m.getAnnotation(EmuSyscall.class).value(); + Long number = mapNames.getKey(name); + if (number == null) { + Msg.warn(cls, "Syscall " + name + " has no number"); + continue; + } + PcodeUseropDefinition opdef = getUserops().get(m.getName()); + if (opdef == null) { + throw new IllegalArgumentException("Method " + m.getName() + + " annotated with @" + EmuSyscall.class.getSimpleName() + + " must also be a p-code userop"); + } + PrototypeModel convention = mapConventions.get(number); + EmuSyscallDefinition existed = + syscallMap.put(number, newBoundSyscall(opdef, convention)); + if (existed != null) { + throw new IllegalArgumentException("Duplicate @" + + EmuSyscall.class.getSimpleName() + " annotated methods with name " + name); + } + } + } + + protected void mapAndBindSyscalls() { + mapAndBindSyscalls(this.getClass()); + } + + @Override + public PcodeUseropDefinition getSyscallUserop() { + return syscallUserop; + } + + @Override + public Map> getSyscalls() { + return syscallMap; + } + + protected class StructuredPart extends StructuredSleigh { + protected StructuredPart() { + super(program); + addDataTypeSources(additionalArchives); + } + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuIOException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuIOException.java new file mode 100644 index 0000000000..5c8a04da0a --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuIOException.java @@ -0,0 +1,37 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.sys; + +import java.io.IOException; + +/** + * The simulated system interrupted with an I/O error + * + *

+ * This exception is for I/O errors within the simulated system. If the host implementation causes a + * real {@link IOException}, it should not be wrapped in this exception unless, e.g., a + * simulated file system intends to proxy the real file system. + */ +public class EmuIOException extends EmuInvalidSystemCallException { + + public EmuIOException(String message, Throwable cause) { + super(message, cause); + } + + public EmuIOException(String message) { + super(message); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuInvalidSystemCallException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuInvalidSystemCallException.java new file mode 100644 index 0000000000..be3ccdbedb --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuInvalidSystemCallException.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.pcode.emu.sys; + +/** + * The emulated program invoked a system call incorrectly + */ +public class EmuInvalidSystemCallException extends EmuSystemException { + + /** + * The system call number was not valid + * + * @param number the system call number + */ + public EmuInvalidSystemCallException(long number) { + this("Invalid system call number: " + number); + } + + public EmuInvalidSystemCallException(String message) { + super(message); + } + + public EmuInvalidSystemCallException(String message, Throwable cause) { + super(message, null, cause); + } +} 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 new file mode 100644 index 0000000000..4a59a90661 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java @@ -0,0 +1,63 @@ +/* ### + * 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 java.math.BigInteger; + +import ghidra.pcode.exec.PcodeArithmetic; + +/** + * A simulated process (or thread group) has exited + */ +public class EmuProcessExitedException extends EmuSystemException { + + public static String tryConcereteToString(PcodeArithmetic arithmetic, T status) { + try { + BigInteger value = arithmetic.toConcrete(status); + return value.toString(); + } + catch (Exception e) { + return status.toString(); + } + } + + private final Object status; + + /** + * Construct a process-exited exception with the given status code + * + *

+ * This will attempt to concretize the status according to the given arithmetic, for display + * purposes. The original status remains accessible via {@link #getStatus()} + * + * @param the type values processed by the library + * @param arithmetic the machine's arithmetic + * @param status + */ + public EmuProcessExitedException(PcodeArithmetic arithmetic, T status) { + super("Process exited with status " + tryConcereteToString(arithmetic, status)); + this.status = status; + } + + /** + * Get the status code as a {@code T} of the throwing machine + * + * @return the status + */ + public Object getStatus() { + return status; + } +} 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 new file mode 100644 index 0000000000..25c9b47cca --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java @@ -0,0 +1,268 @@ +/* ### + * 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 java.io.*; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +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; +import ghidra.program.model.listing.Program; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.model.symbol.*; + +/** + * A library of system calls + * + *

+ * A system call library is a collection of p-code executable routines, invoked by a system call + * dispatcher. That dispatcher is represented by {@link #syscall(PcodeExecutor)}, and is exported as + * a sleigh userop. If this interface is "mixed in" with {@link AnnotatedPcodeUseropLibrary}, that + * 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. + * + * @param the type of data processed by the system calls, typically {@code byte[]} + */ +public interface EmuSyscallLibrary { + String SYSCALL_SPACE_NAME = "syscall"; + String SYSCALL_CONVENTION_NAME = "syscall"; + + /** + * Derive a syscall number to name map from the specification in a given file. + * + * @param dataFileName the file name to be found in a modules data directory + * @return the map + * @throws IOException if the file could not be read + */ + public static Map loadSyscallNumberMap(String dataFileName) throws IOException { + ResourceFile mapFile = Application.findDataFileInAnyModule(dataFileName); + if (mapFile == null) { + throw new FileNotFoundException("Cannot find syscall number map: " + dataFileName); + } + Map result = new HashMap<>(); + + final BufferedReader reader = + new BufferedReader(new InputStreamReader(mapFile.getInputStream())); + String line; + while (null != (line = reader.readLine())) { + line = line.strip(); + if (line.startsWith("#")) { + continue; + } + String[] parts = line.split("\\s+"); + if (parts.length != 2) { + throw new IOException( + "Badly formatted syscall number map: " + dataFileName + ". Line: " + line); + } + try { + result.put(Long.parseLong(parts[0]), parts[1]); + } + catch (NumberFormatException e) { + throw new IOException("Badly formatted syscall number map: " + dataFileName, e); + } + } + return result; + } + + /** + * Scrape functions from the given program's "syscall" space. + * + * @param program the program + * @return a map of syscall number to function + */ + public static Map loadSyscallFunctionMap(Program program) { + AddressSpace space = program.getAddressFactory().getAddressSpace(SYSCALL_SPACE_NAME); + if (space == null) { + throw new IllegalStateException( + "No syscall address space in program. Please analyze the syscalls first."); + } + Map result = new HashMap<>(); + SymbolIterator sit = + program.getSymbolTable().getSymbolIterator(space.getMinAddress(), true); + while (sit.hasNext()) { + Symbol s = sit.next(); + if (s.getAddress().getAddressSpace() != space) { + break; + } + if (s.getSymbolType() != SymbolType.FUNCTION) { + continue; + } + result.put(s.getAddress().getOffset(), (Function) s.getObject()); + } + return result; + } + + /** + * Derive a syscall number to name map by scraping functions in the program's "syscall" space. + * + * @param program the program, likely analyzed for system calls already + * @return the map + */ + public static Map loadSyscallNumberMap(Program program) { + return loadSyscallFunctionMap(program).entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getName())); + } + + /** + * Derive a syscall number to calling convention map by scraping functions in the program's + * "syscall" space. + * + * @param program + * @return + */ + public static Map loadSyscallConventionMap(Program program) { + return loadSyscallFunctionMap(program).entrySet() + .stream() + .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getCallingConvention())); + } + + /** + * The {@link EmuSyscallLibrary#syscall(PcodeExecutor)} method wrapped as a userop definition + * + * @param the type of data processed by the userop, typically {@code byte[]} + */ + final class SyscallPcodeUseropDefinition implements PcodeUseropDefinition { + private final EmuSyscallLibrary syslib; + + public SyscallPcodeUseropDefinition(EmuSyscallLibrary syslib) { + this.syslib = syslib; + } + + @Override + public String getName() { + return "syscall"; + } + + @Override + public int getInputCount() { + return 0; + } + + @Override + public void execute(PcodeExecutor executor, PcodeUseropLibrary library, + Varnode outVar, List inVars) { + syslib.syscall(executor, library); + } + } + + /** + * The definition of a system call + * + * @param the type of data processed by the system call, typically {@code byte[]}. + */ + interface EmuSyscallDefinition { + /** + * Invoke the system call + * + * @param executor the executor for the system/thread invoking the call + * @param library the complete sleigh userop library for the system + */ + void invoke(PcodeExecutor executor, PcodeUseropLibrary library); + } + + /** + * In case this is not an {@link AnnotatedEmuSyscallUseropLibrary} or + * {@link AnnotatedPcodeUseropLibrary}, get the definition of the "syscall" userop for inclusion + * in the {@link PcodeUseropLibrary}. + * + *

+ * Implementors may wish to override this to use a pre-constructed definition. That definition + * can be easily constructed using {@link SyscallPcodeUseropDefinition}. + * + * @return the syscall userop definition + */ + default PcodeUseropDefinition getSyscallUserop() { + return new SyscallPcodeUseropDefinition<>(this); + }; + + /** + * Retrieve the desired system call number according to the emulated system's conventions + * + *

+ * TODO: This should go away in favor of some specification stored in the emulated program + * database. Until then, we require system-specific implementations. + * + * @param state the executor's state + * @return the system call number + */ + long readSyscallNumber(PcodeExecutorStatePiece state); + + /** + * Try to handle an error, usually by returning it to the user program + * + *

+ * If the particular error was not expected, it is best practice to return false, causing the + * emulator to interrupt. Otherwise, some state is set in the machine that, by convention, + * communicates the error back to the user program. + * + * @param executor the executor for the thread that caused the error + * @param err the error + * @return true if execution can continue uninterrupted + */ + boolean handleError(PcodeExecutor executor, PcodeExecutionException err); + + /** + * The entry point for executing a system call on the given executor + * + *

+ * The executor's state must already be prepared according to the relevant system calling + * conventions. This will determine the system call number, according to + * {@link #readSyscallNumber(PcodeExecutorStatePiece)}, retrieve the relevant system call + * definition, and invoke it. + * + * @param executor the executor + * @param library the library + */ + @PcodeUserop + default void syscall(@OpExecutor PcodeExecutor executor, + @OpLibrary PcodeUseropLibrary library) { + long syscallNumber = readSyscallNumber(executor.getState()); + EmuSyscallDefinition syscall = getSyscalls().get(syscallNumber); + if (syscall == null) { + throw new EmuInvalidSystemCallException(syscallNumber); + } + try { + syscall.invoke(executor, library); + } + catch (PcodeExecutionException e) { + if (!handleError(executor, e)) { + throw e; + } + } + } + + /** + * Get the map of syscalls by number + * + *

+ * Note this method will be invoked for every emulated syscall, so it should be a simple + * accessor. Any computations needed to create the map should be done ahead of time. + * + * @return the system call map + */ + Map> getSyscalls(); +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSystemException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSystemException.java new file mode 100644 index 0000000000..bc3f41caef --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/EmuSystemException.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.emu.sys; + +import ghidra.pcode.exec.PcodeExecutionException; +import ghidra.pcode.exec.PcodeFrame; + +/** + * A p-code execution exception related to system simulation + */ +public class EmuSystemException extends PcodeExecutionException { + public EmuSystemException(String message) { + super(message); + } + + public EmuSystemException(String message, PcodeFrame frame) { + super(message, frame); + } + + public EmuSystemException(String message, PcodeFrame frame, Throwable cause) { + super(message, frame, cause); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java new file mode 100644 index 0000000000..2445942ccd --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java @@ -0,0 +1,121 @@ +/* ### + * 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 java.util.Arrays; +import java.util.List; + +import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.sys.EmuSyscallLibrary.EmuSyscallDefinition; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; +import ghidra.program.model.data.DataType; +import ghidra.program.model.lang.PrototypeModel; +import ghidra.program.model.listing.Program; +import ghidra.program.model.listing.VariableStorage; +import ghidra.program.model.pcode.Varnode; + +/** + * A system call that is defined by delegating to a p-code userop + * + *

+ * This is essentially a wrapper of the p-code userop. Knowing the number of inputs to the userop + * and by applying the calling conventions of the platform, the wrapper aliases each parameter's + * storage to its respective parameter of the userop. The userop's output is also aliased to the + * system call's return storage, again as defined by the platform's conventions. + * + * @see AnnotatedEmuSyscallUseropLibrary + * @param the type of values processed by the library + */ +public class UseropEmuSyscallDefinition implements EmuSyscallDefinition { + + /** + * Obtain the program's "pointer" data type, throwing an exception if absent + * + * @param program the program + * @return the "pointer" data type + */ + protected static DataType requirePointerDataType(Program program) { + DataType dtPointer = program.getDataTypeManager().getDataType("/pointer"); + if (dtPointer == null) { + throw new IllegalArgumentException("No 'pointer' data type in " + program); + } + return dtPointer; + } + + protected final PcodeUseropDefinition opdef; + protected final List inVars; + protected final Varnode outVar; + + /** + * Construct a syscall definition + * + * @see AnnotatedEmuSyscallUseropLibrary + * @param opdef the wrapped userop definition + * @param program the program, used for storage computation + * @param convention the "syscall" calling convention + * @param dtMachineWord the "pointer" data type + */ + public UseropEmuSyscallDefinition(PcodeUseropDefinition opdef, Program program, + PrototypeModel convention, DataType dtMachineWord) { + this.opdef = opdef; + + // getStorageLocations needs return(1) + parameters(n) + int inputCount = opdef.getInputCount(); + if (inputCount < 0) { + throw new IllegalArgumentException("Variadic sleigh userop " + opdef.getName() + + " cannot be used as a syscall"); + } + DataType[] locs = new DataType[inputCount + 1]; + for (int i = 0; i < locs.length; i++) { + locs[i] = dtMachineWord; + } + VariableStorage[] vss = convention.getStorageLocations(program, locs, false); + + outVar = getSingleVnStorage(vss[0]); + inVars = Arrays.asList(new Varnode[inputCount]); + for (int i = 0; i < inputCount; i++) { + inVars.set(i, getSingleVnStorage(vss[i + 1])); + } + } + + /** + * Assert variable storage is a single varnode, and get that varnode + * + * @param vs the storage + * @return the single varnode + */ + protected Varnode getSingleVnStorage(VariableStorage vs) { + Varnode[] vns = vs.getVarnodes(); + if (vns.length != 1) { + Unfinished.TODO(); + } + return vns[0]; + } + + @Override + public void invoke(PcodeExecutor executor, PcodeUseropLibrary library) { + try { + opdef.execute(executor, library, outVar, inVars); + } + catch (PcodeExecutionException e) { + throw e; + } + catch (Throwable e) { + throw new EmuSystemException("Error during syscall", null, e); + } + } +} 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 new file mode 100644 index 0000000000..cd55065e9e --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.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.unix; + +/** + * An abstract file contained in an emulated file system + * + *

+ * Contrast this with {@link DefaultEmuUnixFileHandle}, which is a particular process's handle when + * opening the file, not the file itself. + * + * @param the type of values stored in the file + */ +public abstract class AbstractEmuUnixFile implements EmuUnixFile { + protected final String pathname; + protected final EmuUnixFileStat stat = createStat(); + + /** + * Construct a new file + * + *

+ * TODO: Technically, a file can be hardlinked to several pathnames, but for simplicity, or for + * diagnostics, we let the file know its own original name. + * + * @see AbstractEmuUnixFileSystem#newFile(String) + * @param pathname the pathname of the file + * @param mode the mode of the file + */ + public AbstractEmuUnixFile(String pathname, int mode) { + this.pathname = pathname; + stat.st_mode = mode; + } + + /** + * A factory method for the file's {@code stat} structure. + * + * @return the stat structure. + */ + protected EmuUnixFileStat createStat() { + return new EmuUnixFileStat(); + } + + @Override + public String getPathname() { + return pathname; + } + + @Override + public EmuUnixFileStat getStat() { + return stat; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFileSystem.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFileSystem.java new file mode 100644 index 0000000000..7350ca46cb --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFileSystem.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.unix; + +import java.util.*; + +import ghidra.pcode.emu.sys.EmuIOException; + +/** + * An abstract emulated file system, exported to an emulated user-space program + * + * @param the type of values stored in the file system + */ +public abstract class AbstractEmuUnixFileSystem implements EmuUnixFileSystem { + protected final Map> filesByPath = new HashMap<>(); + + @Override + public abstract AbstractEmuUnixFile newFile(String pathname, int mode) throws EmuIOException; + + @Override + public synchronized EmuUnixFile createOrGetFile(String pathname, int mode) + throws EmuIOException { + return filesByPath.computeIfAbsent(pathname, p -> newFile(p, mode)); + } + + @Override + public synchronized EmuUnixFile getFile(String pathname) throws EmuIOException { + return filesByPath.get(pathname); + } + + @Override + public synchronized void putFile(String pathname, EmuUnixFile file) throws EmuIOException { + filesByPath.put(pathname, file); + } + + @Override + public synchronized EmuUnixFile open(String pathname, Set flags, EmuUnixUser user, + int mode) throws EmuIOException { + EmuUnixFile file = + flags.contains(OpenFlag.O_CREAT) ? createOrGetFile(pathname, mode) : getFile(pathname); + + if (file == null) { + throw new EmuIOException("File not found: " + pathname); + } + if (flags.contains(OpenFlag.O_RDONLY) || flags.contains(OpenFlag.O_RDWR)) { + file.checkReadable(user); + } + if (flags.contains(OpenFlag.O_WRONLY) || flags.contains(OpenFlag.O_RDWR)) { + file.checkWritable(user); + if (flags.contains(OpenFlag.O_TRUNC)) { + file.truncate(); + } + } + return file; + } + + @Override + public synchronized void unlink(String pathname, EmuUnixUser user) throws EmuIOException { + filesByPath.remove(pathname); + } +} 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 new file mode 100644 index 0000000000..c0a9eeb8a8 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java @@ -0,0 +1,312 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.unix; + +import java.util.*; + +import ghidra.docking.settings.SettingsImpl; +import ghidra.pcode.emu.PcodeMachine; +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.program.model.address.AddressSpace; +import ghidra.program.model.data.StringDataInstance; +import ghidra.program.model.data.StringDataType; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemBuffer; + +/** + * An abstract library of UNIX system calls, suitable for use with any processor + * + *

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

+ * See a UNIX manual for their exact meaning + */ + public enum Errno { + EBADF; + } + + protected final EmuUnixFileSystem fs; + protected EmuUnixUser user; + + protected final int intSize; + protected final NavigableSet closedFds = new TreeSet<>(); + protected final Map> descriptors = new HashMap<>(); + + /** + * Construct a new library + * + * @param machine the machine emulating the hardware + * @param fs the file system to export to the user-space program + * @param program a program containing the syscall definitions and conventions, likely the + * target program + */ + public AbstractEmuUnixSyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, + Program program) { + this(machine, fs, program, EmuUnixUser.DEFAULT_USER); + } + + /** + * Construct a new library + * + * @param machine the machine emulating the hardware + * @param fs a file system to export to the user-space program + * @param program a program containing the syscall definitions and conventions, likely the + * target program + * @param user the "current user" to simulate + */ + public AbstractEmuUnixSyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, + Program program, EmuUnixUser user) { + super(machine, program); + this.fs = fs; + this.user = user; + this.intSize = program.getCompilerSpec().getDataOrganization().getIntegerSize(); + } + + protected int lowestFd() { + Integer lowest = closedFds.pollFirst(); + if (lowest != null) { + return lowest; + } + return descriptors.size(); + } + + protected int claimFd(EmuUnixFileDescriptor desc) { + synchronized (descriptors) { + int fd = lowestFd(); + putDescriptor(fd, desc); + return fd; + } + } + + protected EmuUnixFileDescriptor findFd(int fd) { + synchronized (descriptors) { + EmuUnixFileDescriptor desc = descriptors.get(fd); + if (desc == null) { + throw new EmuUnixException("Invalid descriptor: " + fd, getErrno(Errno.EBADF)); + } + return desc; + } + } + + protected EmuUnixFileDescriptor releaseFd(int fd) { + synchronized (descriptors) { + if (descriptors.size() + closedFds.size() - 1 == fd) { + return descriptors.remove(fd); + } + EmuUnixFileDescriptor removed = descriptors.remove(fd); + if (removed == null) { + throw new EmuUnixException("Invalid descriptor: " + fd, getErrno(Errno.EBADF)); + } + closedFds.add(fd); + return removed; + } + } + + @Override + protected StructuredPart newStructuredPart() { + return new UnixStructuredPart(); + } + + /** + * Convert the flags as defined for this platform to flags understood by the simulator + * + * @param flags the platform-defined flags + * @return the simulator-defined flags + */ + protected abstract Set convertFlags(int flags); + + /** + * A factory method for creating an open file handle + * + * @param file the file opened by the handle + * @param flags the open flags, as specified by the user, as defined by the platform + * @return the handle + */ + protected EmuUnixFileDescriptor createHandle(EmuUnixFile file, int flags) { + return new DefaultEmuUnixFileHandle<>(machine, cSpec, file, convertFlags(flags), user); + } + + /** + * Get the platform-specific errno value for the given simulator-defined errno + * + * @param err the simulator-defined errno + * @return the platform-defined errno + */ + protected abstract int getErrno(Errno err); + + /** + * Put a descriptor into the process' open file handles + * + * @param fd the file descriptor value + * @param desc the simulated descriptor (handle, console, etc.) + * @return the previous descriptor, which probably ought to be {@code null} + */ + public EmuUnixFileDescriptor putDescriptor(int fd, EmuUnixFileDescriptor desc) { + synchronized (descriptors) { + return descriptors.put(fd, desc); + } + } + + protected abstract boolean returnErrno(PcodeExecutor executor, int errno); + + @Override + public boolean handleError(PcodeExecutor executor, PcodeExecutionException err) { + if (err instanceof EmuUnixException) { + Integer errno = ((EmuUnixException) err).getErrno(); + if (errno == null) { + return false; + } + return returnErrno(executor, errno); + } + return false; + } + + @PcodeUserop + @EmuSyscall("exit") + public T unix_exit(T status) { + throw new EmuProcessExitedException(machine.getArithmetic(), status); + } + + @PcodeUserop + @EmuSyscall("read") + public T unix_read(@OpState PcodeExecutorStatePiece state, T fd, T bufPtr, T count) { + PcodeArithmetic arithmetic = machine.getArithmetic(); + int ifd = arithmetic.toConcrete(fd).intValue(); + EmuUnixFileDescriptor desc = findFd(ifd); + AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); + int size = arithmetic.toConcrete(count).intValue(); // TODO: Not idea to require concrete size + T buf = arithmetic.fromConst(0, size); + T result = desc.read(buf); + int iresult = arithmetic.toConcrete(result).intValue(); + state.setVar(space, bufPtr, iresult, true, buf); + return result; + } + + @PcodeUserop + @EmuSyscall("write") + public T unix_write(@OpState PcodeExecutorStatePiece state, T fd, T bufPtr, T count) { + PcodeArithmetic arithmetic = machine.getArithmetic(); + int ifd = arithmetic.toConcrete(fd).intValue(); + 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(); + T buf = state.getVar(space, bufPtr, size, true); + // TODO: Write back into state? "write" shouldn't touch the buffer.... + return desc.write(buf); + } + + @PcodeUserop + @EmuSyscall("open") + public T unix_open(@OpState PcodeExecutorStatePiece 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(); + AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); + + SettingsImpl settings = new SettingsImpl(); + MemBuffer buffer = state.getConcreteBuffer(space.getAddress(pathnameOff)); + StringDataInstance sdi = + new StringDataInstance(StringDataType.dataType, settings, buffer, -1); + sdi = new StringDataInstance(StringDataType.dataType, settings, buffer, + sdi.getStringLength()); + // TODO: Can NPE here be mapped to a unix error + String pathname = Objects.requireNonNull(sdi.getStringValue()); + EmuUnixFile file = fs.open(pathname, convertFlags(iflags), user, imode); + int ifd = claimFd(createHandle(file, iflags)); + return arithmetic.fromConst(ifd, intSize); + } + + @PcodeUserop + @EmuSyscall("close") + public T unix_close(T fd) { + PcodeArithmetic arithmetic = machine.getArithmetic(); + int ifd = arithmetic.toConcrete(fd).intValue(); + // TODO: Some fs.close or file.close, when all handles have released it? + EmuUnixFileDescriptor desc = releaseFd(ifd); + desc.close(); + return arithmetic.fromConst(0, intSize); + } + + @PcodeUserop + @EmuSyscall("group_exit") + public void unix_group_exit(T status) { + throw new EmuProcessExitedException(machine.getArithmetic(), status); + } + + protected class UnixStructuredPart extends StructuredPart { + final UseropDecl unix_read = userop(type("size_t"), "unix_read", + types("int", "void *", "size_t")); + final UseropDecl unix_write = userop(type("size_t"), "unix_write", + types("int", "void *", "size_t"));; + + /** + * Inline the gather or scatter pattern for an iovec syscall + * + *

+ * 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. + */ + protected void gatherScatterIovec(Var in_fd, Var in_iovec, Var in_iovcnt, + UseropDecl subOp) { + Var tmp_i = local("tmp_i", type("size_t")); + Var tmp_total = local("tmp_total", type("size_t")); + Var tmp_ret = local("tmp_ret", type("size_t")); + + _for(tmp_i.set(0), tmp_i.ltiu(in_iovcnt), tmp_i.inc(), () -> { + Var tmp_io = local("tmp_io", in_iovec.index(tmp_i)); + Var tmp_base = local("tmp_base", tmp_io.field("iov_base").deref()); + Var tmp_len = local("tmp_len", tmp_io.field("iov_len").deref()); + tmp_ret.set(subOp.call(in_fd, tmp_base, tmp_len)); + tmp_total.addiTo(tmp_ret); + _if(tmp_ret.ltiu(tmp_len), () -> _break()); // We got less than this buffer + }); + _result(tmp_total); + } + + @StructuredUserop(type = "size_t") + @EmuSyscall("readv") + public void unix_readv(@Param(type = "int", name = "in_fd") Var in_fd, + @Param(type = "iovec *", name = "in_iovec") Var in_iovec, + @Param(type = "size_t", name = "in_iovcnt") Var in_iovcnt) { + gatherScatterIovec(in_fd, in_iovec, in_iovcnt, unix_read); + } + + @StructuredUserop(type = "size_t") + @EmuSyscall("writev") + public void unix_writev(@Param(type = "int", name = "in_fd") Var in_fd, + @Param(type = "iovec *", name = "in_iovec") Var in_iovec, + @Param(type = "size_t", name = "in_iovcnt") Var in_iovcnt) { + gatherScatterIovec(in_fd, in_iovec, in_iovcnt, unix_write); + } + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractStreamEmuUnixFileHandle.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractStreamEmuUnixFileHandle.java new file mode 100644 index 0000000000..94648df1f8 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/AbstractStreamEmuUnixFileHandle.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.unix; + +import ghidra.lifecycle.Unfinished; +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.sys.EmuIOException; +import ghidra.pcode.exec.PcodeArithmetic; +import ghidra.program.model.lang.CompilerSpec; + +/** + * An abstract file descriptor having no "offset," typically for stream-like files + * + * @param the type of values in the file + */ +public abstract class AbstractStreamEmuUnixFileHandle implements EmuUnixFileDescriptor { + protected final PcodeArithmetic arithmetic; + protected final int offsetBytes; + + private final T offset; + + /** + * Construct a new handle + * + * @see AbstractEmuUnixSyscallUseropLibrary#createHandle(int, EmuUnixFile, int) + * @param machine the machine emulating the hardware + * @param cSpec the ABI of the target platform + */ + public AbstractStreamEmuUnixFileHandle(PcodeMachine machine, CompilerSpec cSpec) { + this.arithmetic = machine.getArithmetic(); + this.offsetBytes = cSpec.getDataOrganization().getLongSize(); // off_t's fundamental type + this.offset = arithmetic.fromConst(0, offsetBytes); + } + + @Override + public T getOffset() { + return offset; + } + + @Override + public void seek(T offset) throws EmuIOException { + // No effect + } + + @Override + public EmuUnixFileStat stat() { + return Unfinished.TODO(); + } +} 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 new file mode 100644 index 0000000000..f401c76ad6 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/BytesEmuUnixFileSystem.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.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; + +/** + * A concrete in-memory file system simulator suitable for UNIX programs + */ +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? + } + } + + /** + * Construct a new concrete simulated file system + */ + public BytesEmuUnixFileSystem() { + } + + @Override + public BytesEmuUnixFile newFile(String pathname, int mode) { + return new BytesEmuUnixFile(pathname, mode); + } +} 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 new file mode 100644 index 0000000000..6b07615aeb --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java @@ -0,0 +1,133 @@ +/* ### + * 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.unix; + +import java.util.Set; + +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; + +/** + * A file descriptor associated with a file on a simulated UNIX file system + * + * @param the type of values stored by the file + */ +public class DefaultEmuUnixFileHandle implements EmuUnixFileDescriptor { + + protected final PcodeArithmetic arithmetic; + protected final EmuUnixFile file; + // TODO: T flags? Meh. + protected final Set flags; + protected final EmuUnixUser user; + protected final int offsetBytes; + + private T offset; + + /** + * Construct a new handle on the given file + * + * @see AbstractEmuUnixSyscallUseropLibrary#createHandle(int, EmuUnixFile, int) + * @param machine the machine emulating the hardware + * @param cSpec the ABI of the target platform + * @param file the file opened by this handle + * @param flags the user-specified flags, as defined by the simulator + * @param user the user that opened the file + */ + public DefaultEmuUnixFileHandle(PcodeMachine machine, CompilerSpec cSpec, + EmuUnixFile file, Set flags, EmuUnixUser user) { + this.arithmetic = machine.getArithmetic(); + this.file = file; + this.flags = flags; + this.user = user; + this.offsetBytes = cSpec.getDataOrganization().getLongSize(); // off_t's fundamental type + + this.offset = arithmetic.fromConst(0, offsetBytes); + } + + /** + * Check if the file is readable, throwing {@link EmuIOException} if not + */ + public void checkReadable() { + if (!OpenFlag.isRead(flags)) { + throw new EmuIOException("File not opened for reading"); + } + } + + /** + * Check if the file is writable, throwing {@link EmuIOException} if not + */ + public void checkWritable() { + if (!OpenFlag.isWrite(flags)) { + throw new EmuIOException("File not opened for writing"); + } + } + + /** + * Advance the handle's offset (negative to rewind) + * + * @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); + } + + @Override + public T getOffset() { + return offset; + } + + @Override + public void seek(T offset) throws EmuIOException { + // TODO: Where does bounds check happen? + this.offset = offset; + } + + @Override + public T read(T buf) throws EmuIOException { + checkReadable(); + T len = file.read(arithmetic, offset, buf); + advanceOffset(len); + return len; + } + + @Override + public T write(T buf) throws EmuIOException { + checkWritable(); + if (flags.contains(OpenFlag.O_APPEND)) { + offset = arithmetic.fromConst(file.getStat().st_size, offsetBytes); + } + T len = file.write(arithmetic, offset, buf); + advanceOffset(len); + return len; + } + + @Override + public EmuUnixFileStat stat() { + return file.getStat(); + } + + @Override + public void close() { + // TODO: Let the file know a handle was closed? + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixException.java new file mode 100644 index 0000000000..46d4fc329e --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixException.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.unix; + +import ghidra.pcode.emu.sys.EmuSystemException; + +/** + * An exception for errors within UNIX sytem call libraries + */ +public class EmuUnixException extends EmuSystemException { + + private final Integer errno; + + public EmuUnixException(String message) { + this(message, null, null); + } + + public EmuUnixException(String message, Throwable e) { + this(message, null, e); + } + + public EmuUnixException(String message, Integer errno) { + this(message, errno, null); + } + + /** + * Construct a new exception with an optional errno + * + *

+ * Providing an errno allows the system call dispatcher to automatically communicate errno to + * the target program. If provided, the exception will not interrupt the emulator, because the + * target program is expected to handle it. If omitted, the dispatcher simply allows the + * exception to interrupt the emulator. + * + * @param message the message + * @param errno the errno, or {@code null} + * @param e the cause of this exception, or {@code null} + */ + public EmuUnixException(String message, Integer errno, Throwable e) { + super(message, null, e); + this.errno = errno; + } + + /** + * Get the errno associated with this exception + * + * @return the errno, or {@code null} + */ + public Integer getErrno() { + return errno; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFile.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFile.java new file mode 100644 index 0000000000..e57837e2ae --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFile.java @@ -0,0 +1,126 @@ +/* ### + * 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.unix; + +import ghidra.pcode.emu.sys.EmuIOException; +import ghidra.pcode.exec.PcodeArithmetic; + +/** + * A simulated UNIX file + * + *

+ * Contrast this with {@link EmuUnixFileDescriptor}, which is a process's handle to an open file, + * not the file itself. + * + * @param the type of values stored in the file + */ +public interface EmuUnixFile { + + /** + * Get the original pathname of this file + * + *

+ * Depending on the fidelity of the file system simulator, and the actions taken by the target + * program, the file may no longer actually exist at this path, but it ought be have been the + * pathname at some point in the file life. + * + * @return the pathname + */ + String getPathname(); + + /** + * Read contents from the file starting at the given offset into the given buffer + * + *

+ * This roughly follows the semantics of the UNIX {@code read()}. While the offset and return + * value may depend on the arithmetic, the actual contents read from the file should not. + * + * @param arithmetic the arithmetic + * @param offset the offset + * @param buf the buffer + * @return the number of bytes read + */ + T read(PcodeArithmetic arithmetic, T offset, T buf); + + /** + * Write contents into the file starting at the given offset from the given buffer + * + *

+ * This roughly follows the semantics of the UNIX {@code write()}. While the offset and return + * value may depend on the arithmetic, the actual contents written to the file should not. + * + * @param arithmetic the arithmetic + * @param offset the offset + * @param buf the buffer + * @return the number of bytes written + */ + T write(PcodeArithmetic arithmetic, T offset, T buf); + + /** + * Erase the contents of the file + */ + void truncate(); + + /** + * Get the file's {@code stat} structure, as defined by the simulator. + * + * @return the stat + */ + EmuUnixFileStat getStat(); + + /** + * Check if the given user can read this file + * + * @param user the user + * @return true if permitted, false otherwise + */ + default boolean isReadable(EmuUnixUser user) { + return getStat().hasPermissions(EmuUnixFileStat.MODE_R, user); + } + + /** + * Check if the given user can write this file + * + * @param user the user + * @return true if permitted, false otherwise + */ + default boolean isWritable(EmuUnixUser user) { + return getStat().hasPermissions(EmuUnixFileStat.MODE_W, user); + } + + /** + * Require the user to have read permission on this file, throwing {@link EmuIOException} if not + * + * @param user the user + */ + default void checkReadable(EmuUnixUser user) { + if (!isReadable(user)) { + throw new EmuIOException("The file " + getPathname() + " cannot be read."); + } + } + + /** + * Require the user to have write permission on this file, throwing {@link EmuIOException} if + * not + * + * @param user the user + */ + default void checkWritable(EmuUnixUser user) { + if (!isWritable(user)) { + throw new EmuIOException("The file " + getPathname() + " cannot be written."); + } + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileDescriptor.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileDescriptor.java new file mode 100644 index 0000000000..cf4d890b14 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileDescriptor.java @@ -0,0 +1,81 @@ +/* ### + * 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.unix; + +import ghidra.pcode.emu.sys.EmuIOException; + +/** + * A process's handle to a file (or other resource) + * + * @param the type of values stored in the file + */ +public interface EmuUnixFileDescriptor { + /** + * The default file descriptor for stdin (standard input) + */ + int FD_STDIN = 0; + /** + * The default file descriptor for stdout (standard output) + */ + int FD_STDOUT = 1; + /** + * The default file descriptor for stderr (standard error output) + */ + int FD_STDERR = 2; + + /** + * Get the current offset of the file, or 0 if not applicable + * + * @return the offset + */ + T getOffset(); + + /** + * See to the given offset + * + * @param offset the desired offset + * @throws EmuIOException if an error occurred + */ + void seek(T offset) throws EmuIOException; + + /** + * Read from the file opened by this handle + * + * @param buf the destination buffer + * @return the number of bytes read + * @throws EmuIOException if an error occurred + */ + T read(T buf) throws EmuIOException; + + /** + * Read into the file opened by this handle + * + * @param buf the source buffer + * @return the number of bytes written + * @throws EmuIOException if an error occurred + */ + T write(T buf) throws EmuIOException; + + /** + * Obtain the {@code stat} structure of the file opened by this handle + */ + EmuUnixFileStat stat(); + + /** + * Close this descriptor + */ + void close(); +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileStat.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileStat.java new file mode 100644 index 0000000000..84f01d85c5 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileStat.java @@ -0,0 +1,83 @@ +/* ### + * 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.unix; + +/** + * Collects the {@code stat} fields common to UNIX platforms + * + *

+ * See a UNIX manual for the exact meaning of each field. + * + *

+ * TODO: Should this be parameterized with T? + * + *

+ * TODO: Are these specific to Linux, or all UNIX? + */ +public class EmuUnixFileStat { + + /** + * The mode bit indicating read permission + */ + public static final int MODE_R = 04; + /** + * The mode bit indicating write permission + */ + public static final int MODE_W = 02; + /** + * The mode bit indicating execute permission + */ + public static final int MODE_X = 01; + + public long st_dev; + public long st_ino; + public int st_mode; + public long st_nlink; + public int st_uid; + public int st_gid; + public long st_rdev; + public long st_size; + public long st_blksize; + public long st_blocks; + + public long st_atim_sec; + public long st_atim_nsec; + public long st_mtim_sec; + public long st_mtim_nsec; + public long st_ctim_sec; + public long st_ctim_nsec; + + /** + * Check if the given user has the requested permissions on the file described by this stat + * + * @param req the requested permissions + * @param user the user requesting permission + * @return true if permitted, false if denied + */ + public boolean hasPermissions(int req, EmuUnixUser user) { + // TODO: Care to simulate 'root'? + if ((st_mode & req) == req) { + return true; + } + if (((st_mode >> 6) & req) == req && user.uid == st_uid) { + return true; + } + if (((st_mode >> 3) & req) == req && user.gids.contains(st_gid)) { + return true; + } + return false; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileSystem.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileSystem.java new file mode 100644 index 0000000000..8115f7a257 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileSystem.java @@ -0,0 +1,167 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.unix; + +import java.util.*; + +import ghidra.pcode.emu.sys.EmuIOException; + +/** + * A simulated UNIX file system + * + * @param the type of values stored in the files + */ +public interface EmuUnixFileSystem { + /** + * Open flags as defined by the simulator + * + *

+ * See a UNIX manual for the exact meaning of each. + */ + enum OpenFlag { + O_RDONLY, + O_WRONLY, + O_RDWR, + O_CREAT, + O_TRUNC, + O_APPEND; + + /** + * Construct a set of flags + * + * @param flags the flags + * @return the set + */ + public static Set set(OpenFlag... flags) { + return set(Arrays.asList(flags)); + } + + /** + * Construct a set of flags + * + * @param flags the flags + * @return the set + */ + public static Set set(Collection flags) { + if (flags.contains(O_RDONLY) && flags.contains(O_WRONLY)) { + throw new IllegalArgumentException("Cannot be read only and write only"); + } + if (flags instanceof EnumSet) { + return Collections.unmodifiableSet((EnumSet) flags); + } + return Collections.unmodifiableSet(EnumSet.copyOf(flags)); + } + + /** + * Check if the given flags indicate open for reading + * + * @param flags the flags + * @return true for reading + */ + public static boolean isRead(Collection flags) { + return flags.contains(OpenFlag.O_RDONLY) || flags.contains(OpenFlag.O_RDWR); + } + + /** + * Check if the given flags indicate open for writing + * + * @param flags the flags + * @return true for writing + */ + public static boolean isWrite(Collection flags) { + return flags.contains(OpenFlag.O_WRONLY) || flags.contains(OpenFlag.O_RDWR); + } + } + + /** + * A factory for constructing a new file (without adding it to the file system) + * + * @param pathname the path of the file + * @param the mode of the new file + * @return the new file + * @throws EmuIOException if the file cannot be constructed + */ + EmuUnixFile newFile(String pathname, int mode) throws EmuIOException; + + /** + * Get the named file, creating it if it doesn't already exist + * + *

+ * This is accessed by the emulator user, not the target program. + * + * @param pathname the pathname of the requested file + * @param mode the mode of a created file. Ignored if the file exists + * @return the file + * @throws EmuIOException if an error occurred + */ + EmuUnixFile createOrGetFile(String pathname, int mode) throws EmuIOException; + + /** + * Get the named file + * + *

+ * This is accessed by the emulator user, not the target program. + * + * @param pathname the pathname of the requested file + * @return the file, or {@code null} if it doesn't exist + * @throws EmuIOException if an error occurred + */ + EmuUnixFile getFile(String pathname) throws EmuIOException; + + /** + * Place the given file at the given location + * + *

+ * This is accessed by the emulator user, not the target program. If the file already exists, it + * is replaced silently. + * + * @param pathname the pathname of the file + * @param file the file, presumably having the same pathname + * @throws EmuIOException if an error occurred + */ + void putFile(String pathname, EmuUnixFile file) throws EmuIOException; + + /** + * Remove the file at the given location + * + *

+ * TODO: Separate the user-facing routine from the target-facing routine. + * + *

+ * If the file does not exist, this has no effect. + * + * @param pathname the pathname of the file to unlink + * @param user the user requesting the unlink + * @throws EmuIOException if an error occurred + */ + void unlink(String pathname, EmuUnixUser user) throws EmuIOException; + + /** + * Open the requested file according to the given flags and user + * + *

+ * This is generally accessed by the target program via a {@link DefaultEmuUnixFileHandle}. + * + * @param pathname the pathname of the requested file + * @param flags the requested open flags + * @param user the user making the request + * @param mode the mode to assign the file, if created. Otherwise ignored + * @return the file + * @throws EmuIOException if an error occurred, e.g., file not found, or access denied + */ + EmuUnixFile open(String pathname, Set flags, EmuUnixUser user, int mode) + throws EmuIOException; +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixUser.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixUser.java new file mode 100644 index 0000000000..4f5c3c4076 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/EmuUnixUser.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.unix; + +import java.util.Collection; +import java.util.Set; + +/** + * A simulated UNIX user + */ +public class EmuUnixUser { + /** + * The default (root?) user + */ + public static final EmuUnixUser DEFAULT_USER = new EmuUnixUser(0, Set.of()); + + public final int uid; + public final Collection gids; + + /** + * Construct a new user + * + * @param uid the user's uid + * @param gids the user's gids + */ + public EmuUnixUser(int uid, Collection gids) { + this.uid = uid; + this.gids = gids; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/IOStreamEmuUnixFileHandle.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/IOStreamEmuUnixFileHandle.java new file mode 100644 index 0000000000..281f46233e --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/emu/unix/IOStreamEmuUnixFileHandle.java @@ -0,0 +1,120 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.unix; + +import java.io.*; + +import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.sys.EmuIOException; +import ghidra.program.model.lang.CompilerSpec; + +/** + * A simulated file descriptor that proxies a host resource, typically a console/terminal + */ +public class IOStreamEmuUnixFileHandle extends AbstractStreamEmuUnixFileHandle { + + /** + * Construct a proxy for the host's standard input + * + * @param machine the machine emulating the hardware + * @param cSpec the ABI of the target platform + * @return the proxy's handle + */ + public static IOStreamEmuUnixFileHandle stdin(PcodeMachine machine, + CompilerSpec cSpec) { + return new IOStreamEmuUnixFileHandle(machine, cSpec, System.in, null); + } + + /** + * Construct a proxy for the host's standard output + * + * @param machine the machine emulating the hardware + * @param cSpec the ABI of the target platform + * @return the proxy's handle + */ + public static IOStreamEmuUnixFileHandle stdout(PcodeMachine machine, + CompilerSpec cSpec) { + return new IOStreamEmuUnixFileHandle(machine, cSpec, null, System.out); + } + + /** + * Construct a proxy for the host's standard error output + * + * @param machine the machine emulating the hardware + * @param cSpec the ABI of the target platform + * @return the proxy's handle + */ + public static IOStreamEmuUnixFileHandle stderr(PcodeMachine machine, + CompilerSpec cSpec) { + return new IOStreamEmuUnixFileHandle(machine, cSpec, null, System.err); + } + + protected final InputStream input; + protected final OutputStream output; + + /** + * Construct a proxy for a host resource + * + *

+ * WARNING: Think carefully before proxying any host resource to a temperamental target + * program. + * + * @param machine the machine emulating the hardware + * @param cSpec the ABI of the target platform + * @param input the stream representing the input side of the descriptor, if applicable + * @param output the stream representing the output side of the descriptor, if applicable + * @return the proxy's handle + */ + public IOStreamEmuUnixFileHandle(PcodeMachine machine, CompilerSpec cSpec, + InputStream input, OutputStream output) { + super(machine, cSpec); + this.input = input; + this.output = output; + } + + @Override + public byte[] read(byte[] buf) throws EmuIOException { + if (input == null) { + return arithmetic.fromConst(0, offsetBytes); + } + try { + int result = input.read(buf); + return arithmetic.fromConst(result, offsetBytes); + } + catch (IOException e) { + throw new EmuIOException("Could not read host input stream", e); + } + } + + @Override + public byte[] write(byte[] buf) throws EmuIOException { + if (output == null) { + return arithmetic.fromConst(0, offsetBytes); + } + try { + output.write(buf); + return arithmetic.fromConst(buf.length, offsetBytes); + } + catch (IOException e) { + throw new EmuIOException("Could not write host output stream", e); + } + } + + @Override + public void close() { + // TODO: Is it my responsibility to close the streams? + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorState.java new file mode 100644 index 0000000000..d64cef8cbd --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractBytesPcodeExecutorState.java @@ -0,0 +1,162 @@ +/* ### + * 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.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +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.mem.*; +import ghidra.util.Msg; + +/** + * An abstract p-code executor state for storing bytes, retrieved and set 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 + */ +public abstract class AbstractBytesPcodeExecutorState> + extends AbstractLongOffsetPcodeExecutorState { + + /** + * A memory buffer bound to a given space in this state + */ + protected class StateMemBuffer implements MemBufferAdapter { + protected final Address address; + protected final BytesPcodeExecutorStateSpace source; + + /** + * Construct a buffer bound to the given space, at the given address + * + * @param address the address + * @param source the space + */ + public StateMemBuffer(Address address, BytesPcodeExecutorStateSpace source) { + this.address = address; + this.source = source; + } + + @Override + public Address getAddress() { + return address; + } + + @Override + public Memory getMemory() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isBigEndian() { + return language.isBigEndian(); + } + + @Override + public int getBytes(ByteBuffer buffer, int addressOffset) { + byte[] data = source.read(address.getOffset() + addressOffset, buffer.remaining()); + buffer.put(data); + return data.length; + } + } + + protected final Map spaces = new HashMap<>(); + + protected final Language language; + + /** + * Construct a state for the given language + * + * @param language the langauge (used for its memory model) + */ + 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()); + } + + /** + * If this state is a cache, get the object backing the given address space + * + * @param space the space + * @return the backing object + */ + protected B getBacking(AddressSpace space) { + return null; + } + + /** + * 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 + * @return the new space + */ + protected abstract S newSpace(AddressSpace space, B backing); + + @Override + protected S getForSpace(AddressSpace space, boolean toWrite) { + return spaces.computeIfAbsent(space, s -> { + B backing = s.isUniqueSpace() ? null : getBacking(space); + return newSpace(s, backing); + }); + } + + @Override + protected void setInSpace(S space, long offset, int size, byte[] val) { + if (val.length > size) { + throw new IllegalArgumentException( + "Value is larger than variable: " + val.length + " > " + size); + } + if (val.length < size) { + Msg.warn(this, "Value is smaller than variable: " + val.length + " < " + size + + ". Zero extending"); + val = arithmetic.unaryOp(PcodeArithmetic.INT_ZEXT, size, val.length, val); + } + space.write(offset, val, 0, size); + } + + @Override + protected byte[] getFromSpace(S space, long offset, int size) { + byte[] read = space.read(offset, size); + if (read.length != size) { + throw new AccessPcodeExecutionException("Incomplete read (" + read.length + + " of " + size + " bytes)"); + } + return read; + } + + @Override + public MemBuffer getConcreteBuffer(Address address) { + return new StateMemBuffer(address, getForSpace(address.getAddressSpace(), false)); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorState.java index b9dc1b1c29..ebfde5317b 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorState.java @@ -17,6 +17,12 @@ package ghidra.pcode.exec; import ghidra.program.model.lang.Language; +/** + * 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 + */ public abstract class AbstractLongOffsetPcodeExecutorState extends AbstractLongOffsetPcodeExecutorStatePiece implements PcodeExecutorState { @@ -24,5 +30,4 @@ public abstract class AbstractLongOffsetPcodeExecutorState public AbstractLongOffsetPcodeExecutorState(Language language, PcodeArithmetic arithmetic) { super(language, arithmetic); } - } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java index ce70d13924..2af88f8caf 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractLongOffsetPcodeExecutorStatePiece.java @@ -18,6 +18,13 @@ package ghidra.pcode.exec; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.Language; +/** + * An abstract executor state piece which internally uses {@code long} to address contents + * + * @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 + */ public abstract class AbstractLongOffsetPcodeExecutorStatePiece implements PcodeExecutorStatePiece { @@ -25,6 +32,12 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece 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 arithmetic an arithmetic used to generate default values of {@code T} + */ public AbstractLongOffsetPcodeExecutorStatePiece(Language language, PcodeArithmetic arithmetic) { this.language = language; @@ -32,26 +45,87 @@ public abstract class AbstractLongOffsetPcodeExecutorStatePiece uniqueSpace = language.getAddressFactory().getUniqueSpace(); } + /** + * Set a value in the unique space + * + *

+ * Some state pieces treat unique values in a way that merits a separate implementation. This + * permits the standard path to be overridden. + * + * @param offset the offset in unique space to store the value + * @param size the number of bytes to write (the size of the value) + * @param val the value to store + */ protected void setUnique(long offset, int size, T val) { S s = getForSpace(uniqueSpace, true); setInSpace(s, offset, size, val); } + /** + * Get a value from the unique space + * + * Some state pieces treat unique values in a way that merits a separate implementation. This + * permits the standard path to be overridden. + * + * @param offset the offset in unique space to get the value + * @param size the number of bytes to read (the size of the value) + * @return the read value + */ protected T getUnique(long offset, int size) { S s = getForSpace(uniqueSpace, false); return getFromSpace(s, offset, size); } + /** + * Get the internal space for the given address space + * + * @param space the address space + * @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} + */ protected abstract S getForSpace(AddressSpace space, boolean toWrite); + /** + * Set a value in the given space + * + * @param space the address space + * @param offset the offset within the space + * @param size the number of bytes to write (the size of the value) + * @param val the value to store + */ protected abstract void setInSpace(S space, long offset, int size, T val); + /** + * Get a value from the given space + * + * @param space the address space + * @param offset the offset within the space + * @param size the number of bytes to read (the size of the value) + * @return the read value + */ protected abstract T getFromSpace(S space, long offset, int size); + /** + * In case spaces are generated lazily, and we're reading from a space that doesn't yet exist, + * "read" a default value. + * + *

+ * By default, the returned value is 0, which should be reasonable for all implementations. + * + * @param size the number of bytes to read (the size of the value) + * @return the default value + */ protected T getFromNullSpace(int size) { 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 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 index 06f863d745..a7439c959d 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractOffsetTransformedPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AbstractOffsetTransformedPcodeExecutorState.java @@ -17,15 +17,33 @@ 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 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 497f9bcf45..928bc90733 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 @@ -21,8 +21,21 @@ import ghidra.pcode.opbehavior.BinaryOpBehavior; import ghidra.pcode.opbehavior.UnaryOpBehavior; import ghidra.program.model.address.Address; +/** + * A rider 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. + */ public enum AddressOfPcodeArithmetic implements PcodeArithmetic

{ // NB: No temp value has a real address + /** + * The singleton instance. + */ INSTANCE; @Override @@ -55,4 +68,9 @@ public enum AddressOfPcodeArithmetic implements PcodeArithmetic
{ public BigInteger toConcrete(Address value, boolean isContextreg) { throw new AssertionError("Should not attempt to concretize 'address of'"); } + + @Override + public Address sizeOf(Address value) { + return fromConst(value.getAddressSpace().getSize() / 8, SIZEOF_SIZEOF); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java index 118b45072a..abd1f0df91 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AddressOfPcodeExecutorState.java @@ -23,11 +23,26 @@ 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 + * + *

+ * This is intended for use as the right side of a {@link PairedPcodeExecutorState} or + * {@link PairedPcodeExecutorStatePiece}. Except for unique spaces, sets are ignored, and gets + * simply echo back the address of the requested read. In unique spaces, the "address of" is treated + * as the value, so that values transiting unique space can correctly have their source addresses + * reported. + */ public class AddressOfPcodeExecutorState implements PcodeExecutorStatePiece { private final boolean isBigEndian; private 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; } 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 new file mode 100644 index 0000000000..934b91a8b2 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java @@ -0,0 +1,557 @@ +/* ### + * 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.lang.annotation.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.*; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.reflect.TypeUtils; + +import ghidra.program.model.pcode.Varnode; +import utilities.util.AnnotationUtilities; + +/** + * A userop library wherein Java methods are exported via a special annotation + * + * @param the type of data processed by the library + */ +public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibrary { + private static final Map, Set> CACHE_BY_CLASS = new HashMap<>(); + + private static Set collectDefinitions( + Class> cls) { + return AnnotationUtilities.collectAnnotatedMethods(PcodeUserop.class, cls); + } + + private enum ParamAnnotProc { + EXECUTOR(OpExecutor.class, PcodeExecutor.class) { + @Override + int getPos(AnnotatedPcodeUseropDefinition opdef) { + return opdef.posExecutor; + } + + @Override + void setPos(AnnotatedPcodeUseropDefinition opdef, int pos) { + opdef.posExecutor = pos; + } + }, + STATE(OpState.class, PcodeExecutorStatePiece.class) { + @Override + int getPos(AnnotatedPcodeUseropDefinition opdef) { + return opdef.posState; + } + + @Override + void setPos(AnnotatedPcodeUseropDefinition opdef, int pos) { + opdef.posState = pos; + } + }, + LIBRARY(OpLibrary.class, PcodeUseropLibrary.class) { + @Override + int getPos(AnnotatedPcodeUseropDefinition opdef) { + return opdef.posLib; + } + + @Override + void setPos(AnnotatedPcodeUseropDefinition opdef, int pos) { + opdef.posLib = pos; + } + }, + OUTPUT(OpOutput.class, Varnode.class) { + @Override + int getPos(AnnotatedPcodeUseropDefinition opdef) { + return opdef.posOut; + } + + @Override + void setPos(AnnotatedPcodeUseropDefinition opdef, int pos) { + opdef.posOut = pos; + } + }; + + static boolean processParameter(AnnotatedPcodeUseropDefinition opdef, int i, + Parameter p) { + ParamAnnotProc only = null; + for (ParamAnnotProc proc : ParamAnnotProc.values()) { + if (proc.hasAnnot(p)) { + if (only != null) { + throw new IllegalArgumentException("Parameter can have at most one of " + + Stream.of(ParamAnnotProc.values()) + .map(pr -> "@" + pr.annotCls.getSimpleName()) + .collect(Collectors.toList())); + } + only = proc; + } + } + if (only == null) { + return false; + } + only.processParameterPerAnnot(opdef, i, p); + return true; + } + + private final Class annotCls; + private final Class paramCls; + + private ParamAnnotProc(Class annotCls, Class paramCls) { + this.annotCls = annotCls; + this.paramCls = paramCls; + } + + abstract int getPos(AnnotatedPcodeUseropDefinition opdef); + + abstract void setPos(AnnotatedPcodeUseropDefinition opdef, int pos); + + boolean hasAnnot(Parameter p) { + return p.getAnnotation(annotCls) != null; + } + + void processParameterPerAnnot(AnnotatedPcodeUseropDefinition opdef, int i, + Parameter p) { + if (getPos(opdef) != -1) { + throw new IllegalArgumentException( + "Can only have one parameter with @" + annotCls.getSimpleName()); + } + if (!p.getType().isAssignableFrom(paramCls)) { + throw new IllegalArgumentException("Parameter " + p.getName() + " with @" + + annotCls.getSimpleName() + " must acccept " + paramCls.getSimpleName()); + } + setPos(opdef, i); + } + } + + /** + * A wrapped, annotated Java method, exported as a userop definition + * + * @param the type of data processed by the userop + */ + protected static abstract class AnnotatedPcodeUseropDefinition + implements PcodeUseropDefinition { + + protected static AnnotatedPcodeUseropDefinition create(PcodeUserop annot, + AnnotatedPcodeUseropLibrary library, Class opType, Lookup lookup, + Method method) { + if (annot.variadic()) { + return new VariadicAnnotatedPcodeUseropDefinition<>(library, opType, lookup, + method); + } + else { + return new FixedArgsAnnotatedPcodeUseropDefinition<>(library, opType, lookup, + method); + } + } + + protected final Method method; + private final MethodHandle handle; + + private int posExecutor = -1; + private int posState = -1; + private int posLib = -1; + private int posOut = -1; + + public AnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary library, + Class opType, Lookup lookup, Method method) { + initStarting(); + this.method = method; + try { + this.handle = lookup.unreflect(method).bindTo(library); + } + catch (IllegalAccessException e) { + throw new IllegalArgumentException( + "Cannot access " + method + " having @" + + PcodeUserop.class.getSimpleName() + + " annotation. Override getMethodLookup()"); + } + + Class rType = method.getReturnType(); + if (rType != void.class && !opType.isAssignableFrom(rType)) { + throw new IllegalArgumentException( + "Method " + method.getName() + " with @" + + PcodeUserop.class.getSimpleName() + + " annotation must return void or a type assignable to " + + opType.getSimpleName()); + } + + Parameter[] params = method.getParameters(); + for (int i = 0; i < params.length; i++) { + Parameter p = params[i]; + boolean processed = ParamAnnotProc.processParameter(this, i, p); + if (!processed) { + processNonAnnotatedParameter(opType, i, p); + } + } + initFinished(); + } + + @Override + public String getName() { + return method.getName(); + } + + @Override + public void execute(PcodeExecutor executor, PcodeUseropLibrary library, + Varnode outVar, List inVars) { + validateInputs(inVars); + + PcodeExecutorStatePiece state = executor.getState(); + List args = Arrays.asList(new Object[method.getParameterCount()]); + + if (posExecutor != -1) { + args.set(posExecutor, executor); + } + if (posState != -1) { + args.set(posState, state); + } + if (posLib != -1) { + args.set(posLib, library); + } + if (posOut != -1) { + args.set(posOut, outVar); + } + placeInputs(executor, args, inVars); + + try { + @SuppressWarnings("unchecked") + T result = (T) handle.invokeWithArguments(args); + if (result != null && outVar != null) { + state.setVar(outVar, result); + } + } + catch (PcodeExecutionException e) { + throw e; + } + catch (Throwable e) { + throw new PcodeExecutionException("Error executing userop", null, e); + } + } + + protected void initStarting() { + // Optional override + } + + protected abstract void processNonAnnotatedParameter(Class opType, int i, + Parameter p); + + protected void initFinished() { + // Optional override + } + + protected void validateInputs(List inVars) throws PcodeExecutionException { + // Optional override + } + + protected abstract void placeInputs(PcodeExecutor executor, List args, + List inVars); + } + + /** + * An annotated userop with a fixed number of arguments + * + * @param the type of data processed by the userop + */ + protected static class FixedArgsAnnotatedPcodeUseropDefinition + extends AnnotatedPcodeUseropDefinition { + + private List posIns; + private Set posTs; + + public FixedArgsAnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary library, + Class opType, Lookup lookup, Method method) { + super(library, opType, lookup, method); + } + + @Override + protected void initStarting() { + posIns = new ArrayList<>(); + posTs = new HashSet<>(); + } + + @Override + protected void processNonAnnotatedParameter(Class opType, int i, Parameter p) { + if (p.getType().equals(Varnode.class)) { + // Just use the Varnode by default + } + else if (p.getType().isAssignableFrom(opType)) { + posTs.add(i); + } + else { + throw new IllegalArgumentException("Input parameter " + p.getName() + + " of userop " + method.getName() + " must be " + + Varnode.class.getSimpleName() + " or accept " + opType.getSimpleName()); + } + posIns.add(i); + } + + @Override + protected void validateInputs(List inVars) + throws PcodeExecutionException { + if (inVars.size() != posIns.size()) { + throw new PcodeExecutionException( + "Incorrect input parameter count for userop " + + method.getName() + ". Expected " + posIns.size() + " but got " + + inVars.size()); + } + } + + @Override + protected void placeInputs(PcodeExecutor executor, List args, + List inVars) { + PcodeExecutorStatePiece state = executor.getState(); + for (int i = 0; i < posIns.size(); i++) { + int pos = posIns.get(i); + if (posTs.contains(pos)) { + args.set(pos, state.getVar(inVars.get(i))); + } + else { + args.set(pos, inVars.get(i)); + } + } + } + + @Override + public int getInputCount() { + return posIns.size(); + } + } + + /** + * An annotated userop with a variable number of arguments + * + * @param the type of data processed by the userop + */ + protected static class VariadicAnnotatedPcodeUseropDefinition + extends AnnotatedPcodeUseropDefinition { + + private int posIns; + private Class opType; + + public VariadicAnnotatedPcodeUseropDefinition(AnnotatedPcodeUseropLibrary library, + Class opType, Lookup lookup, Method method) { + super(library, opType, lookup, method); + } + + @Override + protected void initStarting() { + posIns = -1; + opType = null; + } + + @Override + protected void processNonAnnotatedParameter(Class 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)) { + // Just pass inVars as is + } + else if (p.getType().isAssignableFrom(Array.newInstance(opType, 0).getClass())) { + this.opType = opType; + } + else { + throw new IllegalArgumentException( + "Variadic userop must receive inputs as T[] or " + + Varnode.class.getSimpleName() + "[]"); + } + posIns = i; + } + + @Override + protected void initFinished() { + if (posIns == -1) { + throw new IllegalArgumentException( + "Variadic userop must have a parameter for the inputs"); + } + } + + @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); + } + else { + args.set(posIns, inVars.toArray(Varnode[]::new)); + } + } + + @Override + public int getInputCount() { + return -1; + } + } + + /** + * An annotation to export a Java method as a userop in the library. + * + *

+ * Ordinarily, each parameter receives an input to the userop. Each parameter may be annotated + * with at most one of {@link OpExecutor}, {@link OpState}, {@link OpLibrary}, or + * {@link OpOutput} to change what it receives. If {@link #variadic()} is false, non-annotated + * parameters receive the inputs to the userop in matching order. Conventionally, annotated + * parameters should be placed first or last. Parameters accepting inputs must have type either + * {@link Varnode} or assignable from {@code T}. A parameter of type {@link Varnode} will + * receive the input {@link Varnode}. A parameter that is assignable from {@code T} will receive + * the input value. If it so happens that {@code T} is assignable from {@link Varnode}, the + * parameter will receive the {@link Varnode}, not the value. NOTE: Receiving a value + * instead of a variable may lose its size. Depending on the type of the value, that size may or + * may not be recoverable. + * + *

+ * If {@link #variadic()} is true, then a single non-annotated parameter receives all inputs in + * order. This parameter must have a type {@link Varnode}{@code []} to receive variables or have + * type assignable from {@code T[]} to receive values. + * + *

+ * Note that there is no annotation to receive the "thread," because threads are not a concept + * known to the p-code executor or userop libraries, in general. In most cases, receiving the + * executor and/or state (which are usually bound to a specific thread) is sufficient. The + * preferred means of exposing thread-specific userops is to construct a library bound to that + * specific thread. That strategy should preserve compile-time type safety. Alternatively, you + * can receive the executor or state, cast it to your specific type, and use an accessor to get + * its thread. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + public @interface PcodeUserop { + /** + * Set to true to receive all inputs in an array + */ + boolean variadic() default false; + } + + /** + * 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. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + public @interface OpExecutor { + } + + /** + * 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. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + public @interface OpState { + } + + /** + * An annotation to receive the complete library into a parameter + * + *

+ * Because the library defining the userop may be composed with other libraries, it is not + * sufficient to use the "{@code this}" reference to obtain the library. If the library being + * used for execution needs to be passed to a dependent component of execution, it must be the + * complete library, not just the one defining the userop. This annotation allows a userop + * 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. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + public @interface OpLibrary { + } + + /** + * An annotation to receive the output varnode into a parameter + * + *

+ * The annotated parameter must have a type assignable from {@link Varnode}. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + public @interface OpOutput { + } + + protected Map> ops = new HashMap<>(); + private Map> unmodifiableOps = + Collections.unmodifiableMap(ops); + + /** + * Default constructor, usually invoked implicitly + */ + public AnnotatedPcodeUseropLibrary() { + Lookup lookup = getMethodLookup(); + Class opType = getOperandType(); + @SuppressWarnings({ "unchecked", "rawtypes" }) + Class> cls = (Class) this.getClass(); + Set methods = CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls)); + for (Method m : methods) { + ops.put(m.getName(), AnnotatedPcodeUseropDefinition + .create(m.getAnnotation(PcodeUserop.class), this, opType, lookup, m)); + } + } + + /** + * Determine the operand type by examining the type substituted for {@code T} + * + * @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; + } + + /** + * An override to provide method access, if any non-public method is exported as a userop. + * + * @return a lookup that can access all {@link PcodeUserop}-annotated methods. + */ + protected Lookup getMethodLookup() { + return MethodHandles.lookup(); + } + + @Override + public Map> getUserops() { + return unmodifiableOps; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedSleighUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedSleighUseropLibrary.java deleted file mode 100644 index 08a3335aaa..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/AnnotatedSleighUseropLibrary.java +++ /dev/null @@ -1,169 +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.lang.annotation.*; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodHandles.Lookup; -import java.lang.reflect.*; -import java.util.*; - -import org.apache.commons.lang3.reflect.TypeUtils; - -import ghidra.program.model.pcode.Varnode; - -public abstract class AnnotatedSleighUseropLibrary implements SleighUseropLibrary { - private static final Map, Set> CACHE_BY_CLASS = new HashMap<>(); - - private static Set collectDefinitions( - Class> cls) { - Set defs = new HashSet<>(); - collectDefinitions(cls, defs, new HashSet<>()); - return defs; - } - - private static void collectDefinitions(Class cls, Set defs, Set> visited) { - if (!visited.add(cls)) { - return; - } - Class superCls = cls.getSuperclass(); - if (superCls != null) { - collectDefinitions(superCls, defs, visited); - } - for (Class superIf : cls.getInterfaces()) { - collectDefinitions(superIf, defs, visited); - } - collectClassDefinitions(cls, defs); - } - - private static void collectClassDefinitions(Class cls, Set defs) { - for (Method method : cls.getDeclaredMethods()) { - SleighUserop annot = method.getAnnotation(SleighUserop.class); - if (annot == null) { - continue; - } - defs.add(method); - } - } - - static class AnnotatedSleighUseropDefinition implements SleighUseropDefinition { - private final Method method; - private final MethodHandle handle; - - public AnnotatedSleighUseropDefinition(AnnotatedSleighUseropLibrary library, - Class opType, Lookup lookup, Method method) { - this.method = method; - try { - this.handle = lookup.unreflect(method).bindTo(library); - } - catch (IllegalAccessException e) { - throw new AssertionError("Cannot access " + method + " having @" + - SleighUserop.class.getSimpleName() + " annotation. Override getMethodLookup()"); - } - - for (Class ptype : method.getParameterTypes()) { - if (Varnode.class.isAssignableFrom(ptype)) { - continue; - } - if (opType.isAssignableFrom(ptype)) { - continue; - } - throw new IllegalArgumentException( - "pcode userops can only take Varnode inputs"); - } - } - - @Override - public String getName() { - return method.getName(); - } - - @Override - public int getOperandCount() { - return method.getParameterCount(); - } - - @Override - public void execute(PcodeExecutorStatePiece state, Varnode outVar, - List inVars) { - // outVar is ignored - List args = Arrays.asList(new Object[inVars.size()]); - Class[] ptypes = method.getParameterTypes(); - for (int i = 0; i < args.size(); i++) { - if (Varnode.class.isAssignableFrom(ptypes[i])) { - args.set(i, inVars.get(i)); - } - else { - args.set(i, state.getVar(inVars.get(i))); - } - } - try { - handle.invokeWithArguments(args); - } - catch (PcodeExecutionException e) { - throw e; - } - catch (Throwable e) { - throw new PcodeExecutionException("Error executing userop", null, e); - } - } - } - - @Retention(RetentionPolicy.RUNTIME) - @Target(ElementType.METHOD) - public @interface SleighUserop { - } - - Map> ops = new HashMap<>(); - - public AnnotatedSleighUseropLibrary() { - Lookup lookup = getMethodLookup(); - Class opType = getOperandType(); - @SuppressWarnings({ "unchecked", "rawtypes" }) - Class> cls = (Class) this.getClass(); - Set methods; - synchronized (CACHE_BY_CLASS) { - methods = CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls)); - } - for (Method m : methods) { - ops.put(m.getName(), new AnnotatedSleighUseropDefinition<>(this, opType, lookup, m)); - } - } - - @SuppressWarnings({ "unchecked", "rawtypes" }) - protected Class getOperandType() { - Map, Type> args = - TypeUtils.getTypeArguments(getClass(), AnnotatedSleighUseropLibrary.class); - if (args == null) { - return (Class) Object.class; - } - Type type = args.get(AnnotatedSleighUseropLibrary.class.getTypeParameters()[0]); - if (!(type instanceof Class)) { - return (Class) Object.class; - } - return (Class) type; - } - - protected Lookup getMethodLookup() { - return MethodHandles.lookup(); - } - - @Override - public Map> getUserops() { - return ops; - } -} 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 index 942d0e09d1..0ac9add609 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BigIntegerPcodeArithmetic.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BigIntegerPcodeArithmetic.java @@ -20,6 +20,12 @@ 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; @@ -54,4 +60,10 @@ public enum BigIntegerPcodeArithmetic implements PcodeArithmetic { 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 4e1816c6e4..7346701d7b 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 @@ -22,13 +22,39 @@ import ghidra.pcode.opbehavior.UnaryOpBehavior; import ghidra.pcode.utils.Utils; import ghidra.program.model.lang.Language; +/** + * A p-code arithmetic that operates on 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. + */ public enum BytesPcodeArithmetic implements PcodeArithmetic { - BIG_ENDIAN(true), LITTLE_ENDIAN(false); + /** + * The instance which interprets arrays as big-endian values + */ + BIG_ENDIAN(true), + /** + * The instance which interprets arrays as little-endian values + */ + LITTLE_ENDIAN(false); + /** + * Obtain the instance for the given endianness + * + * @param bigEndian true for {@link #BIG_ENDIAN}, false of {@link #LITTLE_ENDIAN} + * @return the arithmetic + */ public static BytesPcodeArithmetic forEndian(boolean bigEndian) { return bigEndian ? BIG_ENDIAN : LITTLE_ENDIAN; } + /** + * Obtain the instance for the given language's endianness + * + * @param language the language + * @return the arithmetic + */ public static BytesPcodeArithmetic forLanguage(Language language) { return forEndian(language.isBigEndian()); } @@ -94,4 +120,9 @@ public enum BytesPcodeArithmetic implements PcodeArithmetic { public BigInteger toConcrete(byte[] value, boolean isContextreg) { return Utils.bytesToBigInteger(value, value.length, isBigEndian || isContextreg, false); } + + @Override + public byte[] sizeOf(byte[] value) { + return fromConst(value.length, SIZEOF_SIZEOF); + } } 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 new file mode 100644 index 0000000000..d5ae36952c --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorState.java @@ -0,0 +1,40 @@ +/* ### + * 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 suitable for simple emulation, without any backing objects + */ +public class BytesPcodeExecutorState + extends AbstractBytesPcodeExecutorState> { + + /** + * Construct a state for the given language + * + * @param langauge the language (used for its memory model) + */ + public BytesPcodeExecutorState(Language language) { + super(language); + } + + @Override + protected BytesPcodeExecutorStateSpace newSpace(AddressSpace space, Void backing) { + return new BytesPcodeExecutorStateSpace<>(language, space, backing); + } +} 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 new file mode 100644 index 0000000000..78a5072048 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/BytesPcodeExecutorStateSpace.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.pcode.exec; + +import java.util.*; + +import com.google.common.collect.*; +import com.google.common.primitives.UnsignedLong; + +import ghidra.generic.util.datastruct.SemisparseByteArray; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.Register; +import ghidra.util.Msg; + +/** + * A p-code executor state space for storing bytes, retrieved and set as arrays. + * + * @param if this space is a cache, the type of object backing this space + */ +public class BytesPcodeExecutorStateSpace { + protected final SemisparseByteArray bytes = new SemisparseByteArray(); + protected final Language language; // for logging diagnostics + protected final AddressSpace space; + protected final B backing; + + /** + * Construct an internal space for the given address space + * + * @param language the language, for logging diagnostics + * @param space the address space + * @param backing the backing object, possibly {@code null} + */ + public BytesPcodeExecutorStateSpace(Language language, AddressSpace space, B backing) { + this.language = language; + this.space = space; + this.backing = backing; + } + + /** + * Write a value at the given offset + * + * @param offset the offset + * @param val the value + */ + public void write(long offset, byte[] val, int srcOffset, int length) { + bytes.putData(offset, val, srcOffset, length); + } + + /** + * Utility for handling uninitialized ranges: Get the lower endpoint + * + * @param rng the range + * @return the lower endpoint + */ + public long lower(Range rng) { + return rng.lowerBoundType() == BoundType.CLOSED + ? rng.lowerEndpoint().longValue() + : rng.lowerEndpoint().longValue() + 1; + } + + /** + * Utility for handling uninitialized ranges: Get the upper endpoint + * + * @param rng the range + * @return the upper endpoint + */ + public long upper(Range rng) { + return rng.upperBoundType() == BoundType.CLOSED + ? rng.upperEndpoint().longValue() + : rng.upperEndpoint().longValue() - 1; + } + + /** + * Extension point: Read from backing into this space, when acting as a cache. + * + * @param uninitialized the ranges which need to be read. + */ + protected void readUninitializedFromBacking(RangeSet uninitialized) { + } + + /** + * Read a value from cache (or raw space if not acting as a cache) at the given offset + * + * @param offset the offset + * @param size the number of bytes to read (the size of the value) + * @return the bytes read + */ + protected byte[] readBytes(long offset, int size) { + byte[] data = new byte[size]; + bytes.getData(offset, data); + return data; + } + + protected AddressRange addrRng(Range rng) { + Address start = space.getAddress(lower(rng)); + Address end = space.getAddress(upper(rng)); + return new AddressRangeImpl(start, end); + } + + protected AddressSet addrSet(RangeSet set) { + AddressSet result = new AddressSet(); + for (Range rng : set.asRanges()) { + result.add(addrRng(rng)); + } + return result; + } + + protected Set getRegs(AddressSet set) { + Set regs = new TreeSet<>(); + for (AddressRange rng : set) { + Register r = language.getRegister(rng.getMinAddress(), (int) rng.getLength()); + if (r != null) { + regs.add(r); + } + else { + regs.addAll(Arrays.asList(language.getRegisters(rng.getMinAddress()))); + } + } + return regs; + } + + protected void warnAddressSet(String message, AddressSet set) { + Set regs = getRegs(set); + if (regs.isEmpty()) { + Msg.warn(this, message + ": " + set); + } + else { + Msg.warn(this, message + ": " + set + " (registers " + regs + ")"); + } + } + + protected void warnUninit(RangeSet uninit) { + AddressSet uninitialized = addrSet(uninit); + warnAddressSet("Emulator read from uninitialized state", uninitialized); + } + + /** + * Read a value from the space at the given offset + * + *

+ * If this space is not acting as a cache, this simply delegates to + * {@link #readBytes(long, int)}. Otherwise, it will first ensure the cache covers the requested + * value. + * + * @param offset the offset + * @param size the number of bytes to read (the size of the value) + * @return the bytes read + */ + public byte[] read(long offset, int size) { + if (backing != null) { + readUninitializedFromBacking(bytes.getUninitialized(offset, offset + size - 1)); + } + RangeSet stillUninit = bytes.getUninitialized(offset, offset + size - 1); + if (!stillUninit.isEmpty()) { + warnUninit(stillUninit); + } + return readBytes(offset, size); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ComposedPcodeUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ComposedPcodeUseropLibrary.java new file mode 100644 index 0000000000..03915eecbf --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ComposedPcodeUseropLibrary.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; + +import java.util.*; + +/** + * A p-code userop library composed of other libraries + * + * @param the type of values processed by the library + */ +public class ComposedPcodeUseropLibrary implements PcodeUseropLibrary { + /** + * Obtain a map representing the composition of userops from all the given libraries + * + *

+ * Name collisions are not allowed. If any two libraries export the same symbol, even if the + * definitions happen to do the same thing, it is an error. + * + * @param the type of values processed by the libraries + * @param libraries the libraries whose userops to collect + * @return the resulting map + */ + public static Map> composeUserops( + Collection> libraries) { + Map> userops = new HashMap<>(); + for (PcodeUseropLibrary lib : libraries) { + for (PcodeUseropDefinition def : lib.getUserops().values()) { + if (userops.put(def.getName(), def) != null) { + throw new IllegalArgumentException( + "Cannot compose libraries with conflicting definitions on " + + def.getName()); + } + } + } + return userops; + } + + private final Map> userops; + + /** + * Construct a composed userop library from the given libraries + * + *

+ * This uses {@link #composeUserops(Collection)}, so its restrictions apply here, too. + * + * @param libraries the libraries + */ + public ComposedPcodeUseropLibrary(Collection> libraries) { + this.userops = composeUserops(libraries); + } + + @Override + public Map> getUserops() { + return userops; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ComposedSleighUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ComposedSleighUseropLibrary.java deleted file mode 100644 index 970237ede1..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/ComposedSleighUseropLibrary.java +++ /dev/null @@ -1,46 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.pcode.exec; - -import java.util.*; - -public class ComposedSleighUseropLibrary implements SleighUseropLibrary { - public static Map> composeUserops( - Collection> libraries) { - Map> userops = new HashMap<>(); - for (SleighUseropLibrary lib : libraries) { - for (SleighUseropDefinition def : lib.getUserops().values()) { - if (userops.put(def.getName(), def) != null) { - throw new IllegalArgumentException( - "Cannot compose libraries with conflicting definitions on " + - def.getName()); - } - } - } - return userops; - } - - private final Map> userops; - - public ComposedSleighUseropLibrary(Collection> libraries) { - this.userops = composeUserops(libraries); - } - - @Override - public Map> getUserops() { - return userops; - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/InterruptPcodeExecutionException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/InterruptPcodeExecutionException.java index dc4b13f317..b29d77761b 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/InterruptPcodeExecutionException.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/InterruptPcodeExecutionException.java @@ -15,6 +15,12 @@ */ package ghidra.pcode.exec; +import ghidra.pcode.emu.DefaultPcodeThread.PcodeEmulationLibrary; + +/** + * Exception thrown by {@link PcodeEmulationLibrary#emu_swi()}, a p-code userop exported by + * emulators for implementing breakpoints. + */ public class InterruptPcodeExecutionException extends PcodeExecutionException { public InterruptPcodeExecutionException(PcodeFrame frame, Throwable cause) { super("Execution hit breakpoint", frame, cause); 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 f99567478d..045107067f 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 @@ -25,20 +25,26 @@ import ghidra.pcode.opbehavior.BinaryOpBehavior; import ghidra.pcode.opbehavior.UnaryOpBehavior; /** - * Compose an arithmetic from two. + * 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" arithmetic. + * defers to left ("control") arithmetic. * - * @param the type of the left element - * @param the type of the right element + * @param the type of the left ("control") element + * @param the type of the right ("rider") element */ public class PairedPcodeArithmetic implements PcodeArithmetic> { private final PcodeArithmetic leftArith; private final PcodeArithmetic rightArith; + /** + * Construct a composed arithmetic from the given two + * + * @param leftArith the left ("control") arithmetic + * @param rightArith the right ("rider") arithmetic + */ public PairedPcodeArithmetic(PcodeArithmetic leftArith, PcodeArithmetic rightArith) { this.leftArith = leftArith; this.rightArith = rightArith; @@ -81,10 +87,27 @@ public class PairedPcodeArithmetic implements PcodeArithmetic> return leftArith.toConcrete(value.getLeft(), isContextreg); } + @Override + public Pair sizeOf(Pair value) { + return Pair.of( + leftArith.sizeOf(value.getLeft()), + rightArith.sizeOf(value.getRight())); + } + + /** + * Get the left ("control") arithmetic + * + * @return the arithmetic + */ public PcodeArithmetic getLeft() { return leftArith; } + /** + * Get the right ("rider") arithmetic + * + * @return the arithmetic + */ public PcodeArithmetic getRight() { return rightArith; } 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 ef3febef15..58d076c00d 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 @@ -26,6 +26,14 @@ 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 + * 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 @@ -43,6 +51,12 @@ public class PairedPcodeExecutorState private final PcodeExecutorStatePiece left; private final PcodeExecutorStatePiece right; + /** + * 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") + */ public PairedPcodeExecutorState(PcodeExecutorStatePiece left, PcodeExecutorStatePiece right) { super(new PairedPcodeExecutorStatePiece<>(left, right)); @@ -65,10 +79,20 @@ public class PairedPcodeExecutorState return left.getConcreteBuffer(address); } + /** + * Get the delegate backing the left side of paired values + * + * @return the left state + */ public PcodeExecutorStatePiece getLeft() { return left; } + /** + * Get the delegate backing the right side of paired values + * + * @return the right state + */ public PcodeExecutorStatePiece getRight() { return right; } 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 b2fa61fcc4..bb044d8424 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 @@ -25,6 +25,14 @@ 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. + * + * @see PairedPcodeExecutorState * @param the type of offset, usually the type of a controlling state * @param the type of the "left" state * @param the type of the "right" state @@ -66,10 +74,20 @@ public class PairedPcodeExecutorStatePiece return left.getConcreteBuffer(address); } + /** + * Get the delegate backing the left side of paired values + * + * @return the left piece + */ public PcodeExecutorStatePiece getLeft() { return left; } + /** + * Get the delegate backing the right side of paired values + * + * @return the right piece + */ public PcodeExecutorStatePiece getRight() { return right; } 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 eef7fc1d91..f3fed985ac 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 @@ -17,29 +17,118 @@ package ghidra.pcode.exec; import java.math.BigInteger; -import ghidra.pcode.opbehavior.BinaryOpBehavior; -import ghidra.pcode.opbehavior.UnaryOpBehavior; +import ghidra.pcode.opbehavior.*; +import ghidra.program.model.pcode.PcodeOp; +/** + * An interface that defines arithmetic p-code operations on values of type {@code T}. + * + * @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 + */ + int SIZEOF_SIZEOF = 8; + + /** + * The arithmetic for operating on bytes in big-endian + */ PcodeArithmetic BYTES_BE = BytesPcodeArithmetic.BIG_ENDIAN; + /** + * The arithmetic for operating on bytes in little-endian + */ 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; + /** + * Apply a unary operator to the given input + * + *

+ * Note the sizes of variables are given, because values don't necessarily have an intrinsic + * 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 + * @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); + /** + * Apply a binary operator to the given inputs + * + *

+ * Note the sizes of variables are given, because values don't necessarily have an intrinsic + * 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 + * @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 + * @param sizein2 the size (in bytes) of the second (right) input variable + * @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); + /** + * Convert the given constant concrete value to type {@code T} having the given size. + * + *

+ * Note that the size may not be applicable to {@code T}. It is given to ensure the value can be + * held in a variable of that size when passed to downstream operators or stored in the executor + * state. + * + * @param value the constant value + * @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); + /** + * Convert the given constant concrete value to type {@code T} having the given size. + * + *

+ * Note that the size may not be applicable to {@code T}. It is given to ensure the value can be + * held in a variable of that size when passed to downstream operators or stored in the executor + * state. + * + * @param value the constant value + * @param size the size (in bytes) of the variable into which the value is to be stored + * @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. + * @return the value as a {@code T} + */ T fromConst(BigInteger value, int size, boolean isContextreg); + /** + * Convert the given constant concrete value to type {@code T} having the given size. + * + *

+ * The value is assumed not to be for the disassembly context register. + * + * @see #fromConst(BigInteger, int, boolean) + */ default T fromConst(BigInteger value, int size) { return fromConst(value, size, false); } /** - * Make concrete, if possible, the given abstract condition to a boolean value + * Convert, if possible, the given abstract condition to a concrete boolean value * * @param cond the abstract condition * @return the boolean value @@ -47,7 +136,7 @@ public interface PcodeArithmetic { boolean isTrue(T cond); /** - * Make concrete, if possible, the given abstract value + * Convert, if possible, the given abstract value to a concrete value * *

* If the conversion is not possible, throw an exception. TODO: Decide on conventions of which @@ -74,4 +163,17 @@ public interface PcodeArithmetic { default BigInteger toConcrete(T value) { return toConcrete(value, false); } + + /** + * 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. + * + * @param value the abstract value + * @return the size in bytes + */ + T sizeOf(T value); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutionException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutionException.java index 2dc30213ec..2590fdaaa8 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutionException.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExecutionException.java @@ -15,10 +15,31 @@ */ package ghidra.pcode.exec; +/** + * The base exception for all p-code execution errors + * + *

+ * Exceptions caught by the executor that are not of this type are typically caught and wrapped, so + * that the frame can be recovered. The frame is important for diagnosing the error, because it + * records what the executor was doing. It essentially serves as the "line number" of the p-code + * program within the greater Java stack. Additionally, if execution of p-code is to resume, the + * frame must be recovered, and possibly stepped back one. + */ public class PcodeExecutionException extends RuntimeException { /*package*/ PcodeFrame frame; + /** + * Construct an execution exception + * + *

+ * The frame is often omitted at the throw site. The executor should catch the exception, fill + * in the frame, and re-throw it. + * + * @param message the message + * @param frame if known, the frame at the time of the exception + * @param cause the exception that caused this one + */ public PcodeExecutionException(String message, PcodeFrame frame, Throwable cause) { super(message, cause); this.frame = frame; @@ -36,6 +57,20 @@ public class PcodeExecutionException extends RuntimeException { this(message, null, null); } + /** + * Get the frame at the time of the exception + * + *

+ * Note that the frame counter is advanced before execution of the p-code op. Thus, the + * counter often points to the op following the one which caused the exception. For a frame to + * be present and meaningful, the executor must intervene between the throw and the catch. In + * other words, if you're invoking the executor, you should always expect to see a frame. If you + * are implementing, e.g., a userop, then it is possible to catch an exception without frame + * information populated. You might instead retrieve the frame from the executor, if you have a + * handle to it. + * + * @return the frame, possibly {@code null} + */ public PcodeFrame getFrame() { return frame; } 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 30da729c52..15c7f18b0c 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 @@ -19,8 +19,9 @@ import java.util.List; 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.SleighUseropLibrary.SleighUseropDefinition; +import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.pcode.opbehavior.*; import ghidra.program.model.address.Address; import ghidra.program.model.address.AddressSpace; @@ -28,6 +29,15 @@ import ghidra.program.model.lang.Register; import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; +/** + * An executor of p-code programs + * + *

+ * This is the kernel of SLEIGH expression evaluation and p-code emulation. For a complete example + * of a p-code emulator, see {@link PcodeEmulator}. + * + * @param the type of values processed by the executor + */ public class PcodeExecutor { protected final SleighLanguage language; protected final PcodeArithmetic arithmetic; @@ -35,6 +45,13 @@ public class PcodeExecutor { protected final Register pc; protected final int pointerSize; + /** + * Construct an executor with the given bindings + * + * @param language the processor language + * @param arithmetic an implementation of arithmetic p-code ops + * @param state an implementation of load/store p-code ops + */ public PcodeExecutor(SleighLanguage language, PcodeArithmetic arithmetic, PcodeExecutorStatePiece state) { this.language = language; @@ -74,15 +91,15 @@ public class PcodeExecutor { public void executeSleighLine(String line) { PcodeProgram program = SleighProgramCompiler.compileProgram(language, - "line", List.of(line + ";"), SleighUseropLibrary.NIL); - execute(program, SleighUseropLibrary.nil()); + "line", List.of(line + ";"), PcodeUseropLibrary.NIL); + execute(program, PcodeUseropLibrary.nil()); } public PcodeFrame begin(PcodeProgram program) { return begin(program.code, program.useropNames); } - public PcodeFrame execute(PcodeProgram program, SleighUseropLibrary library) { + public PcodeFrame execute(PcodeProgram program, PcodeUseropLibrary library) { return execute(program.code, program.useropNames, library); } @@ -91,7 +108,7 @@ public class PcodeExecutor { } public PcodeFrame execute(List code, Map useropNames, - SleighUseropLibrary library) { + PcodeUseropLibrary library) { PcodeFrame frame = begin(code, useropNames); finish(frame, library); return frame; @@ -108,7 +125,7 @@ public class PcodeExecutor { * @param frame the incomplete frame * @param library the library of userops to use */ - public void finish(PcodeFrame frame, SleighUseropLibrary library) { + public void finish(PcodeFrame frame, PcodeUseropLibrary library) { try { while (!frame.isFinished()) { step(frame, library); @@ -133,7 +150,7 @@ public class PcodeExecutor { } } - public void stepOp(PcodeOp op, PcodeFrame frame, SleighUseropLibrary library) { + public void stepOp(PcodeOp op, PcodeFrame frame, PcodeUseropLibrary library) { OpBehavior b = OpBehaviorFactory.getOpBehavior(op.getOpcode()); if (b == null) { badOp(op); @@ -181,7 +198,7 @@ public class PcodeExecutor { } } - public void step(PcodeFrame frame, SleighUseropLibrary library) { + public void step(PcodeFrame frame, PcodeUseropLibrary library) { try { stepOp(frame.nextOp(), frame, library); } @@ -300,19 +317,20 @@ public class PcodeExecutor { return frame.getUseropName(opNo); } - public void executeCallother(PcodeOp op, PcodeFrame frame, SleighUseropLibrary library) { + 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"); } - SleighUseropDefinition opDef = library.getUserops().get(opName); + PcodeUseropDefinition opDef = library.getUserops().get(opName); if (opDef == null) { throw new SleighLinkException( "Sleigh userop '" + opName + "' is not in the library " + library); } - opDef.execute(state, op.getOutput(), List.of(op.getInputs()).subList(1, op.getNumInputs())); + opDef.execute(this, library, op.getOutput(), + List.of(op.getInputs()).subList(1, op.getNumInputs())); } public void executeReturn(PcodeOp op, PcodeFrame 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 03eced958e..27caca84a7 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,7 +17,28 @@ package ghidra.pcode.exec; import org.apache.commons.lang3.tuple.Pair; +/** + * An interface that provides storage for values of type {@code T}, addressed by offsets of type + * {@code T}. + * + * @param the type of offsets and values + */ public interface PcodeExecutorState extends PcodeExecutorStatePiece { + + /** + * Use this state as the control, paired with the given state as the rider. + * + *

+ * CAUTION: Often, the default paired state is not quite sufficient. Consider + * {@link #getVar(AddressSpace, Object, int, boolean)}. The rider on the offset may offer + * information that must be incorporated into the rider of the value just read. This is the + * case, for example, with taint propagation. In those cases, an anonymous inner class extending + * {@link PairedPcodeExecutorState} is sufficient. + * + * @param the type of values and offsets stored by the rider + * @param right the rider state + * @return the paired state + */ default PcodeExecutorState> paired( PcodeExecutorStatePiece right) { return new PairedPcodeExecutorState<>(this, right); 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 c3b776f748..83a082634b 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 @@ -20,8 +20,22 @@ import ghidra.program.model.lang.Register; import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.pcode.Varnode; +/** + * An interface that provides storage for values of type {@code T}, addressed by offsets of type + * {@code A}. + * + * @param the type of offsets + * @param the type of values + */ public interface PcodeExecutorStatePiece { + /** + * Construct a range, if only to verify the range is valid + * + * @param space the address space + * @param offset the starting offset + * @param size the length (in bytes) of the range + */ default void checkRange(AddressSpace space, long offset, int size) { // TODO: Perhaps get/setVar should just take an AddressRange? try { @@ -32,46 +46,136 @@ public interface PcodeExecutorStatePiece { } } + /** + * Convert the given offset from {@code long} to type {@code A} + * + *

+ * 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} + */ A longToOffset(AddressSpace space, long l); + /** + * Set the value of a register variable + * + * @param reg the register + * @param val the value + */ default void setVar(Register reg, T val) { Address address = reg.getAddress(); setVar(address.getAddressSpace(), address.getOffset(), reg.getMinimumByteSize(), true, val); } + /** + * Set the value of a variable + * + * @param var the variable + * @param val the value + */ default void setVar(Varnode var, T val) { Address address = var.getAddress(); setVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true, val); } + /** + * Set the value of a variable + * + * @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 val the value + */ void setVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit, 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 val the value + */ default void setVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit, T val) { checkRange(space, offset, size); setVar(space, longToOffset(space, offset), size, truncateAddressableUnit, val); } + /** + * Get the value of a register variable + * + * @param reg the register + * @return the value + */ default T getVar(Register reg) { Address address = reg.getAddress(); return getVar(address.getAddressSpace(), address.getOffset(), reg.getMinimumByteSize(), true); } + /** + * Get the value of a variable + * + * @param var the variable + * @return the value + */ default T getVar(Varnode var) { Address address = var.getAddress(); return getVar(address.getAddressSpace(), address.getOffset(), var.getSize(), true); } + /** + * Get the value of a variable + * + * @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" + * @return the value + */ T getVar(AddressSpace space, A offset, int size, boolean truncateAddressableUnit); + /** + * Get the value of a variable + * + *

+ * This method is typically used for reading 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 "addressalbe unit" + * @return the value + */ default T getVar(AddressSpace space, long offset, int size, boolean truncateAddressableUnit) { checkRange(space, offset, size); return getVar(space, longToOffset(space, offset), size, truncateAddressableUnit); } + /** + * Bind a buffer of concrete bytes at the given start address + * + * @param address the start address + * @return a buffer + */ MemBuffer getConcreteBuffer(Address address); + /** + * Truncate 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 + */ default long truncateOffset(AddressSpace space, long offset) { return space.truncateAddressableWordOffset(offset) * space.getAddressableUnitSize(); } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExpression.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExpression.java new file mode 100644 index 0000000000..73f5f5af71 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeExpression.java @@ -0,0 +1,80 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import java.util.List; +import java.util.Map; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; +import ghidra.program.model.pcode.PcodeOp; + +/** + * A p-code program derived from, i.e., implementing, a SLEIGH expression + */ +public class PcodeExpression extends PcodeProgram { + public static final String RESULT_NAME = "___result"; + protected static final PcodeUseropLibrary CAPTURING = + new ValueCapturingPcodeUseropLibrary<>(); + + /** + * A clever means of capturing the result of the expression. + * + * @implNote The compiled source is actually {@code ___result();} which allows us to + * capture the value (and size) of arbitrary expressions. Assigning the value to a + * temp variable instead of a userop does not quite suffice, since it requires a fixed + * size, which cannot be known ahead of time. + * + * @param no type in particular, except to match the executor + */ + protected static class ValueCapturingPcodeUseropLibrary + extends AnnotatedPcodeUseropLibrary { + T result; + + @PcodeUserop + public void ___result(T result) { + this.result = result; + } + } + + /** + * Construct a p-code program from source already compiled into p-code ops + * + * @param language the language that generated the p-code + * @param code the list of p-code ops + * @param useropSymbols a map of expected userop symbols + */ + protected PcodeExpression(SleighLanguage language, List code, + Map useropSymbols) { + super(language, code, useropSymbols); + } + + // TODO: One that can take a library, and compose the result into it + + /** + * Evaluate the expression using the given executor + * + * @param the type of the result + * @param executor the executor + * @return the result + */ + public T evaluate(PcodeExecutor executor) { + ValueCapturingPcodeUseropLibrary library = + new ValueCapturingPcodeUseropLibrary<>(); + execute(executor, library); + return library.result; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeFrame.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeFrame.java index 9a9da7520f..f4afc04832 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeFrame.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeFrame.java @@ -21,10 +21,29 @@ import java.util.Map; import ghidra.app.plugin.processors.sleigh.template.OpTpl; import ghidra.app.util.pcode.AbstractAppender; import ghidra.app.util.pcode.AbstractPcodeFormatter; +import ghidra.pcode.emulate.EmulateInstructionStateModifier; import ghidra.pcode.error.LowlevelError; import ghidra.program.model.lang.Language; import ghidra.program.model.pcode.PcodeOp; +/** + * The executor's internal counter + * + *

+ * To distinguish the program counter of a p-code program from the program counter of the machine it + * models, we address p-code ops by "index." When derived from an instruction, the address and index + * together form the "sequence number." Because the executor care's not about the derivation of a + * p-code program, it counts through indices. The frame carries with it the p-code ops comprising + * its current p-code program. + * + *

+ * A p-code emulator feeds p-code to an executor by decoding one instruction at a time. Thus, the + * "current p-code program" comprises only those ops generated by a single instruction. Or else, it + * is a user-supplied p-code program, e.g., to evaluate a SLEIGH expression. The frame completes the + * program by falling-through, i.e., stepping past the final op, or by branching externally, i.e., + * to a different machine instruction. The emulator must then update its program counter accordingly + * and proceed to the next instruction. + */ public class PcodeFrame { protected class MyAppender extends AbstractAppender { protected final StringBuffer buf = new StringBuffer(); @@ -138,42 +157,120 @@ public class PcodeFrame { return new MyFormatter().formatOps(language, code); } + /** + * The index of the next p-code op to be executed + * + *

+ * If the last p-code op resulted in a branch, this will instead return -1. + * + * @see #isBranch() + * @see #isFallThrough() + * @see #isFinished() + * @return the index, i.e, p-code "program counter." + */ public int index() { return index; } + /** + * Get the op at the current index, and then advance that index + * + *

+ * This is used in the execution loop to retrieve each op to execute + * + * @return the op to execute + */ public PcodeOp nextOp() { return code.get(advance()); } + /** + * Advance the index + * + * @return the value of the index before it was advanced + */ public int advance() { return index++; } + /** + * Step the index back one + * + * @return the value of the index before it was stepped back + */ public int stepBack() { return index--; } + /** + * Get the name of the userop for the given number + * + * @param userop the userop number, as encoded in the first operand of {@link PcodeOp#CALLOTHER} + * @return the name of the userop, as expressed in the SLEIGH source + */ public String getUseropName(int userop) { return useropNames.get(userop); } + /** + * Get the map of userop numbers to names + * + * @return the map + */ public Map getUseropNames() { return useropNames; } + /** + * Check if the index has advanced past the end of the p-code program + * + *

+ * If the index has advanced beyond the program, it implies the program has finished executing. + * In the case of instruction emulation, no branch was encountered. The machine should advance + * to the fall-through instruction. + * + * @see #isBranch() + * @see #isFinished() + * @return true if the program completed without branching + */ public boolean isFallThrough() { return index == code.size(); } + /** + * Check if the p-code program has executed a branch + * + *

+ * Branches can be internal, i.e., within the current program, or external, i.e., to another + * machine instructions. This refers strictly to the latter. + * + * @see #isFallThrough() + * @see #isFinished() + * @return true if the program completed with an external branch + */ public boolean isBranch() { return index == -1; } + /** + * Check if the p-code program is completely executed + * + * @see #isFallThrough() + * @see #isBranch() + * @return true if execution finished, either in fall-through or an external branch + */ public boolean isFinished() { return !(0 <= index && index < code.size()); } + /** + * Perform an internal branch, relative to the current op. + * + *

+ * Because index advances before execution of each op, the index is adjusted by an extra -1. + * + * @param rel the adjustment to the index + */ public void branch(int rel) { index += rel - 1; // -1 because we already advanced if (!(0 <= index && index <= code.size())) { @@ -181,15 +278,28 @@ public class PcodeFrame { } } + /** + * Complete the p-code program, indicating an external branch + */ public void finishAsBranch() { branched = index - 1; // -1 because we already advanced index = -1; } + /** + * Get all the ops in the current p-code program. + * + * @return the list of ops + */ public List getCode() { return code; } + /** + * Copy the frame's code (shallow copy) into a new array + * + * @return the array of ops + */ public PcodeOp[] copyCode() { return code.toArray(PcodeOp[]::new); } @@ -198,9 +308,10 @@ public class PcodeFrame { * Get the index of the last (branch) op executed * *

- * The behavior here is a bit strange for compatibility with the established concrete emulator. - * If the program (instruction) completed with fall-through, then this will return -1. If it - * completed on a branch, then this will return the index of that branch. + * The behavior here is a bit strange for compatibility with + * {@link EmulateInstructionStateModifier}. If the p-code program (likely derived from a machine + * instruction) completed with fall-through, then this will return -1. If it completed on a + * branch, then this will return the index of that branch. * * @return */ diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java index e8cb3c7e6b..137b6291ca 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeProgram.java @@ -26,6 +26,12 @@ import ghidra.program.model.lang.Language; import ghidra.program.model.listing.Instruction; import ghidra.program.model.pcode.PcodeOp; +/** + * A p-code program to be executed by a {@link PcodeExecutor} + * + *

+ * This is a list of p-code operations together with a map of expected userops. + */ public class PcodeProgram { protected static class MyAppender extends AbstractAppender { protected final PcodeProgram program; @@ -82,6 +88,12 @@ public class PcodeProgram { } } + /** + * Generate a p-code program from the given instruction + * + * @param instruction the instruction + * @return the p-code program. + */ public static PcodeProgram fromInstruction(Instruction instruction) { Language language = instruction.getPrototype().getLanguage(); if (!(language instanceof SleighLanguage)) { @@ -96,6 +108,13 @@ public class PcodeProgram { protected final List code; protected final Map useropNames = new HashMap<>(); + /** + * Construct a p-code program with the given bindings + * + * @param language the language that generated the p-code + * @param code the list of p-code ops + * @param useropSymbols a map of expected userop symbols + */ protected PcodeProgram(SleighLanguage language, List code, Map useropSymbols) { this.language = language; @@ -112,6 +131,11 @@ public class PcodeProgram { } } + /** + * Get the language generating this program + * + * @return the language + */ public SleighLanguage getLanguage() { return language; } @@ -120,10 +144,22 @@ public class PcodeProgram { return code; } - public void execute(PcodeExecutor executor, SleighUseropLibrary library) { + /** + * Execute this program using the given executor and library + * + * @param the type of values to be operated on + * @param executor the executor + * @param library the library + */ + public void execute(PcodeExecutor executor, PcodeUseropLibrary library) { executor.execute(this, library); } + /** + * For display purposes, get the header above the frame, usually the class name + * + * @return the frame's display header + */ protected String getHead() { return getClass().getSimpleName(); } 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 new file mode 100644 index 0000000000..e1253bae6a --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java @@ -0,0 +1,161 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import java.util.*; + +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. + * + *

+ * 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}. + * + * @param the type of values accepted by the p-code userops. + */ +public interface PcodeUseropLibrary { + /** + * The class of the empty userop library. + * + * @see {@link PcodeUseropLibrary#nil()} + */ + final class EmptyPcodeUseropLibrary implements PcodeUseropLibrary { + @Override + public Map> getUserops() { + return Map.of(); + } + } + + /** + * The empty userop library. + * + *

+ * Executors cannot accept {@code null} libraries. Instead, give it this empty library. To + * satisfy Java's type checker, you may use {@link #nil()} instead. + */ + PcodeUseropLibrary NIL = new EmptyPcodeUseropLibrary(); + + /** + * The empty userop library, cast to match parameter types. + * + * @param the type required by the executor + * @return the empty userop library + */ + @SuppressWarnings("unchecked") + public static PcodeUseropLibrary nil() { + return (PcodeUseropLibrary) NIL; + } + + /** + * The definition of a p-code userop. + * + * @param the type of parameter accepted (and possibly returned) by the userop. + */ + interface PcodeUseropDefinition { + /** + * Get the name of the userop. + * + *

+ * This is the symbol assigned to the userop when compiling new SLEIGH code. It cannot + * conflict with existing userops (except those declared, but not defined, by the executor's + * language) or other symbols of the executor's language. If this userop is to be used + * generically across many languages, choose an unlikely name. Conventionally, these start + * with two underscores {@code __}. + * + * @return the name of the userop + */ + String getName(); + + /** + * Get the number of input operands acccepted by the userop. + * + * @return the count or -1 if the userop is variadic + */ + int getInputCount(); + + /** + * Invoke/execute the userop. + * + * @param executor the executor invoking this userop. + * @param library the complete library for this execution. Note the library may have been + * composed from more than the one defining this userop. + * @param outVar if invoked as an rval, the destination varnode for the userop's output. + * Otherwise, {@code null}. + * @param inVars the input varnodes as ordered in the source. + * @see AnnotatedPcodeUseropLibrary.AnnotatedPcodeUseropDefinition + */ + void execute(PcodeExecutor executor, PcodeUseropLibrary library, Varnode outVar, + List inVars); + } + + /** + * Get all the userops defined in this library, keyed by (symbol) name. + * + * @return the map of names to defined userops + */ + Map> getUserops(); + + /** + * Compose this and the given library into a new library. + * + * @param lib the other library + * @return a new library having all userops defined between the two + */ + default PcodeUseropLibrary compose(PcodeUseropLibrary lib) { + if (lib == null) { + return this; + } + return new ComposedPcodeUseropLibrary<>(List.of(this, lib)); + } + + /** + * Get named symbols defined by this library that are not already declared in the language + * + * @param language the language whose existing symbols to consider + * @return a map of new userop indices to extra userop symbols + */ + default Map getSymbols(SleighLanguage language) { + //Set langDefedNames = new HashSet<>(); + Map symbols = new HashMap<>(); + Set allNames = new HashSet<>(); + int langOpCount = language.getNumberOfUserDefinedOpNames(); + for (int i = 0; i < langOpCount; i++) { + String name = language.getUserDefinedOpName(i); + allNames.add(name); + } + int nextOpNo = langOpCount; + for (PcodeUseropDefinition uop : new TreeMap<>(getUserops()).values()) { + String opName = uop.getName(); + if (!allNames.add(opName)) { + // Real duplicates will cause a warning during execution + continue; + } + + int opNo = nextOpNo++; + Location loc = new Location(getClass().getName() + ":" + opName, 0); + UserOpSymbol sym = new UserOpSymbol(loc, opName); + sym.setIndex(opNo); + symbols.put(opNo, sym); + } + return symbols; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighExpression.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighExpression.java deleted file mode 100644 index fa022bddec..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighExpression.java +++ /dev/null @@ -1,53 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.pcode.exec; - -import java.util.List; -import java.util.Map; - -import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; -import ghidra.program.model.pcode.PcodeOp; - -public class SleighExpression extends PcodeProgram { - public static final String RESULT_NAME = "___result"; - protected static final SleighUseropLibrary CAPTURING = - new ValueCapturingSleighUseropLibrary<>(); - - protected static class ValueCapturingSleighUseropLibrary - extends AnnotatedSleighUseropLibrary { - T result; - - @SleighUserop - public void ___result(T result) { - this.result = result; - } - } - - protected SleighExpression(SleighLanguage language, List code, - Map useropSymbols) { - super(language, code, useropSymbols); - } - - // TODO: One that can take a library, and compose the result into it - - public T evaluate(PcodeExecutor executor) { - ValueCapturingSleighUseropLibrary library = - new ValueCapturingSleighUseropLibrary<>(); - execute(executor, library); - return library.result; - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighLinkException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighLinkException.java index 2194b410ea..93592a7a2b 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighLinkException.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighLinkException.java @@ -15,6 +15,13 @@ */ package ghidra.pcode.exec; +import ghidra.program.model.pcode.PcodeOp; + +/** + * An exception thrown by + * {@link PcodeExecutor#executeCallother(PcodeOp, PcodeFrame, PcodeUseropLibrary) when a p-code + * userop turns up missing. + */ public class SleighLinkException extends RuntimeException { public SleighLinkException(String message) { super(message); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java new file mode 100644 index 0000000000..ffdf3af005 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java @@ -0,0 +1,221 @@ +/* ### + * 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.text.MessageFormat; +import java.util.*; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; +import ghidra.program.model.pcode.Varnode; + +/** + * A p-code userop defined using SLEIGH source + * + * @param no type in particular, except to match any executor + */ +public class SleighPcodeUseropDefinition implements PcodeUseropDefinition { + public static final String OUT_SYMBOL_NAME = "__op_output"; + + /** + * A factory for building {@link SleighPcodeUseropDefinition}s. + */ + public static class Factory { + private final SleighLanguage language; + + /** + * Construct a factory for the given language + * + * @param language the language + */ + public Factory(SleighLanguage language) { + this.language = language; + } + + /** + * Begin building the definition for a userop with the given name + * + * @param name the name of the new userop + * @return a builder for the userop + */ + public Builder define(String name) { + return new Builder(this, name); + } + } + + /** + * A builder for a particular userop + * + * @see Factory + */ + public static class Builder { + private final Factory factory; + private final String name; + private final List params = new ArrayList<>(); + private final List lines = new ArrayList<>(); + + protected Builder(Factory factory, String name) { + this.factory = factory; + this.name = name; + + params(OUT_SYMBOL_NAME); + } + + /** + * Add parameters with the given names (to the end) + * + * @param additionalParams the additional parameter names + * @return this builder + */ + public Builder params(Collection additionalParams) { + this.params.addAll(additionalParams); + return this; + } + + /** + * @see #params(Collection) + */ + public Builder params(String... additionalParams) { + return this.params(Arrays.asList(additionalParams)); + } + + /** + * Add lines of SLEIGH source + * + *

+ * NOTE: The lines are joined only with line separators. No semicolons (;) are added at the + * end of each line. + * + *

+ * TODO: See if this can be made any prettier with text blocks in newer Java versions. + * + * @param additionalLines the additional lines + * @return this builder + */ + public Builder sleigh(Collection additionalLines) { + this.lines.addAll(additionalLines); + return this; + } + + /** + * @see #sleigh(Collection) + */ + public Builder sleigh(String... additionalLines) { + return this.sleigh(Arrays.asList(additionalLines)); + } + + /** + * Treat each line as a pattern as in {@link MessageFormat#format(String, Object...)}, + * replacing each with the result. + * + * @param arguments the arguments to pass to the formatter + * @return this builder + */ + public Builder applyAsPattern(Object[] arguments) { + for (int i = 0; i < lines.size(); i++) { + lines.set(i, MessageFormat.format(lines.get(i), arguments)); + } + return this; + } + + /** + * Build the actual definition + * + *

+ * NOTE: Compilation of the sleigh source is delayed until the first invocation, since the + * compiler must know about the varnodes used as parameters. TODO: There may be some way to + * template it at the p-code level instead of the SLEIGH source level. + * + * @param no particular type, except to match the executor + * @return the definition + */ + public SleighPcodeUseropDefinition build() { + return new SleighPcodeUseropDefinition<>(factory.language, name, List.copyOf(params), + List.copyOf(lines)); + } + } + + private final SleighLanguage language; + private final String name; + private final List params; + private final List lines; + + private final Map, PcodeProgram> cacheByArgs = new HashMap<>(); + + protected SleighPcodeUseropDefinition(SleighLanguage language, String name, List params, + List lines) { + this.language = language; + this.name = name; + this.params = params; + this.lines = lines; + } + + /** + * Get the p-code program implementing this userop for the given arguments and library. + * + *

+ * This will compile and cache a program for each new combination of arguments seen. + * + * @param outArg the output operand, if applicable + * @param inArgs the input operands + * @param library the complete userop library + * @return the p-code program to be fed to the same executor as invoked this userop, but in a + * new frame + */ + public PcodeProgram programFor(Varnode outArg, List inArgs, + PcodeUseropLibrary library) { + List args = new ArrayList<>(inArgs.size() + 1); + args.add(outArg); + args.addAll(inArgs); + return cacheByArgs.computeIfAbsent(args, + a -> SleighProgramCompiler.compileUserop(language, name, params, lines, library, a)); + } + + @Override + public String getName() { + return name; + } + + @Override + public int getInputCount() { + return params.size() - 1; // account for __op_output + } + + @Override + public void execute(PcodeExecutor executor, PcodeUseropLibrary library, + Varnode outArg, List inArgs) { + PcodeProgram program = programFor(outArg, inArgs, library); + executor.execute(program, library); + } + + /** + * Get the names of the inputs in order + * + * @return the input names + */ + public List getInputs() { + return params; + } + + /** + * Get the lines of source that define this userop + * + * @return the lines + */ + public List getLines() { + return lines; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java index 3677257275..33339ea6c5 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighProgramCompiler.java @@ -15,32 +15,73 @@ */ package ghidra.pcode.exec; -import java.util.*; +import java.util.List; +import java.util.Map; import org.apache.commons.lang3.StringUtils; import ghidra.app.plugin.processors.sleigh.*; import ghidra.app.plugin.processors.sleigh.template.ConstructTpl; -import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; +import ghidra.pcodeCPort.pcoderaw.VarnodeData; +import ghidra.pcodeCPort.sleighbase.SleighBase; +import ghidra.pcodeCPort.slghsymbol.*; +import ghidra.pcodeCPort.space.AddrSpace; import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.*; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; +import ghidra.sleigh.grammar.Location; +import ghidra.util.Msg; +/** + * Methods for compiling p-code programs for various purposes + * + *

+ * Depending on the purpose, special provisions may be necessary around the execution of the + * resulting program. Many utility methods are declared public here because they, well, they have + * utility. The main public methods of this class, however, all start with {@code compile}.... + */ public class SleighProgramCompiler { - private static final String EXPRESSION_SOURCE_NAME = "expression"; + private static final String EXPRESSION_SOURCE_NAME = "expression"; + public static final String NIL_SYMBOL_NAME = "__nil"; + + /** + * Create a p-code parser for the given language + * + * @param language the language + * @return a parser + */ public static PcodeParser createParser(SleighLanguage language) { return new PcodeParser(language, UniqueLayout.INJECT.getOffset(language)); } + /** + * Compile the given source into a p-code template + * + * @see #compileProgram(SleighLanguage, String, List, PcodeUseropLibrary) + * @param language the language + * @param parser the parser + * @param sourceName the name of the program, for error diagnostics + * @param text the SLEIGH source + * @return the constructor template + */ public static ConstructTpl compileTemplate(Language language, PcodeParser parser, String sourceName, String text) { - ConstructTpl template = - Objects.requireNonNull(parser.compilePcode(text, EXPRESSION_SOURCE_NAME, 1)); - return template; + return parser.compilePcode(text, EXPRESSION_SOURCE_NAME, 1); } + /** + * Construct a list of p-code ops from the given template + * + * @param language the language generating the template and p-code + * @param template the template + * @return the list of p-code ops + * @throws UnknownInstructionException in case of crossbuilds, the target instruction is unknown + * @throws MemoryAccessException in case of crossbuilds, the target address cannot be accessed + */ public static List buildOps(Language language, ConstructTpl template) throws UnknownInstructionException, MemoryAccessException { Address zero = language.getDefaultSpace().getAddress(0); @@ -68,34 +109,192 @@ public class SleighProgramCompiler { } } + /** + * Add a symbol for unwanted result + * + *

+ * This is basically a hack to avoid NPEs when no output varnode is given. + * + * @param parser the parser to add the symbol to + */ + protected static VarnodeSymbol addNilSymbol(PcodeParser parser) { + SleighSymbol exists = parser.findSymbol(NIL_SYMBOL_NAME); + if (exists != null) { + // A ClassCastException here indicates a name collision + return (VarnodeSymbol) exists; + } + long offset = parser.allocateTemp(); + VarnodeSymbol nil = + new VarnodeSymbol(new Location("", 0), NIL_SYMBOL_NAME, parser.getUniqueSpace(), + offset, 1); + parser.addSymbol(nil); + return nil; + } + + /** + * A factory for {@code PcodeProgram}s + * + * @param the type of program to build + */ + public interface PcodeProgramConstructor { + T construct(SleighLanguage language, List ops, Map symbols); + } + + /** + * Invoke the given constructor with the given template and library symbols + * + * @param the type of the p-code program + * @param ctor the constructor, often a method reference to {@code ::new} + * @param language the language producing the p-code + * @param template the p-code constructor template + * @param libSyms the map of symbols by userop ID + * @return the p-code program + */ + public static T constructProgram(PcodeProgramConstructor ctor, + SleighLanguage language, ConstructTpl template, Map libSyms) { + try { + return ctor.construct(language, SleighProgramCompiler.buildOps(language, template), + libSyms); + } + catch (UnknownInstructionException | MemoryAccessException e) { + throw new AssertionError(e); + } + } + + /** + * Compile the given SLEIGH source into a simple p-code program + * + *

+ * This is suitable for modifying program state using SLEIGH statements. Most likely, in + * scripting, or perhaps in a SLEIGH repl. The library given during compilation must match the + * library given for execution, at least in its binding of userop IDs to symbols. + * + * @param language the language of the target p-code machine + * @param sourceName a diagnostic name for the SLEIGH source + * @param lines the lines of SLEIGH source. These are joined with line separators but no + * semicolon! + * @param library the userop library or stub library for binding userop symbols + * @return the compiled p-code program + */ public static PcodeProgram compileProgram(SleighLanguage language, String sourceName, - List lines, SleighUseropLibrary library) { + List lines, PcodeUseropLibrary library) { PcodeParser parser = createParser(language); Map symbols = library.getSymbols(language); addParserSymbols(parser, symbols); ConstructTpl template = compileTemplate(language, parser, sourceName, StringUtils.join(lines, "\n")); - try { - return new PcodeProgram(language, buildOps(language, template), symbols); - } - catch (UnknownInstructionException | MemoryAccessException e) { - throw new AssertionError(e); - } + return constructProgram(PcodeProgram::new, language, template, symbols); } - public static SleighExpression compileExpression(SleighLanguage language, String expression) { + /** + * Compile the given SLEIGH expression into a p-code program that can evaluate it + * + *

+ * TODO: Currently, expressions cannot be compiled for a user-supplied userop library. The + * evaluator p-code program uses its own library as a means of capturing the result; however, + * userop libraries are easily composed. It should be easy to add that feature if needed. + * + * @param language the languge of the target p-code machine + * @param expression the SLEIGH expression to be evaluated + * @return a p-code program whose {@link PcodeExpression#evaluate(PcodeExecutor)} method will + * evaluate the expression on the given executor and its state. + */ + public static PcodeExpression compileExpression(SleighLanguage language, String expression) { PcodeParser parser = createParser(language); - Map symbols = SleighExpression.CAPTURING.getSymbols(language); + Map symbols = PcodeExpression.CAPTURING.getSymbols(language); addParserSymbols(parser, symbols); ConstructTpl template = compileTemplate(language, parser, EXPRESSION_SOURCE_NAME, - SleighExpression.RESULT_NAME + "(" + expression + ");"); - try { - return new SleighExpression(language, buildOps(language, template), symbols); + PcodeExpression.RESULT_NAME + "(" + expression + ");"); + return constructProgram(PcodeExpression::new, language, template, symbols); + } + + /** + * Generate a SLEIGH symbol for context when compiling a userop definition + * + * @param language the language of the target p-code machine + * @param sleigh a means of translating address spaces between execution and compilation + * contexts + * @param opName a diagnostic name for the userop in which this parameter applies + * @param paramName the symbol name for the parameter + * @param arg the varnode to bind to the parameter symbol + * @return the named SLEIGH symbol bound to the given varnode + */ + public static VarnodeSymbol paramSym(Language language, SleighBase sleigh, String opName, + String paramName, Varnode arg) { + AddressSpace gSpace = language.getAddressFactory().getAddressSpace(arg.getSpace()); + AddrSpace sSpace = sleigh.getSpace(gSpace.getUnique()); + return new VarnodeSymbol(new Location(opName, 0), paramName, sSpace, arg.getOffset(), + arg.getSize()); + } + + /** + * Compile the definition of a p-code userop from SLEIGH source into a p-code program + * + *

+ * TODO: Defining a userop from SLEIGH source is currently a bit of a hack. It would be nice if + * there were a formalization of SLEIGH/p-code subroutines. At the moment, the control flow for + * subroutines is handled out of band, which actually works fairly well. However, parameter + * passing and returning results is not well defined. The current solution is to alias the + * parameters to their arguments, implementing a pass-by-reference scheme. Similarly, the output + * variable is aliased to the symbol named {@link SleighPcodeUseropDefinition#OUT_SYMBOL_NAME}, + * which could be problematic if no output variable is given. In this setup, the use of + * temporary variables is tenuous, since no provision is made to ensure a subroutine's + * allocation of temporary variables do not collide with those of callers lower in the stack. + * This could be partly resolved by creating a fresh unique space for each invocation, but then + * it becomes necessary to copy values from the caller's to the callee's. If we're strict about + * parameters being inputs, this is straightforward. If parameters can be used to communicate + * results, then we may need parameter attributes to indicate in, out, or inout. Of course, + * having a separate unique space per invocation implies the executor state can't simply have + * one unique space. Likely, the {@link PcodeFrame} would come to own its own unique space, but + * the {@link PcodeExecutorState} should probably still manufacture it. + * + * @param language the language of the target p-code machine + * @param opName the name of the userop (used only for diagnostics here) + * @param params the names of parameters in order. Index 0 names the output symbol, probably + * {@link SleighPcodeUseropDefinition#OUT_SYMBOL_NAME} + * @param lines the lines of SLEIGH source. These are joined with line separators but no + * semicolon! + * @param library the userop library or stub library for binding userop symbols + * @param args the varnode arguments in order. Index 0 is the output varnode. + * @return a p-code program that implements the userop for the given arguments + */ + public static PcodeProgram compileUserop(SleighLanguage language, String opName, + List params, List lines, PcodeUseropLibrary library, + List args) { + PcodeParser parser = createParser(language); + Map symbols = library.getSymbols(language); + addParserSymbols(parser, symbols); + SleighBase sleigh = parser.getSleigh(); + + int count = params.size(); + if (args.size() != count) { + throw new IllegalArgumentException("Mismatch of params and args sizes"); } - catch (UnknownInstructionException | MemoryAccessException e) { - throw new AssertionError(e); + VarnodeSymbol nil = addNilSymbol(parser); + VarnodeData nilVnData = nil.getFixedVarnode(); + for (int i = 0; i < count; i++) { + String p = params.get(i); + Varnode a = args.get(i); + if (a == null && i == 0) { // Only allow output to be omitted + parser.addSymbol( + new VarnodeSymbol(nil.getLocation(), p, nilVnData.space, nilVnData.offset, + nilVnData.size)); + } + else { + parser.addSymbol(paramSym(language, sleigh, opName, p, a)); + } + } + + String source = StringUtils.join(lines, "\n"); + try { + ConstructTpl template = compileTemplate(language, parser, opName, source); + return constructProgram(PcodeProgram::new, language, template, symbols); + } + catch (Throwable t) { + Msg.error(SleighProgramCompiler.class, "Error trying to compile userop:\n" + source); + throw t; } } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighUseropLibrary.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighUseropLibrary.java deleted file mode 100644 index f6e56f2a88..0000000000 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SleighUseropLibrary.java +++ /dev/null @@ -1,88 +0,0 @@ -/* ### - * IP: GHIDRA - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package ghidra.pcode.exec; - -import java.util.*; - -import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.pcodeCPort.slghsymbol.UserOpSymbol; -import ghidra.program.model.pcode.Varnode; -import ghidra.sleigh.grammar.Location; - -public interface SleighUseropLibrary { - final class EmptySleighUseropLibrary implements SleighUseropLibrary { - @Override - public Map> getUserops() { - return Map.of(); - } - } - - SleighUseropLibrary NIL = new EmptySleighUseropLibrary(); - - @SuppressWarnings("unchecked") - public static SleighUseropLibrary nil() { - return (SleighUseropLibrary) NIL; - } - - interface SleighUseropDefinition { - String getName(); - - int getOperandCount(); - - void execute(PcodeExecutorStatePiece state, Varnode outVar, List inVars); - } - - Map> getUserops(); - - default SleighUseropLibrary compose(SleighUseropLibrary lib) { - if (lib == null) { - return this; - } - return new ComposedSleighUseropLibrary<>(List.of(this, lib)); - } - - /** - * Get named symbols defined by this library that are not already defined in the language - * - * @param language the language whose existing symbols to consider - * @return a map of new user-op indices to extra user-op symbols - */ - default Map getSymbols(SleighLanguage language) { - //Set langDefedNames = new HashSet<>(); - Map symbols = new HashMap<>(); - Set allNames = new HashSet<>(); - int langOpCount = language.getNumberOfUserDefinedOpNames(); - for (int i = 0; i < langOpCount; i++) { - String name = language.getUserDefinedOpName(i); - allNames.add(name); - } - int nextOpNo = langOpCount; - for (SleighUseropDefinition uop : new TreeMap<>(getUserops()).values()) { - String opName = uop.getName(); - if (!allNames.add(opName)) { - // Real duplicates will cause a warning during execution - continue; - } - - int opNo = nextOpNo++; - Location loc = new Location(getClass().getName() + ":" + opName, 0); - UserOpSymbol sym = new UserOpSymbol(loc, opName); - sym.setIndex(opNo); - symbols.put(opNo, sym); - } - return symbols; - } -} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SuspendedPcodeExecutionException.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SuspendedPcodeExecutionException.java index 8e79424716..78ddd7d9b6 100644 --- a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SuspendedPcodeExecutionException.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/exec/SuspendedPcodeExecutionException.java @@ -15,6 +15,12 @@ */ package ghidra.pcode.exec; +import ghidra.pcode.emu.PcodeThread; + +/** + * An exception thrown during execution if {@link PcodeThread#setSuspended(boolean)} is invoked with + * {@code true}. + */ public class SuspendedPcodeExecutionException extends PcodeExecutionException { public SuspendedPcodeExecutionException(PcodeFrame frame, Throwable cause) { super("Execution suspended by user", frame, cause); diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/AbstractStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/AbstractStmt.java new file mode 100644 index 0000000000..04bbb86e87 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/AbstractStmt.java @@ -0,0 +1,107 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.lifecycle.Internal; +import ghidra.pcode.struct.StructuredSleigh.Label; +import ghidra.pcode.struct.StructuredSleigh.Stmt; + +abstract class AbstractStmt implements Stmt { + protected final StructuredSleigh ctx; + protected AbstractStmt parent; + + protected AbstractStmt(StructuredSleigh ctx) { + this.ctx = ctx; + BlockStmt parent = ctx.stack.peek(); + this.parent = parent; + if (parent != null) { + parent.children.add(this); + } + } + + /** + * Internal: Provides the implementation of {@link RValInternal#getContext()} for + * {@link AssignStmt} + * + * @return the context + */ + public StructuredSleigh getContext() { + return ctx; + } + + @Internal + protected AbstractStmt reparent(AbstractStmt newParent) { + assert parent instanceof BlockStmt; + BlockStmt parent = (BlockStmt) this.parent; + parent.children.remove(this); + this.parent = newParent; + return this; + } + + /** + * Get the innermost statement of the given class containing this statement + * + *

+ * This is used to implement "truncation" statements like "break", "continue", and "result". + * + * @param the type of the statement sought + * @param cls the class of the statement sought + * @return the found statement or null + */ + @Internal + protected T nearest(Class cls) { + if (cls.isAssignableFrom(this.getClass())) { + return cls.cast(this); + } + if (parent == null) { + return null; + } + return parent.nearest(cls); + } + + /** + * Generate the Sleigh code that implements this statement + * + * @param next the label receiving control immediately after this statement is executed + * @param fall the label positioned immediately after this statement in the generated code + * @return the generated Sleigh code + */ + protected abstract String generate(Label next, Label fall); + + /** + * Check if the statement is or contains a single branch statement + * + *

+ * This is to avoid the unnecessary generation of labels forming chains of unconditional gotos. + * + * @return true if so, false otherwise. + */ + protected boolean isSingleGoto() { + return false; + } + + /** + * Get the label for the statement immediately following this statement + * + *

+ * For statements that always fall-through, this is just {#link {@link StructuredSleigh#FALL}. + * + * @return the label + */ + protected Label getNext() { + return ctx.FALL; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ArithBinExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ArithBinExpr.java new file mode 100644 index 0000000000..725f5e9513 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ArithBinExpr.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; + +class ArithBinExpr extends BinExpr { + enum Op { + ORB("||"), + ORI("|"), + XORB("^^"), + XORI("^"), + ANDB("&&"), + ANDI("&"), + SHLI("<<"), + SHRIU(">>"), + SHRIS("s>>"), + ADDI("+"), + ADDF("f+"), + SUBI("-"), + SUBF("f-"), + MULI("*"), + MULF("f*"), + DIVIU("/"), + DIVIS("s/"), + DIVF("f/"), + REMIU("%"), + REMIS("s%"), + ; + + private final String str; + + Op(String str) { + this.str = str; + } + } + + protected ArithBinExpr(StructuredSleigh ctx, RVal lhs, Op op, RVal rhs) { + // TODO: Should take more general of two types? + super(ctx, lhs, op.str, rhs, lhs.getType()); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/AssignStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/AssignStmt.java new file mode 100644 index 0000000000..910f575acc --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/AssignStmt.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.*; +import ghidra.program.model.data.DataType; + +/** + * An assignment statement + */ +class AssignStmt extends AbstractStmt implements RValInternal, StmtWithVal { + private final LValInternal lhs; + private final RValInternal rhs; + private final DataType type; + + private AssignStmt(StructuredSleigh ctx, LVal lhs, RVal rhs, DataType type) { + super(ctx); + this.lhs = (LValInternal) lhs; + this.rhs = (RValInternal) rhs; + this.type = type; + } + + public AssignStmt(StructuredSleigh ctx, LVal lhs, RVal rhs) { + // NOTE: Takes the type of the left-hand side + this(ctx, lhs, rhs, lhs.getType()); + } + + @Override + public RVal cast(DataType type) { + return new AssignStmt(ctx, lhs, rhs, type); + } + + @Override + public String toString() { + return ""; + } + + @Override + protected String generate(Label next, Label fall) { + return lhs.generate() + " = " + rhs.generate() + ";\n" + next.genGoto(fall); + } + + @Override + public DataType getType() { + return type; + } + + @Override + public String generate() { + return lhs.generate(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BinExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BinExpr.java new file mode 100644 index 0000000000..d41c22155f --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BinExpr.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.program.model.data.DataType; + +class BinExpr extends Expr { + protected final RValInternal lhs; + protected final String op; + protected final RValInternal rhs; + + protected BinExpr(StructuredSleigh ctx, RVal lhs, String op, RVal rhs, DataType type) { + super(ctx, type); + this.lhs = (RValInternal) lhs; + this.op = op; + this.rhs = (RValInternal) rhs; + } + + @Override + public RVal cast(DataType type) { + return new BinExpr(ctx, lhs, op, rhs, type); + } + + @Override + public String toString() { + return "<" + getClass().getSimpleName() + " " + lhs + " " + op + " " + rhs + ">"; + } + + @Override + public String generate() { + return "(" + lhs.generate() + " " + op + " " + rhs.generate() + ")"; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BlockStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BlockStmt.java new file mode 100644 index 0000000000..b8a87061e8 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BlockStmt.java @@ -0,0 +1,73 @@ +/* ### + * 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.struct; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.pcode.struct.StructuredSleigh.Label; + +/** + * A block statement + */ +class BlockStmt extends AbstractStmt { + List children = new ArrayList<>(); + + /** + * Build a block statement + * + * @param ctx the context + * @param body the body, usually a lambda + */ + protected BlockStmt(StructuredSleigh ctx, Runnable body) { + super(ctx); + ctx.stack.push(this); + body.run(); + ctx.stack.pop(); + } + + /** + * Add a child to this statement + * + * @param child the child statement + */ + public void addChild(AbstractStmt child) { + children.add(child); + } + + @Override + protected String generate(Label next, Label fall) { + if (children.isEmpty()) { + return next.genGoto(fall); + } + StringBuilder sb = new StringBuilder(); + for (AbstractStmt c : children.subList(0, children.size() - 1)) { + sb.append(c.generate(ctx.FALL, ctx.FALL)); + } + sb.append(children.get(children.size() - 1).generate(next, fall)); + return sb.toString(); + } + + @Override + protected boolean isSingleGoto() { + return children.size() == 1 && children.get(0).isSingleGoto(); + } + + @Override + protected Label getNext() { + return children.isEmpty() ? ctx.FALL : children.get(children.size() - 1).getNext(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BreakStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BreakStmt.java new file mode 100644 index 0000000000..5c81ec1f95 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/BreakStmt.java @@ -0,0 +1,29 @@ +/* ### + * 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.struct; + +import ghidra.pcode.struct.StructuredSleigh.Label; + +class BreakStmt extends LoopTruncateStmt { + protected BreakStmt(StructuredSleigh ctx) { + super(ctx); + } + + @Override + protected Label getNext() { + return getContainingLoop().lBreak; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/CallExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/CallExpr.java new file mode 100644 index 0000000000..97000bf5cb --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/CallExpr.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.struct; + +import java.util.List; +import java.util.stream.Collectors; + +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.pcode.struct.StructuredSleigh.UseropDecl; +import ghidra.program.model.data.DataType; + +/** + * A p-code userop invocation expression + * + *

+ * Userops are essentially treated as functions. They can be invoked passing a list of parameters, + * and the expression takes the value it returns (via {@link StructuredSleigh#_result(RVal)}. + */ +public class CallExpr extends Expr { + private final UseropDecl userop; + private final List args; + + private CallExpr(StructuredSleigh ctx, DataType type, UseropDecl userop, + List args) { + super(ctx, type); + this.userop = userop; + this.args = args; + } + + /** + * Build a call expression + * + * @param ctx the context + * @param userop the userop to invoke + * @param args the arguments to pass in (by reference) + */ + protected CallExpr(StructuredSleigh ctx, UseropDecl userop, List args) { + this(ctx, userop.getReturnType(), userop, + args.stream().map(a -> (RValInternal) a).collect(Collectors.toList())); + } + + @Override + public RVal cast(DataType type) { + return new CallExpr(ctx, type, userop, args); + } + + @Override + public String toString() { + return " a.toString()).collect(Collectors.joining(",")) + ")>"; + } + + @Override + public String generate() { + return userop.getName() + "(" + + args.stream().map(a -> a.generate()).collect(Collectors.joining(",")) + ")"; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/CmpExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/CmpExpr.java new file mode 100644 index 0000000000..485d89e2b0 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/CmpExpr.java @@ -0,0 +1,146 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.program.model.data.BooleanDataType; +import ghidra.program.model.data.DataType; + +class CmpExpr extends BinExpr { + enum Op { + EQ("==") { + @Override + Op not() { + return NEQ; + } + }, + NEQ("!=") { + @Override + Op not() { + return EQ; + } + }, + EQF("f==") { + @Override + Op not() { + return NEQF; + } + }, + NEQF("f!=") { + @Override + Op not() { + return EQF; + } + }, + LTIU("<") { + @Override + Op not() { + return GTEIU; + } + }, + LTIS("s<") { + @Override + Op not() { + return GTEIS; + } + }, + LTF("f<") { + @Override + Op not() { + return GTEF; + } + }, + LTEIU("<=") { + @Override + Op not() { + return GTIU; + } + }, + LTEIS("s<=") { + @Override + Op not() { + return GTIS; + } + }, + LTEF("f<=") { + @Override + Op not() { + return GTF; + } + }, + GTIU(">") { + @Override + Op not() { + return LTEIU; + } + }, + GTIS("s>") { + @Override + Op not() { + return LTEIS; + } + }, + GTF("f>") { + @Override + Op not() { + return LTEF; + } + }, + GTEIU(">=") { + @Override + Op not() { + return LTIU; + } + }, + GTEIS("s>=") { + @Override + Op not() { + return LTIS; + } + }, + GTEF("f>=") { + @Override + Op not() { + return LTF; + } + }, + ; + + private final String str; + + Op(String str) { + this.str = str; + } + + abstract Op not(); + } + + protected Op op; + + private CmpExpr(StructuredSleigh ctx, RVal lhs, Op op, RVal rhs, DataType type) { + super(ctx, lhs, op.str, rhs, type); + this.op = op; + } + + protected CmpExpr(StructuredSleigh ctx, RVal lhs, Op op, RVal rhs) { + this(ctx, lhs, op, rhs, BooleanDataType.dataType); + } + + @Override + public RVal notb() { + return new CmpExpr(ctx, lhs, op.not(), rhs, type); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ConditionalStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ConditionalStmt.java new file mode 100644 index 0000000000..a771a2f8d9 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ConditionalStmt.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.pcode.struct.StructuredSleigh.Stmt; + +abstract class ConditionalStmt extends AbstractStmt { + protected final RValInternal cond; + protected final AbstractStmt stmt; + + protected ConditionalStmt(StructuredSleigh ctx, RVal cond, Stmt stmt) { + super(ctx); + this.cond = (RValInternal) cond; + this.stmt = ((AbstractStmt) stmt).reparent(this); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ContinueStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ContinueStmt.java new file mode 100644 index 0000000000..7b316f9373 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ContinueStmt.java @@ -0,0 +1,29 @@ +/* ### + * 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.struct; + +import ghidra.pcode.struct.StructuredSleigh.Label; + +class ContinueStmt extends LoopTruncateStmt { + protected ContinueStmt(StructuredSleigh ctx) { + super(ctx); + } + + @Override + protected Label getNext() { + return getContainingLoop().lContinue; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DeclStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DeclStmt.java new file mode 100644 index 0000000000..18ce9c2d84 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DeclStmt.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.Label; +import ghidra.program.model.data.DataType; + +class DeclStmt extends AbstractStmt { + private final String name; + private final DataType type; + + protected DeclStmt(StructuredSleigh ctx, LocalVar decl) { + super(ctx); + this.name = decl.getName(); + this.type = decl.getType(); + } + + @Override + protected String generate(Label next, Label fall) { + return "local " + name + ":" + type.getLength() + ";\n" + + next.genGoto(fall); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DefaultUseropDecl.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DefaultUseropDecl.java new file mode 100644 index 0000000000..7aba434de5 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DefaultUseropDecl.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.struct; + +import java.util.List; + +import ghidra.pcode.struct.StructuredSleigh.*; +import ghidra.program.model.data.DataType; + +public class DefaultUseropDecl implements UseropDecl { + private final StructuredSleigh ctx; + private final DataType retType; + private final String name; + private final List paramTypes; + + public DefaultUseropDecl(StructuredSleigh ctx, DataType retType, String name, + List paramTypes) { + this.ctx = ctx; + this.retType = retType; + this.name = name; + this.paramTypes = List.copyOf(paramTypes); + } + + @Override + public DataType getReturnType() { + return retType; + } + + @Override + public String getName() { + return name; + } + + @Override + public List getParameterTypes() { + return paramTypes; + } + + @Override + public StmtWithVal call(RVal... args) { + if (paramTypes.size() != args.length) { + ctx.emitParameterCountMismatch(this, List.of(args)); + } + for (int i = 0; i < args.length && i < paramTypes.size(); i++) { + DataType pType = paramTypes.get(i); + RVal a = args[i]; + if (!ctx.isAssignable(pType, a.getType())) { + ctx.emitParameterTypeMismatch(this, i, a); + } + } + if (retType.getLength() == 0) { + return new VoidExprStmt(ctx, new CallExpr(ctx, this, List.of(args))); + } + return new AssignStmt(ctx, ctx.temp(retType), new CallExpr(ctx, this, List.of(args))); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DefaultVar.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DefaultVar.java new file mode 100644 index 0000000000..37fc9a3b16 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DefaultVar.java @@ -0,0 +1,120 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.app.plugin.processors.sleigh.SleighException; +import ghidra.pcode.struct.StructuredSleigh.Var; +import ghidra.pcodeCPort.slghsymbol.SleighSymbol; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.lang.PcodeParser; +import ghidra.util.database.UndoableTransaction; + +class DefaultVar implements LValInternal, Var { + /** + * The rule for name collision + */ + enum Check { + /** The name may or may not already be defined by the language */ + NONE { + @Override + void check(PcodeParser parser, String name) { + // Do nothing + } + }, + /** The name must already be defined by the language so it can be imported */ + IMPORT { + @Override + void check(PcodeParser parser, String name) { + // TODO: Also check the type is compatible? + SleighSymbol symbol = parser.findSymbol(name); + if (symbol == null) { + throw new SleighException("Missing symbol '" + name + "'"); + } + } + }, + /** The name cannot already be defined by the language so it is free */ + FREE { + @Override + void check(PcodeParser parser, String name) { + SleighSymbol symbol = parser.findSymbol(name); + if (symbol != null) { + throw new SleighException( + "Duplicate symbol '" + name + "': Already defined by the language"); + } + } + }; + + /** + * Check that the given name obeys the rule + * + * @param parser a parser bound to the target language + * @param name the name to check + */ + abstract void check(PcodeParser parser, String name); + } + + protected final StructuredSleigh ctx; + protected final String name; + protected final DataType type; + + /** + * Create a new variable + * + * @param ctx the context + * @param check the rule to check the name + * @param name the name of the variable + * @param type the type of the variable + */ + protected DefaultVar(StructuredSleigh ctx, Check check, String name, DataType type) { + check.check(ctx.parser, name); + this.ctx = ctx; + this.name = name; + try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type", true)) { + this.type = ctx.dtm.resolve(type, DataTypeConflictHandler.DEFAULT_HANDLER); + } + } + + @Override + public Var cast(DataType type) { + return new DefaultVar(ctx, Check.NONE, name, type); + } + + @Override + public String toString() { + return "<" + getClass().getSimpleName() + ": " + name + " : " + type + ">"; + } + + @Override + public StructuredSleigh getContext() { + return ctx; + } + + @Override + public String getName() { + return name; + } + + @Override + public DataType getType() { + return type; + } + + @Override + public String generate() { + return name; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DerefExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DerefExpr.java new file mode 100644 index 0000000000..73c0997810 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/DerefExpr.java @@ -0,0 +1,54 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.LVal; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.DataType; + +class DerefExpr extends Expr implements LValInternal { + private final AddressSpace space; + private final RValInternal addr; + + private DerefExpr(StructuredSleigh ctx, AddressSpace space, RValInternal addr, + DataType type) { + super(ctx, type); + this.space = space; + this.addr = addr; + } + + protected DerefExpr(StructuredSleigh ctx, AddressSpace space, RValInternal addr) { + this(ctx, space, addr, ctx.computeDerefType(addr)); + } + + @Override + public LVal cast(DataType type) { + return new DerefExpr(ctx, space, addr, type); + } + + @Override + public String toString() { + return ""; + } + + @Override + public String generate() { + String spacePiece = + ctx.language.getDefaultSpace() == space ? "" : ("[" + space.getName() + "]"); + String sizePiece = type.getLength() == 0 ? "" : (":" + type.getLength()); + return "(*" + spacePiece + sizePiece + " " + addr.generate() + ")"; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/Expr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/Expr.java new file mode 100644 index 0000000000..4812568118 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/Expr.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.struct; + +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.util.database.UndoableTransaction; + +public abstract class Expr implements RValInternal { + protected final StructuredSleigh ctx; + protected final DataType type; + + protected Expr(StructuredSleigh ctx, DataType type) { + this.ctx = ctx; + + try (UndoableTransaction tid = UndoableTransaction.start(ctx.dtm, "Resolve type", true)) { + this.type = ctx.dtm.resolve(type, DataTypeConflictHandler.DEFAULT_HANDLER); + } + } + + @Override + public StructuredSleigh getContext() { + return ctx; + } + + @Override + public DataType getType() { + return type; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/FieldExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/FieldExpr.java new file mode 100644 index 0000000000..5c6fdb8e81 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/FieldExpr.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.LVal; +import ghidra.program.model.data.*; + +class FieldExpr extends Expr implements LValInternal { + private final RValInternal parent; + private final int offset; + + private FieldExpr(StructuredSleigh ctx, RValInternal parent, int offset, DataType type) { + super(ctx, type); + this.parent = parent; + this.offset = offset; + } + + private FieldExpr(StructuredSleigh ctx, RValInternal parent, DataTypeComponent component) { + this(ctx, parent, component.getOffset(), new PointerDataType(component.getDataType())); + } + + protected FieldExpr(StructuredSleigh ctx, RValInternal parent, String name) { + this(ctx, parent, ctx.findComponent(parent, name)); + } + + @Override + public LVal cast(DataType type) { + return new FieldExpr(ctx, parent, offset, type); + } + + @Override + public String toString() { + return ""; + } + + @Override + public String generate() { + return "(" + parent.generate() + " + " + offset + ")"; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ForStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ForStmt.java new file mode 100644 index 0000000000..e641c6ad63 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ForStmt.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.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.*; + +class ForStmt extends LoopStmt { + private final AbstractStmt init; + private final AbstractStmt step; + + protected ForStmt(StructuredSleigh ctx, Stmt init, RVal cond, Stmt step, + AbstractStmt stmt) { + super(ctx, cond, stmt); + this.init = ((AbstractStmt) init).reparent(this); + this.step = ((AbstractStmt) step).reparent(this); + } + + @Override + protected String generate(Label next, Label fall) { + Label lTest = ctx.new FreshLabel(); + Label lBegin = ctx.new FreshLabel(); + Label lExit = lBreak = next.freshOrBorrow(); + Label lStep = lContinue = ctx.new FreshLabel(); + + String initGen = init.generate(lTest, lTest); + String testGen = lExit.genGoto(cond.notb(), lBegin); + String stmtGen = stmt.generate(lStep, lStep); + String stepGen = step.generate(lTest, fall); + return initGen + + lTest.genAnchor() + + testGen + + lBegin.genAnchor() + + stmtGen + + lStep.genAnchor() + + stepGen + + lExit.genAnchor(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/IfStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/IfStmt.java new file mode 100644 index 0000000000..5a3bdc1a12 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/IfStmt.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.*; + +class IfStmt extends ConditionalStmt { + protected AbstractStmt elseStmt; + + protected IfStmt(StructuredSleigh ctx, RVal cond, Stmt stmt) { + super(ctx, cond, stmt); + } + + @Override + protected String generate(Label next, Label fall) { + if (elseStmt == null) { + if (stmt.isSingleGoto()) { + return stmt.getNext().genGoto(cond, fall); + } + + Label lTrue = ctx.new FreshLabel(); + Label lFalse = next.freshOrBorrow(); + + String condGen = lFalse.genGoto(cond.notb(), lTrue); + String stmtGen = stmt.generate(next, fall); + return condGen + + lTrue.genAnchor() + + stmtGen + + lFalse.genAnchor(); + } + + Label lFalse = ctx.new FreshLabel(); + Label lTrue = ctx.new FreshLabel(); + Label lExit = next.freshOrBorrow(); + + String condGen = lTrue.genGoto(cond, lFalse); + String elseGen = elseStmt.generate(lExit, lTrue); + String stmtGen = stmt.generate(next, fall); + + return condGen + + lFalse.genAnchor() + + elseGen + + lTrue.genAnchor() + + stmtGen + + lExit.genAnchor(); + } + + protected void addElse(Stmt elseStmt) { + assert this.elseStmt == null; + this.elseStmt = ((AbstractStmt) elseStmt).reparent(this); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/IndexExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/IndexExpr.java new file mode 100644 index 0000000000..b5681bdbc1 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/IndexExpr.java @@ -0,0 +1,52 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.LVal; +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.program.model.data.DataType; + +class IndexExpr extends Expr implements LValInternal { + private final RValInternal base; + private final RValInternal index; + private final int elemLen; + + private IndexExpr(StructuredSleigh ctx, RVal base, RVal index, int elemLen, DataType type) { + super(ctx, type); + this.base = (RValInternal) base; + this.index = (RValInternal) index; + this.elemLen = elemLen; + } + + protected IndexExpr(StructuredSleigh ctx, RVal base, RVal index) { + this(ctx, base, index, ctx.computeElementLength(base), base.getType()); + } + + @Override + public LVal cast(DataType type) { + return new IndexExpr(ctx, base, index, elemLen, type); + } + + @Override + public String toString() { + return ""; + } + + @Override + public String generate() { + return "(" + base.generate() + " + (" + index.generate() + "*" + elemLen + "))"; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/InvExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/InvExpr.java new file mode 100644 index 0000000000..9db5f40ca5 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/InvExpr.java @@ -0,0 +1,29 @@ +/* ### + * 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.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; + +class InvExpr extends UnExpr { + public InvExpr(StructuredSleigh ctx, RVal u) { + super(ctx, "~", u, u.getType()); + } + + @Override + public RVal noti() { + return u; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LValInternal.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LValInternal.java new file mode 100644 index 0000000000..3fbbb05274 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LValInternal.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.*; + +interface LValInternal extends LVal, RValInternal { + @Override + default LVal field(String name) { + return new FieldExpr(getContext(), this, name); + } + + @Override + default LVal index(RVal index) { + return new IndexExpr(getContext(), this, index); + } + + @Override + default LVal index(long index) { + // TODO: Since constant, fold with type size + return index(getContext().lit(index, 8)); + } + + @Override + default AssignStmt set(RVal rhs) { + StructuredSleigh ctx = getContext(); + if (!ctx.isAssignable(getType(), rhs.getType())) { + ctx.emitAssignmentTypeMismatch(this, rhs); + } + return new AssignStmt(ctx, this, rhs); + } + + @Override + default AssignStmt set(long rhs) { + return set(getContext().lit(rhs, getType().getLength())); + } + + @Override + default StmtWithVal addiTo(RVal rhs) { + return set(addi(rhs)); + } + + @Override + default StmtWithVal addiTo(long rhs) { + return set(addi(rhs)); + } + + @Override + default StmtWithVal inc() { + return addiTo(1); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesPcodeExecutorStateMixin.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LangVar.java similarity index 72% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesPcodeExecutorStateMixin.java rename to Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LangVar.java index 98adf5d55f..87d8958274 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/pcode/exec/trace/BytesPcodeExecutorStateMixin.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LangVar.java @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.pcode.exec.trace; +package ghidra.pcode.struct; -public interface BytesPcodeExecutorStateMixin { - T fromConstant(long constant); +import ghidra.program.model.data.DataType; - long offsetToLong(A offset); - - byte[] toBytes(T val, int size); - - T fromBytes(byte[] data); +class LangVar extends DefaultVar { + protected LangVar(StructuredSleigh ctx, String name, DataType type) { + super(ctx, Check.IMPORT, name, type); + } } diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralExpr.java new file mode 100644 index 0000000000..e4c05f1321 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralExpr.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.program.model.data.DataType; + +class LiteralExpr extends Expr { + private final long val; + private final int size; + + protected LiteralExpr(StructuredSleigh ctx, long val, int size, DataType type) { + super(ctx, type); + this.val = val; + this.size = size; + } + + @Override + public RVal cast(DataType type) { + return new LiteralExpr(ctx, val, size, type); + } + + @Override + public String toString() { + return ""; + } + + @Override + public String generate() { + return "0x" + Long.toHexString(val) + ":" + size; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralFloatExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralFloatExpr.java new file mode 100644 index 0000000000..39e32e3ae5 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralFloatExpr.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.program.model.data.DataType; + +class LiteralFloatExpr extends LiteralExpr { + private LiteralFloatExpr(StructuredSleigh ctx, long val, int size, DataType type) { + super(ctx, val, size, type); + } + + private LiteralFloatExpr(StructuredSleigh ctx, double val, int size, DataType type) { + this(ctx, ctx.encodeFloat(val, size), size, type); + } + + protected LiteralFloatExpr(StructuredSleigh ctx, double val, DataType type) { + this(ctx, val, ctx.computeFloatSize(type), type); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralLongExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralLongExpr.java new file mode 100644 index 0000000000..0541c4b7ce --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LiteralLongExpr.java @@ -0,0 +1,29 @@ +/* ### + * 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.struct; + +import ghidra.program.model.data.AbstractIntegerDataType; +import ghidra.program.model.data.DataType; + +public class LiteralLongExpr extends LiteralExpr { + private LiteralLongExpr(StructuredSleigh ctx, long val, int size, DataType type) { + super(ctx, val, size, type); + } + + protected LiteralLongExpr(StructuredSleigh ctx, long val, int size) { + this(ctx, val, size, AbstractIntegerDataType.getSignedDataType(size, ctx.dtm)); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LocalVar.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LocalVar.java new file mode 100644 index 0000000000..d18eb613d2 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LocalVar.java @@ -0,0 +1,24 @@ +/* ### + * 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.struct; + +import ghidra.program.model.data.DataType; + +class LocalVar extends DefaultVar { + protected LocalVar(StructuredSleigh ctx, String name, DataType type) { + super(ctx, Check.FREE, name, type); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LoopStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LoopStmt.java new file mode 100644 index 0000000000..43cad7c752 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LoopStmt.java @@ -0,0 +1,29 @@ +/* ### + * 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.struct; + +import ghidra.pcode.struct.StructuredSleigh.*; + +abstract class LoopStmt extends ConditionalStmt { + /** To be set during generation */ + protected Label lBreak; + /** To be set during generation */ + protected Label lContinue; + + public LoopStmt(StructuredSleigh ctx, RVal cond, Stmt stmt) { + super(ctx, cond, stmt); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LoopTruncateStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LoopTruncateStmt.java new file mode 100644 index 0000000000..189c060c37 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/LoopTruncateStmt.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.Label; +import ghidra.pcode.struct.StructuredSleigh.StructuredSleighError; + +abstract class LoopTruncateStmt extends AbstractStmt { + protected LoopTruncateStmt(StructuredSleigh ctx) { + super(ctx); + } + + protected LoopStmt getContainingLoop() { + LoopStmt loop = nearest(LoopStmt.class); + if (loop == null) { + throw new StructuredSleighError("No loop to break or continue"); + } + return loop; + } + + @Override + protected String generate(Label next, Label fall) { + return getNext().genGoto(fall); + } + + @Override + protected boolean isSingleGoto() { + return true; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/NotExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/NotExpr.java new file mode 100644 index 0000000000..c47dc5981e --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/NotExpr.java @@ -0,0 +1,29 @@ +/* ### + * 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.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; + +class NotExpr extends UnExpr { + protected NotExpr(StructuredSleigh ctx, RVal u) { + super(ctx, "!", u, u.getType()); + } + + @Override + public RVal notb() { + return u; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RValInternal.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RValInternal.java new file mode 100644 index 0000000000..1c532db041 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RValInternal.java @@ -0,0 +1,363 @@ +/* ### + * 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.struct; + +import ghidra.pcode.struct.StructuredSleigh.LVal; +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.DataType; + +interface RValInternal extends RVal { + StructuredSleigh getContext(); + + @Override + DataType getType(); + + String generate(); + + @Override + default LVal deref() { + return deref(getContext().language.getDefaultSpace()); + } + + @Override + default LVal deref(AddressSpace space) { + return new DerefExpr(getContext(), space, this); + } + + @Override + default RVal notb() { + return new NotExpr(getContext(), this); + } + + @Override + default RVal noti() { + return new InvExpr(getContext(), this); + } + + @Override + default RVal eq(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.EQ, rhs); + } + + @Override + default RVal eq(long rhs) { + return eq(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal eqf(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.EQF, rhs); + } + + @Override + default RVal neq(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.NEQ, rhs); + } + + @Override + default RVal neq(long rhs) { + return neq(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal neqf(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.NEQF, rhs); + } + + @Override + default RVal ltiu(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.LTIU, rhs); + } + + @Override + default RVal ltiu(long rhs) { + return ltiu(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal ltis(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.LTIS, rhs); + } + + @Override + default RVal ltis(long rhs) { + return ltis(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal ltf(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.LTF, rhs); + } + + @Override + default RVal gtiu(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.GTIU, rhs); + } + + @Override + default RVal gtiu(long rhs) { + return gtiu(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal gtis(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.GTIS, rhs); + } + + @Override + default RVal gtis(long rhs) { + return gtis(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal gtf(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.GTF, rhs); + } + + @Override + default RVal lteiu(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.LTEIU, rhs); + } + + @Override + default RVal lteiu(long rhs) { + return lteiu(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal lteis(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.LTEIS, rhs); + } + + @Override + default RVal lteis(long rhs) { + return lteis(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal ltef(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.LTEF, rhs); + } + + @Override + default RVal gteiu(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.GTEIU, rhs); + } + + @Override + default RVal gteiu(long rhs) { + return gteiu(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal gteis(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.GTEIS, rhs); + } + + @Override + default RVal gteis(long rhs) { + return gteis(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal gtef(RVal rhs) { + return new CmpExpr(getContext(), this, CmpExpr.Op.GTEF, rhs); + } + + @Override + default RVal orb(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.ORB, rhs); + } + + @Override + default RVal orb(long rhs) { + return orb(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal ori(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.ORI, rhs); + } + + @Override + default RVal ori(long rhs) { + return ori(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal xorb(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.XORB, rhs); + } + + @Override + default RVal xorb(long rhs) { + return xorb(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal xori(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.XORI, rhs); + } + + @Override + default RVal xori(long rhs) { + return xori(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal andb(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.ANDB, rhs); + } + + @Override + default RVal andb(long rhs) { + return andb(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal andi(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.ANDI, rhs); + } + + @Override + default RVal andi(long rhs) { + return andi(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal shli(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.SHLI, rhs); + } + + @Override + default RVal shli(long rhs) { + return shli(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal shriu(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.SHRIU, rhs); + } + + @Override + default RVal shriu(long rhs) { + return shriu(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal shris(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.SHRIS, rhs); + } + + @Override + default RVal shris(long rhs) { + return shris(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal addi(RVal rhs) { + // TODO: Validate types? At least warn? + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.ADDI, rhs); + } + + @Override + default RVal addi(long rhs) { + return addi(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal addf(RVal rhs) { + // TODO: Validate types? At least warn? + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.ADDF, rhs); + } + + @Override + default RVal subi(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.SUBI, rhs); + } + + @Override + default RVal subi(long rhs) { + return subi(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal subf(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.SUBF, rhs); + } + + @Override + default RVal muli(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.MULI, rhs); + } + + @Override + default RVal muli(long rhs) { + return muli(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal mulf(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.MULF, rhs); + } + + @Override + default RVal diviu(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.DIVIU, rhs); + } + + @Override + default RVal diviu(long rhs) { + return diviu(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal divis(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.DIVIS, rhs); + } + + @Override + default RVal divis(long rhs) { + return divis(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal divf(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.DIVF, rhs); + } + + @Override + default RVal remiu(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.REMIU, rhs); + } + + @Override + default RVal remiu(long rhs) { + return remiu(getContext().lit(rhs, getType().getLength())); + } + + @Override + default RVal remis(RVal rhs) { + return new ArithBinExpr(getContext(), this, ArithBinExpr.Op.REMIS, rhs); + } + + @Override + default RVal remis(long rhs) { + return remis(getContext().lit(rhs, getType().getLength())); + } + +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RawExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RawExpr.java new file mode 100644 index 0000000000..d9a6f7cea5 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RawExpr.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.VoidDataType; + +public class RawExpr extends Expr { + private final String expr; + + private RawExpr(StructuredSleigh ctx, String expr, DataType type) { + super(ctx, type); + this.expr = expr; + } + + protected RawExpr(StructuredSleigh ctx, String expr) { + this(ctx, expr, VoidDataType.dataType); + } + + @Override + public RVal cast(DataType type) { + return new RawExpr(ctx, expr, type); + } + + @Override + public String generate() { + return "(" + expr + ")"; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RawStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RawStmt.java new file mode 100644 index 0000000000..9c1adcc3d4 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RawStmt.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.Label; + +class RawStmt extends AbstractStmt { + private final String stmt; + + protected RawStmt(StructuredSleigh ctx, String stmt) { + super(ctx); + this.stmt = stmt; + } + + @Override + protected String generate(Label next, Label fall) { + return stmt + ";\n" + next.genGoto(fall); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ResultStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ResultStmt.java new file mode 100644 index 0000000000..3a767014fe --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ResultStmt.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.struct; + +import java.util.Objects; + +import ghidra.pcode.exec.SleighPcodeUseropDefinition; +import ghidra.pcode.struct.StructuredSleigh.Label; +import ghidra.pcode.struct.StructuredSleigh.RVal; + +class ResultStmt extends AbstractStmt { + private final RValInternal result; + + protected ResultStmt(StructuredSleigh ctx, RVal result) { + super(ctx); + this.result = (RValInternal) result; + } + + @Override + protected String generate(Label next, Label fall) { + RoutineStmt routine = Objects.requireNonNull(nearest(RoutineStmt.class)); + + if (!ctx.isAssignable(routine.retType, result.getType())) { + ctx.emitResultTypeMismatch(routine, result); + } + + return SleighPcodeUseropDefinition.OUT_SYMBOL_NAME + " = " + result.generate() + ";\n" + + routine.lReturn.genGoto(fall); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ReturnStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ReturnStmt.java new file mode 100644 index 0000000000..e11f288209 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/ReturnStmt.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. + */ +package ghidra.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.Label; +import ghidra.pcode.struct.StructuredSleigh.RVal; + +class ReturnStmt extends AbstractStmt { + private final RValInternal target; + + protected ReturnStmt(StructuredSleigh ctx, RVal target) { + super(ctx); + this.target = (RValInternal) target; + } + + @Override + protected String generate(Label next, Label fall) { + return "return " + target.generate() + ";\n"; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RoutineStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RoutineStmt.java new file mode 100644 index 0000000000..3083d6fc7a --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/RoutineStmt.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.Label; +import ghidra.program.model.data.DataType; + +class RoutineStmt extends BlockStmt { + protected final String name; + protected final DataType retType; + + /** To be set during generation */ + protected Label lReturn; + + protected RoutineStmt(StructuredSleigh ctx, String name, DataType retType, Runnable body) { + super(ctx, body); + this.name = name; + this.retType = retType; + } + + @Override + protected String generate(Label next, Label fall) { + if (children.isEmpty()) { + return ""; + } + Label lExit = lReturn = next.freshOrBorrow(); + // This is an odd case, because it's the root: use lExit instead of fall + String blockGen = super.generate(lReturn, lExit); + return blockGen + + lExit.genAnchor(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/StructuredSleigh.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/StructuredSleigh.java new file mode 100644 index 0000000000..0ac61819c4 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/StructuredSleigh.java @@ -0,0 +1,1938 @@ +/* ### + * 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.struct; + +import java.lang.annotation.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.*; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.lifecycle.Internal; +import ghidra.pcode.emu.unix.AbstractEmuUnixSyscallUseropLibrary; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.SleighPcodeUseropDefinition.Builder; +import ghidra.pcode.exec.SleighPcodeUseropDefinition.Factory; +import ghidra.pcode.floatformat.FloatFormatFactory; +import ghidra.pcode.struct.DefaultVar.Check; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.Program; +import ghidra.util.Msg; +import ghidra.util.data.DataTypeParser; +import ghidra.util.data.DataTypeParser.AllowedDataTypes; +import ghidra.util.exception.CancelledException; +import utilities.util.AnnotationUtilities; + +/** + * The primary class for using the "structured sleigh" DSL + * + *

+ * This provides some conveniences for generating Sleigh source code, which is otherwise completely + * typeless and lacks basic control structure. In general, the types are not used so much for type + * checking as they are for easing access to fields of C structures, array indexing, etc. + * Furthermore, it becomes possible to re-use code when data types differ among platforms, so long + * as those variations are limited to field offsets and type sizes. + * + *

+ * Start by declaring an extension of {@link StructuredSleigh}. Then put any necessary "forward + * declarations" as fields of the class. Then declare methods annotated with + * {@link StructuredUserop}. Inside those methods, all the protected methods of this class are + * accessible, providing a DSL (as far as Java can provide :/ ) for writing Sleigh code. For + * example: + * + *

+ * class MyStructuredPart extends StructuredSleigh {
+ * 	Var r0 = lang("r0", "/long");
+ * 
+ * 	protected MyStructuredPart() {
+ * 		super(program);
+ * 	}
+ * 
+ * 	@StructuredUserop
+ * 	public void my_userop() {
+ * 		r0.set(0xdeadbeef);
+ * 	}
+ * }
+ * 
+ * + *

+ * This will simply generate the source "{@code r0 = 0xdeadbeef:4}", but it also provides all the + * scaffolding to compile and invoke the userop as in a {@link PcodeUseropLibrary}. Internal methods + * -- which essentially behave like macros -- may be used, so only annotate methods to export as + * userops. For a more complete and practical example of using structured sleigh in a userop + * library, see {@link AbstractEmuUnixSyscallUseropLibrary}. + * + *

+ * Structured sleigh is also usable in a more standalone manner: + * + *

+ * StructuredSleigh ss = new StructuredSleigh(compilerSpec) {
+ * 	@StructuredUserop
+ * 	public void my_userop() {
+ * 		// Something interesting, I'm sure
+ * 	}
+ * };
+ * 
+ * SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop");
+ * // To print source
+ * myUserop.getLines().forEach(System.out::print);
+ * 
+ * // To compile for given parameters (none in this case) and print the p-code
+ * Register r0 = lang.getRegister("r0");
+ * System.out.println(myUserop.programFor(new Varnode(r0.getAddress(), r0.getNumBytes()), List.of(),
+ * 	PcodeUseropLibrary.NIL));
+ * 
+ * 
+ * 

+ * Known limitations: + *

    + *
  • Recursion is not really possible. Currently, local variables of a userop do not actually get + * their own unique storage per invocation record. Furthermore, it's possible that local variable in + * different userop definition will be assigned the same storage location, meaning they could be + * unintentionally aliased if one invokes the other. Care should be taken when invoking one + * sleigh-based userop from another, or it should be avoided altogether until this limitation is + * addressed. It's generally safe to allow such invocations at the tail.
  • + *
  • Parameters are passed by reference. Essentially, the formal argument becomes an alias to its + * parameter. This is more a feature, but can be surprising if C semantics are expected.
  • + *
  • Calling one Structured Sleigh userop from another still requires a "external declaration" of + * the callee, despite being defined in the same "compilation unit."
  • + *
+ */ +public class StructuredSleigh { + private static final Map, Set> CACHE_BY_CLASS = new HashMap<>(); + + private static Set collectDefinitions(Class cls) { + return AnnotationUtilities.collectAnnotatedMethods(StructuredUserop.class, cls); + } + + /** + * "Export" a method as a p-code userop implemented using p-code compiled from structured Sleigh + * + *

+ * This is applied to methods used to generate Sleigh source code. Take note that the method is + * only invoked once (for a given library instance) to generate code. Thus, beware of + * non-determinism during code generation. For example, implementing something like + * {@code rdrnd} in structured Sleigh is rife with peril. Take the following implementation: + * + *

+	 * @StructuredUserop
+	 * public void rdrnd() {
+	 * 	r0.set(Random.nextLong()); // BAD: Die rolled once at compile time
+	 * }
+	 * 
+ * + *

+ * The random number will be generated once at structured Sleigh compilation time, and then that + * same number used on every invocation of the p-code userop. Instead, this userop should be + * implemented using a Java callback, i.e., {@link AnnotatedPcodeUseropLibrary.PcodeUserop}. + * + *

+ * The userop may accept parameters and return a result. To accept parameters, declare them in + * the Java method signature and annotate them with {@link Param}. To return a result, name the + * appropriate type in the {@link #type()} attribute and use + * {@link StructuredSleigh#_result(RVal)}. The Java return type of the method must still be + * {@code void}. Note that parameters are passed by reference, so results can also be + * communicated by setting a parameter's value. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.METHOD) + protected @interface StructuredUserop { + /** + * The data type path for the "return type" of the userop. See + * {@link StructuredSleigh#type(String)}. + */ + String type() default "void"; + } + + /** + * Declare a parameter of the p-code userop + * + *

+ * This is attached to parameters of methods annotated with {@link StructuredUserop}, providing + * the type and name of the parameter. The Java type of the parameter must be {@link Var}. For + * example: + * + *

+	 * @StructuredUserop
+	 * public void twice(@Param(name = "p0", type = "void *") Var p0) {
+	 * 	_result(p0.mul(2));
+	 * }
+	 * 
+ */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.PARAMETER) + protected @interface Param { + /** + * The data type path for the type of the parameter. See + * {@link StructuredSleigh#type(String)}. + */ + String type(); + + /** + * The name of the parameter in the output Sleigh code + * + *

+ * If the variable is referenced via {@link StructuredSleigh#s(String)} or + * {@link StructuredSleigh#e(String)}, then is it necessary to specify the name used in the + * Sleigh code. Otherwise, the name is typically derived from the Java parameter name, which + * Java platforms are not required to preserve. If the variable is referenced only by its + * handle, then the name will be consistent and unique in the generated Sleigh code. When + * diagnosing Structured Sleigh compilation issues, it may be desirable to specify the + * variable name, regardless. + */ + String name() default ""; + } + + /** + * The declaration of an "imported" userop + * + *

+ * Because Sleigh is typeless, structured Sleigh needs additional type information about the + * imported userop. The referenced userop may be implemented by another library and may be a + * Java callback or a p-code based userop, or something else. Note that if the userop is + * missing, it might not be detected until the calling Sleigh code is invoked for the first + * time. + */ + protected interface UseropDecl { + /** + * Get the userop's return type + * + * @return the return type + */ + DataType getReturnType(); + + /** + * Get the name of the userop + * + * @return the name + */ + String getName(); + + /** + * Get the parameter types of the userop + * + * @return the types, in order of parameters + */ + List getParameterTypes(); + + /** + * Generate an invocation of the userop + * + *

+ * If the userop has a result type, then the resulting statement will also have a value. If + * the user has a {@code void} result type, the "value" should not be used. Otherwise, a + * warning will likely be generated, and the "result value" will be undefined. + * + * @param args the arguments to pass + * @return a handle to the statement + */ + StmtWithVal call(RVal... args); + } + + /** + * A value which can only be used on the right-hand side of an assignment + */ + protected interface RVal { + /** + * Get the type of the value + * + * @return the type + */ + DataType getType(); + + /** + * Cast the value to the given type + * + *

+ * This functions like a C-style pointer cast. There are no implied operations or + * conversions. Notably, casting between integers and floats is just a re-interpretation of + * the underlying bits. + * + * @param type the type + * @return a handle to the resulting value + */ + RVal cast(DataType type); + + /** + * Generate a dereference (in the C sense) + * + *

+ * The value is treated as an address, and the result is essentially a variable in the given + * target address space. + * + * @param space the address space of the result + * @return a handle to the resulting value + */ + LVal deref(AddressSpace space); + + /** + * Generate a dereference (in the C sense) in the default address space + * + * @return a handle to the resulting value + */ + LVal deref(); + + /** + * Generate boolean inversion + * + * @return a handle to the resulting value + */ + RVal notb(); + + /** + * Generate integer (bitwise) inversion + * + * @return a handle to the resulting value + */ + RVal noti(); + + /** + * Generate integer comparison: equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal eq(RVal rhs); + + /** + * Generate integer comparison: equal to + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal eq(long rhs); + + /** + * Generate float comparison: equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal eqf(RVal rhs); + + /** + * Generate integer comparison: not equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal neq(RVal rhs); + + /** + * Generate integer comparison: not equal to + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal neq(long rhs); + + /** + * Generate float comparison: not equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal neqf(RVal rhs); + + /** + * Generate unsigned integer comparison: less than + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal ltiu(RVal rhs); + + /** + * Generate unsigned integer comparison: less than + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal ltiu(long rhs); + + /** + * Generate signed integer comparison: less than + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal ltis(RVal rhs); + + /** + * Generate signed integer comparison: less than + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal ltis(long rhs); + + /** + * Generate unsigned integer comparison: less than + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal ltf(RVal rhs); + + /** + * Generate unsigned integer comparison: greater than + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal gtiu(RVal rhs); + + /** + * Generate unsigned integer comparison: greater than + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal gtiu(long rhs); + + /** + * Generate signed integer comparison: greater than + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal gtis(RVal rhs); + + /** + * Generate signed integer comparison: greater than + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal gtis(long rhs); + + /** + * Generate float comparison: greater than + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal gtf(RVal rhs); + + /** + * Generate unsigned integer comparison: less than or equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal lteiu(RVal rhs); + + /** + * Generate unsigned integer comparison: less than or equal to + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal lteiu(long rhs); + + /** + * Generate signed integer comparison: less than or equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal lteis(RVal rhs); + + /** + * Generate signed integer comparison: less than or equal to + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal lteis(long rhs); + + /** + * Generate float comparison: less than or equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal ltef(RVal rhs); + + /** + * Generate unsigned integer comparison: greater than or equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal gteiu(RVal rhs); + + /** + * Generate unsigned integer comparison: greater than or equal to + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal gteiu(long rhs); + + /** + * Generate signed integer comparison: greater than or equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal gteis(RVal rhs); + + /** + * Generate signed integer comparison: greater than or equal to + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal gteis(long rhs); + + /** + * Generate float comparison: greater than or equal to + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal gtef(RVal rhs); + + /** + * Generate boolean or + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal orb(RVal rhs); + + /** + * Generate boolean or + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal orb(long rhs); + + /** + * Generate an integer (bitwise) or + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal ori(RVal rhs); + + /** + * Generate an integer (bitwise) or + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal ori(long rhs); + + /** + * Generate boolean exclusive or + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal xorb(RVal rhs); + + /** + * Generate boolean exclusive or + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal xorb(long rhs); + + /** + * Generate an integer (bitwise) exclusive or + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal xori(RVal rhs); + + /** + * Generate an integer (bitwise) exclusive or + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal xori(long rhs); + + /** + * Generate boolean and + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal andb(RVal rhs); + + /** + * Generate boolean and + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal andb(long rhs); + + /** + * Generate an integer (bitwise) and + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal andi(RVal rhs); + + /** + * Generate an integer (bitwise) and + * + * @param rhs the immediate operand (mask) + * @return a handle to the resulting value + */ + RVal andi(long rhs); + + /** + * Generate bit shift to the left + * + * @param rhs the second operand (shift amount) + * @return a handle to the resulting value + */ + RVal shli(RVal rhs); + + /** + * Generate bit shift to the left + * + * @param rhs the immediate operand (shift amount) + * @return a handle to the resulting value + */ + RVal shli(long rhs); + + /** + * Generate unsigned bit shift to the right + * + * @param rhs the second operand (shift amount) + * @return a handle to the resulting value + */ + RVal shriu(RVal rhs); + + /** + * Generate unsigned bit shift to the right + * + * @param rhs the immediate operand (shift amount) + * @return a handle to the resulting value + */ + RVal shriu(long rhs); + + /** + * Generate signed bit shift to the right + * + * @param rhs the second operand (shift amount) + * @return a handle to the resulting value + */ + RVal shris(RVal rhs); + + /** + * Generate signed bit shift to the right + * + * @param rhs the immediate operand (shift amount) + * @return a handle to the resulting value + */ + RVal shris(long rhs); + + /** + * Generate integer addition + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal addi(RVal rhs); + + /** + * Generate integer addition + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal addi(long rhs); + + /** + * Generate float addition + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal addf(RVal rhs); + + /** + * Generate integer subtraction + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal subi(RVal rhs); + + /** + * Generate integer subtraction of an immediate + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal subi(long rhs); + + /** + * Generate float subtraction + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal subf(RVal rhs); + + /** + * Generate integer multiplication + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal muli(RVal rhs); + + /** + * Generate integer multiplication + * + * @param rhs the immediate operand + * @return a handle to the resulting value + */ + RVal muli(long rhs); + + /** + * Generate float multiplication + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + RVal mulf(RVal rhs); + + /** + * Generate unsigned integer division + * + * @param rhs the divisor + * @return a handle to the resulting value + */ + RVal diviu(RVal rhs); + + /** + * Generate unsigned integer division + * + * @param rhs the immediate divisor + * @return a handle to the resulting value + */ + RVal diviu(long rhs); + + /** + * Generate signed integer division + * + * @param rhs the divisor + * @return a handle to the resulting value + */ + RVal divis(RVal rhs); + + /** + * Generate signed integer division + * + * @param rhs the immediate divisor + * @return a handle to the resulting value + */ + RVal divis(long rhs); + + /** + * Generate float division + * + * @param rhs the divisor + * @return a handle to the resulting value + */ + RVal divf(RVal rhs); + + /** + * Generate unsigned integer division remainder + * + * @param rhs the divisor + * @return a handle to the resulting value + */ + RVal remiu(RVal rhs); + + /** + * Generate unsigned integer division remainder + * + * @param rhs the immediate divisor + * @return a handle to the resulting value + */ + RVal remiu(long rhs); + + /** + * Generate signed integer division remainder + * + * @param rhs the divisor + * @return a handle to the resulting value + */ + RVal remis(RVal rhs); + + /** + * Generate signed integer division remainder + * + * @param rhs the immediate divisor + * @return a handle to the resulting value + */ + RVal remis(long rhs); + } + + /** + * A value which can be used on either side of an assignment + */ + protected interface LVal extends RVal { + @Override + LVal cast(DataType type); + + /** + * Generate a field offset + * + *

+ * This departs subtly from expected C semantics. This value's type is assumed to be a + * pointer to a {@link Composite}. That type is retrieved and the field located. This then + * generates unsigned addition of that field offset to this value. The type of the result is + * a pointer to the type of the field. The C equivalent is "{@code &(val->field)}". + * Essentially, it's just address computation. Note that this operator will fail if the type + * is not a pointer. It cannot be used directly on the {@link Composite} type. + * + *

+ * TODO: Allow direct use on the composite type? Some mechanism for dealing with bitfields? + * Bitfields cannot really work if this is just pointer manipulation. If it's also allowed + * to manipulate raw bytes of a composite, then bitfield access could work. Assignment would + * be odd, but doable. The inputs would be the composite-typed value, the field name, and + * the desired field value. The output would be the resulting composite-typed value. For + * large structures, though, we'd like to manipulate the least number of bytes possible, + * since they'll likely need to be written back out to target memory. + * + * @param name the name of the field + * @return a handle to the resulting value + */ + LVal field(String name); + + /** + * Generate an array index + * + *

+ * This departs subtly from expected C semantics. This value's type is assumed to be a + * pointer to the element type. The size of the element type is computed, and this generates + * unsigned multiplcation of the index and size, then addition to this value. The type of + * the result is the same as this value's type. The C equivalent is "{@code &(val[index])}". + * Essentially, it's just address computation. Note that this operator will fail if the type + * is not a pointer. It cannot be used on an {@link Array} type. + * + * + *

+ * TODO: Allow use of {@link Array} type? While it's possible for authors to specify pointer + * types for their variables, the types of fields they access may not be under their + * control. In particular, between {@link #field(String)} and {@link #index(RVal)}, we ought + * to support accessing fixed-length array fields. + * + * @param index the operand to use as the index into the array + * @return a handle to the resulting value + */ + LVal index(RVal index); + + /** + * Generate an array index + * + * @see #index(RVal) + * @param index the immediate to use as the index into the array + * @return a handle to the resulting value + */ + LVal index(long index); + + /** + * Assign this value + * + * @param rhs the value to assign + * @return a handle to the resulting value + */ + StmtWithVal set(RVal rhs); + + /** + * Assign this value + * + * @param rhs the immediate value to assign + * @return a handle to the resulting value + */ + StmtWithVal set(long rhs); + + /** + * Generate in-place integer addition + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + StmtWithVal addiTo(RVal rhs); + + /** + * Generate in-place integer addition + * + * @param rhs the second operand + * @return a handle to the resulting value + */ + StmtWithVal addiTo(long rhs); + + /** + * Generate an in-place increment (by 1) + * + * @return a handle to the resulting value + */ + StmtWithVal inc(); + } + + /** + * A Sleigh variable + */ + protected interface Var extends LVal { + @Override + Var cast(DataType type); + + /** + * Get the name of the variable as it appears in generated Sleigh code + * + * @return the name + */ + String getName(); + } + + /** + * A Structured Sleigh statement + */ + protected interface Stmt { + // Nothing added + } + + /** + * A Structured Sleigh statement that also has a value + */ + protected interface StmtWithVal extends Stmt, RVal { + } + + /** + * Utility: Get the named component (field) from the given composite data type + * + * @param composite the type + * @param name the name of the component + * @return the found component, or null + */ + protected static DataTypeComponent findComponentByName(Composite composite, String name) { + for (DataTypeComponent dtc : composite.getComponents()) { + if (name.equals(dtc.getFieldName())) { + return dtc; + } + } + return null; + } + + /** + * An exception for unrecoverable Structured Sleigh compilation errors + */ + public static class StructuredSleighError extends RuntimeException { + protected StructuredSleighError(String message) { + super(message); + } + + protected StructuredSleighError(String message, Throwable cause) { + super(message, cause); + } + } + + /** + * A generated Sleigh label + */ + @Internal + protected interface Label { + /** + * Borrow this label + * + *

+ * This should be used whenever a statement (or its children) may need to generate a goto + * using the "next" label passed into it. If "next" is the fall-through label, this will + * generate a fresh label. If this label is already fresh, this will "borrow" the label, + * meaning references will be generated, but it will not produce another anchor. This is to + * prevent generation of duplicate anchors. + * + * @return the resulting label + */ + abstract Label freshOrBorrow(); + + /** + * Generate code for this label + * + * This must be the last method called on the label, because it relies on knowing whether or + * not the label is actually used. (The Sleigh compiler rejects code if it contains unused + * labels.) + * + * @return the Sleigh code + */ + abstract String genAnchor(); + + /** + * Generate a reference to this label as it should appear in a Sleigh "{@code goto}" + * statement + * + * @return the label's expression + */ + abstract String ref(); + + /** + * Generate a goto statement that targets this label + * + * @param fall the label following the goto + * @return the Sleigh code + */ + abstract String genGoto(Label fall); + + /** + * Generate a conditional goto statement that targets this label + * + * @param cond the condition value + * @param fall the label following the goto + * @return the Sleigh code + */ + abstract String genGoto(RVal cond, Label fall); + } + + /** + * A fresh Sleigh label + */ + protected class FreshLabel implements Label { + // Name will be assigned at use + private String name = null; + + private String getName() { + if (name != null) { + return name; + } + return name = "L" + (nextLabel++); + } + + @Override + public Label freshOrBorrow() { + return new BorrowedLabel(this); + } + + @Override + public String genAnchor() { + if (name == null) { + return ""; + } + return "<" + name + ">\n"; + } + + @Override + public String ref() { + return "<" + getName() + ">"; + } + + @Override + public String genGoto(Label fall) { + if (this == fall) { + return ""; + } + return "goto " + ref() + ";\n"; + } + + @Override + public String genGoto(RVal cond, Label fall) { + if (this == fall) { + return ""; + } + return "if " + ((RValInternal) cond).generate() + " " + genGoto(fall); + } + } + + /** + * A label whose anchor placement is already claimed + */ + private class BorrowedLabel implements Label { + protected final FreshLabel borrowed; + + /** + * Replicate the given label, but without an anchor + * + * @param borrowed the already-fresh label + */ + protected BorrowedLabel(FreshLabel borrowed) { + this.borrowed = borrowed; + } + + @Override + public Label freshOrBorrow() { + return this; + } + + @Override + public String genAnchor() { + return ""; + } + + @Override + public String ref() { + return borrowed.ref(); + } + + @Override + public String genGoto(Label fall) { + if (this == fall) { // placed will also check + return ""; + } + return borrowed.genGoto(fall); + } + + @Override + public String genGoto(RVal cond, Label fall) { + if (this == fall) { // placed with also check + return ""; + } + return borrowed.genGoto(cond, fall); + } + } + + /** + * The virtual fall-through label + * + *

+ * The idea is that no one should ever need to generate labels or gotos to achieve fall-through. + * Any attempt to do so probably indicates an implementation error where code generation failed + * to place a label. + */ + private final class FallLabel implements Label { + @Override + public Label freshOrBorrow() { + return new FreshLabel(); + } + + @Override + public String genAnchor() { + return ""; + } + + @Override + public String ref() { + throw new AssertionError(); + } + + @Override + public String genGoto(Label fall) { + return ""; + } + + @Override + public String genGoto(RVal cond, Label fall) { + throw new AssertionError(); + } + } + + /** + * The singleton instance of the fall-through "label." + */ + protected final Label FALL = new FallLabel(); + + // Used only for variable name validation + final PcodeParser parser; + final SleighLanguage language; + private final Factory factory; + + private BlockStmt root; + // Used to determine statement binding, e.g., for "_break" and "_result" + final Deque stack = new LinkedList<>(); + // Collects data types used in annotations and during code generation + final StandAloneDataTypeManager dtm; + private final List dtSources = new ArrayList<>(); + + // The next "free" label + private int nextLabel = 1; + // The next "free" temp variable + private int nextTemp = 1; + + /** A variable to use for unwanted results */ + DefaultVar nil; + + /** + * Bind this Structured Sleigh context to the given program's language, compiler spec, and data + * type manager. + * + * @param program the program + */ + protected StructuredSleigh(Program program) { + this(program.getCompilerSpec()); + addDataTypeSource(program.getDataTypeManager()); + } + + /** + * Bind this Structured Sleigh context to the given compiler spec using only built-in types + * + * @param cs the compiler spec + */ + protected StructuredSleigh(CompilerSpec cs) { + this.language = (SleighLanguage) cs.getLanguage(); + this.parser = SleighProgramCompiler.createParser(language); + this.factory = new Factory(language); + this.dtm = new StandAloneDataTypeManager("/", cs.getDataOrganization()); + + addDataTypeSource(dtm); + addDataTypeSource(BuiltInDataTypeManager.getDataTypeManager()); + + this.nil = new DefaultVar(this, Check.NONE, SleighProgramCompiler.NIL_SYMBOL_NAME, + DefaultDataType.dataType); + } + + /** + * Add another data type manager as a possible source of data types + * + * @see #type(String) + * @param source the additional data type manager + */ + protected void addDataTypeSource(DataTypeManager source) { + dtSources.add(new DataTypeParser(source, dtm, null, AllowedDataTypes.ALL)); + } + + /** + * Add several data type managers as source of data types + * + * @see #type(String) + * @param sources the additional managers + */ + protected void addDataTypeSources(Collection sources) { + for (DataTypeManager src : sources) { + addDataTypeSource(src); + } + } + + /** + * Import a variable defined by the processor language + * + * @param name the name of the variable. The name must already be defined by the processor + * @param type the type of the variable + * @return a handle to the variable + */ + protected Var lang(String name, DataType type) { + LangVar lang = new LangVar(this, name, type); + return lang; + } + + /** + * Import a register variable + * + * @param register the register + * @param type the type of the variable + * @return a handle to the variable + */ + protected Var reg(Register register, DataType type) { + return lang(register.getName(), type); + } + + /** + * Internal use only: Create a handle to a parameter + * + * @param name the name of the parameter + * @param type the type of the parameter + * @return a handle to the variable + */ + private Var param(String name, DataType type) { + return new LocalVar(this, name, type); + } + + /** + * Declare a local variable with the given name and type + * + *

+ * If the variable has no definitive type, but has a known size, use e.g., + * {@link Undefined8DataType} or {@link #type(String)} with "{@code /undefined8}". If the + * variable's size depends on the ABI, use the most appropriate integer or pointer type, e.g., + * "{@code /void*}". + * + * @param name the name of the variable. The name cannot already be defined by the processor + * @param type the type of the variable + * @return a handle to the variable + */ + protected Var local(String name, DataType type) { + LocalVar local = new LocalVar(this, name, type); + new DeclStmt(this, local); + return local; + } + + /** + * Declare a local variable with the given name and initial value + * + *

+ * The type is taken from that of the initial value. + * + * @param name the name of the variable. The name cannot already be defined by the processor + * @param init the initial value (and type) + * @return a handle to the variable + */ + protected Var local(String name, RVal init) { + Var temp = new LocalVar(this, name, init.getType()); + new AssignStmt(this, temp, init); + return temp; + } + + /** + * Allocate a temporary local variable of the given type + * + * @param type the type + * @return a handle to the variable + */ + protected Var temp(DataType type) { + return local("__temp" + (nextTemp++), type); + } + + /** + * Get a type from a bound data type manager by path + * + * @param path the full path to the data type, including leading "/" + * @return the data type + * @throws StructuredSleighError if the type cannot be found + */ + protected DataType type(String path) { + for (DataTypeParser source : dtSources) { + DataType type; + try { + type = source.parse(path); + } + catch (InvalidDataTypeException e) { + continue; + } + catch (CancelledException e) { + throw new AssertionError(e); + } + if (type != null) { + return type; + } + } + throw new StructuredSleighError("No such type: " + path); + } + + /** + * Get several types + * + * @see #type(String) + * @param paths the data types paths + * @return the data types in the same order + * @throws StructuredSleighError if any type cannot be found + */ + protected List types(String... paths) { + return Stream.of(paths).map(this::type).collect(Collectors.toList()); + } + + /** + * Declare an external userop + * + * @param returnType the userop's "return type" + * @param name the name of the userop as it would appear in Sleigh code + * @param parameterTypes the types of its parameters, in order + * @return the declaration, suitable for generating invocations + */ + protected UseropDecl userop(DataType returnType, String name, List parameterTypes) { + return new DefaultUseropDecl(this, returnType, name, parameterTypes); + } + + /** + * Generate a literal (or immediate or constant) value + * + *

+ * WARNING: Passing a literal int that turns out to be negative (easy to do in hex + * notation) can be perilous. For example, 0xdeadbeef will actually result in 0xffffffffdeadbeef + * because Java will cast it to a long before it's passed into this method. Use 0xdeadbeefL + * instead. + * + * @param val the value + * @param size the size of the value in bytes + * @return a handle to the value + */ + protected RVal lit(long val, int size) { + return new LiteralLongExpr(this, val, size); + } + + /** + * Generate a literal (or immediate or constant) single-precision floating-point value + * + * @param val the value + * @return a handle to the value + */ + protected RVal litf(float val) { + return litf(val, FloatDataType.dataType); + } + + /** + * Generate a literal (or immediate or constant) double-precision floating-point value + * + * @param val + * @return a handle to the value + */ + protected RVal litd(double val) { + return litf(val, DoubleDataType.dataType); + } + + /** + * Generate a literal (or immediate or constant) floating-point value + * + * @param val + * @param type the type of the value + * @return a handle to the value + */ + protected RVal litf(double val, DataType type) { + return new LiteralFloatExpr(this, val, type); + } + + /** + * Generate Sleigh code + * + *

+ * This is similar in concept to inline assembly. It allows the embedding of Sleigh code into + * Structured Sleigh that is otherwise impossible or inconvenient to state. No effort is made to + * ensure the correctness of the given Sleigh code nor its impact in context. + * + * @param rawStmt the Sleigh code + * @return a handle to the statement + */ + public Stmt s(String rawStmt) { + return new RawStmt(this, rawStmt); + } + + /** + * Generate a Sleigh expression + * + *

+ * This is similar in concept to inline assembly, except it also has a value. It allows the + * embedding of Sleigh code into Structured Sleigh that is otherwise impossible or inconvenient + * to express. No effort is made to ensure the correctness of the given Sleigh expression nor + * its impact in context. The result is assigned a type of "void". + * + * @param rawExpr the Sleigh expression + * @return a handle to the value + */ + public Expr e(String rawExpr) { + return new RawExpr(this, rawExpr); + } + + /** + * The wrapper around an {@link StructuredSleigh#_if(RVal, Runnable)} statement providing + * additional DSL syntax + */ + public class WrapIf { + private final IfStmt _if; + + protected WrapIf(IfStmt _if) { + this._if = _if; + } + + /** + * Generate an "else" clause for the wrapped "if" statement + * + * @param body the body of the clause + */ + public void _else(Runnable body) { + _if.addElse(new BlockStmt(StructuredSleigh.this, body)); + } + + /** + * Generate an "else if" clause for the wrapped "if" statement + * + *

+ * This is shorthand for {@code _else(_if(...))} but avoids the unnecessary nesting of + * parentheses. + * + * @param cond the condition + * @param body the body of the clause + * @return a wrapper to the second "if" statement + */ + public WrapIf _elif(Expr cond, Runnable body) { + IfStmt _elif = doIf(cond, body); + _if.addElse(_elif); + return new WrapIf(_elif); + } + } + + private IfStmt doIf(RVal cond, Runnable body) { + return new IfStmt(this, cond, new BlockStmt(this, body)); + } + + /** + * Generate an "if" statement + * + *

+ * The body is usually a lambda containing additional statements, predicated on this statement's + * condition, so that it resembles Java / C syntax: + * + *

+	 * _if(r0.eq(4), () -> {
+	 * 	r1.set(1);
+	 * });
+	 * 
+ * + *

+ * The returned "wrapper" provides for additional follow-on syntax, e.g.: + * + *

+	 * _if(r0.eq(4), () -> {
+	 * 	r1.set(1);
+	 * })._elif(r0.eq(5), () -> {
+	 * 	r1.set(3);
+	 * })._else(() -> {
+	 * 	r1.set(r0.muli(2));
+	 * });
+	 * 
+ * + * @param cond the condition + * @param body the body of the statement + * @return a wrapper to the generated "if" statement + */ + protected WrapIf _if(RVal cond, Runnable body) { + return new WrapIf(doIf(cond, body)); + } + + /** + * Generate a "while" statement + * + *

+ * The body is usually a lambda containing the controlled statements, so that it resembles Java + * / C syntax: + * + *

+	 * Var temp = local("temp", "/int");
+	 * _while(temp.ltiu(10), () -> {
+	 * 	temp.inc();
+	 * });
+	 * 
+ * + * @param cond the condition + * @param body the body of the loop + */ + protected void _while(RVal cond, Runnable body) { + new WhileStmt(this, cond, new BlockStmt(this, body)); + } + + /** + * Generate a "for" statement + * + *

+ * The body is usually a lambda containing the controlled statements, so that it resembles Java + * / C syntax: + * + *

+	 * Var temp = local("temp", "/int");
+	 * Var total = local("total", "/int");
+	 * total.set(0);
+	 * _for(temp.set(0), temp.ltiu(10), temp.inc(1), () -> {
+	 * 	total.addiTo(temp);
+	 * });
+	 * 
+ * + *

+ * TIP: If the number of repetitions is known at generation time, consider using a standard Java + * for loop, as a sort of Structured Sleigh macro. For example, to broadcast element 0 to an + * in-memory 16-long vector pointed to by r0: + * + *

+	 * Var arr = lang("r0", "/int *");
+	 * for (int i = 1; i < 16; i++) {
+	 * 	arr.index(i).deref().set(arr.index(0).deref());
+	 * }
+	 * 
+ * + *

+ * Instead of generating a loop, this will generate 15 Sleigh statements. + * + * @param init the loop initializer + * @param cond the loop condition + * @param step the loop stepper + * @param body the body of the loop + */ + protected void _for(Stmt init, RVal cond, Stmt step, Runnable body) { + new ForStmt(this, init, cond, step, new BlockStmt(this, body)); + } + + /** + * Generate a "break" statement + * + *

+ * This must appear in the body of a loop statement. It binds to the innermost loop statement in + * which it appears, generating code to leave that loop. + */ + protected void _break() { + new BreakStmt(this); + } + + /** + * Generate a "continue" statement + * + *

+ * This must appear in the body of a loop statement. It binds to the innermost loop statement in + * which it appears, generating code to immediately repeat the loop, skipping the remainder of + * its body. + */ + protected void _continue() { + new ContinueStmt(this); + } + + /** + * Generate a "result" statement + * + *

+ * This is semantically similar to a C "return" statement, but is named differently to avoid + * confusion with Sleigh's return statement. When this is code implementing a p-code userop, + * this immediately exits the userop, returning control to the caller where the invocation takes + * the value given in this statement. + * + *

+ * Contrast with {@link #_return(RVal)} + * + * @param result the resulting value of the userop + */ + protected void _result(RVal result) { + new ResultStmt(this, result); + } + + /** + * Generate a "return" statement + * + *

+ * This models (in part) a C-style return from the current target function to its callee. It + * simply generates the "return" Sleigh statement, which is an indirect branch to the given + * target. The target must be at an address in the processor's code space. + * + *

+ * Contrast with {@link #_result(RVal)} + */ + protected void _return(RVal target) { + new ReturnStmt(this, target); + } + + /** + * Get the method lookup for this context + * + *

+ * If the annotated methods cannot be accessed by {@link StructuredSleigh}, this method must be + * overridden. It should simply return {@link MethodHandles#lookup()}. This is necessary when + * the author chooses access modifiers other than {@code public}, which is good practice, or + * when the class is an anonymous inner class, as is often the case with stand-alone use. + * + * @return the lookup + */ + protected Lookup getMethodLookup() { + return MethodHandles.lookup(); + } + + private SleighPcodeUseropDefinition compile(StructuredUserop annot, Lookup lookup, + Method method) { + if (annot == null) { + throw new IllegalArgumentException("Method " + method + " is missing @" + + StructuredUserop.class.getSimpleName() + " annotation."); + } + if (method.getReturnType() != void.class) { + throw new IllegalArgumentException("Method " + method + " having @" + + StructuredUserop.class.getSimpleName() + " annotation must return void."); + } + MethodHandle handle; + try { + handle = lookup.unreflect(method).bindTo(this); + } + catch (IllegalAccessException e) { + throw new IllegalArgumentException("Cannot access " + method + " having @" + + StructuredUserop.class.getSimpleName() + " annotation. Override getMethodLookup()"); + } + Builder builder = factory.define(method.getName()); + + DataType retType = type(annot.type()); + + Parameter[] params = method.getParameters(); + @SuppressWarnings("unchecked") + List> paramsAndTypes = Arrays.asList(new Entry[params.length]); + for (int i = 0; i < params.length; i++) { + Parameter p = params[i]; + if (p.getType() != Var.class) { + throw new IllegalArgumentException( + "Parameter " + p + " of method " + method + " must have type Var."); + } + Param pAnnot = p.getAnnotation(Param.class); + if (pAnnot == null) { + throw new StructuredSleighError("No @" + Param.class.getSimpleName() + + " annotation of parameter " + p + " of method " + method + "."); + } + String name = "".equals(pAnnot.name()) ? p.getName() : pAnnot.name(); + DataType type = type(pAnnot.type()); + paramsAndTypes.set(i, Map.entry(name, type)); + } + builder.params(paramsAndTypes.stream().map(p -> p.getKey()).collect(Collectors.toList())); + + assert stack.isEmpty(); + root = new RoutineStmt(this, method.getName(), retType, () -> { + List args = paramsAndTypes.stream() + .map(p -> param(p.getKey(), p.getValue())) + .collect(Collectors.toList()); + try { + handle.invokeWithArguments(args); + } + catch (StructuredSleighError e) { + throw e; + } + catch (Throwable e) { + throw new StructuredSleighError( + "Exception processing structured sleigh body", + e); + } + }); + String source = root.generate(FALL, FALL); + builder.sleigh(source); + return builder.build(); + } + + /** + * Generate all the exported userops and place them into the given map + * + * @param the type of values used by the userops. For sleigh, this can be anything. + * @param into the destination map, usually belonging to a {@link PcodeUseropLibrary}. + */ + public void generate(Map> into) { + Lookup lookup = getMethodLookup(); + Class cls = this.getClass(); + Set methods = + CACHE_BY_CLASS.computeIfAbsent(cls, __ -> collectDefinitions(cls)); + for (Method m : methods) { + into.put(m.getName(), doGenerate(lookup, m)); + } + } + + /** + * Generate the userop for a given Java method + * + * @param the type of values used by the userop. For sleigh, this can be anything. + * @param m the method exported as a userop + * @return the userop + */ + public SleighPcodeUseropDefinition generate(Method m) { + return doGenerate(getMethodLookup(), m); + } + + protected SleighPcodeUseropDefinition doGenerate(Lookup lookup, Method m) { + return compile(m.getAnnotation(StructuredUserop.class), lookup, m); + } + + /** + * Generate all the exported userops and return them in a map + * + *

+ * This is typically only used when not part of a larger {@link PcodeUseropLibrary}, for example + * to aid in developing a Sleigh module or for generating injects. + * + * @param the type of values used by the userop. For sleigh, this can be anything. + * @return the userop + */ + public Map> generate() { + Map> ops = new HashMap<>(); + generate(ops); + return ops; + } + + /** + * Validate and compute the size (in bytes) of a floating-point data type + * + * @param type the type + * @return the size of the type + */ + protected int computeFloatSize(DataType type) { + if (!(type instanceof AbstractFloatDataType)) { + throw new StructuredSleighError("Must be a floating-point type. Got " + type); + } + return type.getLength(); + } + + /** + * Encode a floating-point value + * + * @param val the value + * @param size the size (in bytes) + * @return the encoded bits + */ + protected long encodeFloat(double val, int size) { + return FloatFormatFactory.getFloatFormat(size).getEncoding(val); + } + + /** + * Extension point: Specify whether values of a given type can be assigned to variables of + * another type + * + *

+ * The default is to check if the types are equivalent: {@link DataType#isEquivalent(DataType)}. + * + * @param varType the variable's data type (assign to) + * @param valType the value's data type (assign from) + * @return true if allowed, false otherwise + */ + protected boolean isAssignable(DataType varType, DataType valType) { + // TODO: isEquivalent is not quite it + return varType.isEquivalent(valType); + } + + /** + * Extension point: Specify how to handle a type mismatch in an assignment + * + *

+ * The default is to log a warning and continue. + * + * @param lhs the variable being assigned + * @param rhs the value being assigned to the variable + */ + protected void emitAssignmentTypeMismatch(LVal lhs, RVal rhs) { + Msg.warn(this, "Type mismatch in assignment: " + lhs + " = " + rhs); + } + + /** + * Extension point: Specify how to handle a parameter to argument count mismatch + * + *

+ * The default is to throw an unrecoverable error. If allowed to continue, the matched + * parameters are type checked and the invocation generated as specified. Most likely, the + * emulator will crash while executing the invoked userop. + * + * @param userop the userop being called + * @param arguments the arguments being passed + */ + protected void emitParameterCountMismatch(UseropDecl userop, List arguments) { + throw new StructuredSleighError( + "Parameter/argument count mismatch invoking " + userop.getName() + ": Expected " + + userop.getParameterTypes().size() + " but got " + arguments.size()); + } + + /** + * Extension point: Specify how to handle a parameter type mismatch + * + *

+ * The default is to log a warning and continue. + * + * @param userop the userop being called + * @param position the position of the parameter + * @param value the value being assigned + */ + protected void emitParameterTypeMismatch(UseropDecl userop, int position, RVal value) { + Msg.warn(this, "Type mismatch for parameter " + position + " of " + userop.getName() + + ": " + value + " is not a " + userop.getParameterTypes().get(position)); + } + + /** + * Extension point: Specify how to handle a result type mismatch + * + *

+ * The default is to log a warning and continue. + * + * @param routine the routine (userop) containing the result statement + * @param result the result value specified in the statement + */ + protected void emitResultTypeMismatch(RoutineStmt routine, RVal result) { + Msg.warn(this, "Type mismatch on result of " + routine.name + ": " + result + " is not a " + + routine.retType); + } + + /** + * Compute the type of a dereferenced address + * + * @param addr the value of the pointer + * @return the resulting type + */ + protected DataType computeDerefType(RVal addr) { + DataType addrType = addr.getType(); + if (addrType instanceof Pointer) { + Pointer pointer = (Pointer) addrType; + return pointer.getDataType(); + } + emitDerefNonPointer(addr); + return VoidDataType.dataType; + } + + /** + * Extension point: Specify how to handle dereference of a non-pointer value + * + *

+ * The default is to log a warning and continue. If permitted to continue, the resulting type + * will be {@code void}, likely resulting in more issues. See + * {@link #computeDerefType(DataType)}. + * + * @param addr the value being dereferenced + */ + protected void emitDerefNonPointer(RVal addr) { + Msg.warn(this, "Dereference requires pointer type. Got " + addr); + } + + /** + * Compute the length (in bytes) of an element of a pointer to an array + * + * @param addr the value of the pointer + * @return the length of one element + */ + protected int computeElementLength(RVal addr) { + DataType pType = addr.getType(); + if (!(pType instanceof Pointer)) { + throw new StructuredSleighError("Index requires pointer type. Got " + addr); + } + DataType eType = ((Pointer) pType).getDataType(); + return eType.getLength(); + } + + /** + * Find the type component (field) of a pointer to a composite type + * + *

+ * In terms of type manipulation, this is equivalent the C expression {@code addr->field}. + * {@link LVal#field(String)} uses the component to derive the offset and the resulting pointer + * type. + * + * @param addr the value of the pointer + * @param name the field being accessed + * @return the found component + * + * @throws StructuredSleighError if the field cannot be found + */ + protected DataTypeComponent findComponent(RVal addr, String name) { + DataType aType = addr.getType(); + if (!(aType instanceof Pointer)) { + throw new StructuredSleighError( + "Offset requires pointer to composite type. Got " + addr); + } + DataType rType = ((Pointer) aType).getDataType(); + if (!(rType instanceof Composite)) { + throw new StructuredSleighError( + "Cannot access field of non-Composite pointer " + addr); + } + Composite composite = (Composite) rType; + DataTypeComponent dtc = findComponentByName(composite, name); + if (dtc == null) { + throw new StructuredSleighError("No such field '" + name + "' of " + addr); + } + if (dtc.isBitFieldComponent()) { + throw new StructuredSleighError( + "Bitfield types are not yet supported: '" + dtc + "' of " + addr); + } + return dtc; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/UnExpr.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/UnExpr.java new file mode 100644 index 0000000000..5973bc3cd9 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/UnExpr.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.struct; + +import ghidra.pcode.struct.StructuredSleigh.RVal; +import ghidra.program.model.data.DataType; + +class UnExpr extends Expr { + protected final String op; + protected final RValInternal u; + + protected UnExpr(StructuredSleigh ctx, String op, RVal u, DataType type) { + super(ctx, type); + this.op = op; + this.u = (RValInternal) u; + } + + @Override + public RVal cast(DataType type) { + return new UnExpr(ctx, op, u, type); + } + + @Override + public String toString() { + return "<" + getClass().getSimpleName() + " " + op + " " + u + ">"; + } + + @Override + public String generate() { + return "(" + op + u.generate() + ")"; + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/VoidExprStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/VoidExprStmt.java new file mode 100644 index 0000000000..9516b2eec0 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/VoidExprStmt.java @@ -0,0 +1,59 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.*; +import ghidra.program.model.data.DataType; + +class VoidExprStmt extends AbstractStmt implements RValInternal, StmtWithVal { + private final RValInternal expr; + private final DataType type; + + private VoidExprStmt(StructuredSleigh ctx, RVal expr, DataType type) { + super(ctx); + this.expr = (RValInternal) expr; + this.type = type; + } + + protected VoidExprStmt(StructuredSleigh ctx, RVal expr) { + this(ctx, expr, expr.getType()); + } + + @Override + public RVal cast(DataType type) { + return new VoidExprStmt(ctx, expr, type); + } + + @Override + public String toString() { + return ""; + } + + @Override + protected String generate(Label next, Label fall) { + return expr.generate() + ";\n" + next.genGoto(fall); + } + + @Override + public DataType getType() { + return type; + } + + @Override + public String generate() { + return ctx.nil.generate(); + } +} diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/WhileStmt.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/WhileStmt.java new file mode 100644 index 0000000000..44974d3451 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/pcode/struct/WhileStmt.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.pcode.struct; + +import ghidra.pcode.struct.StructuredSleigh.*; + +class WhileStmt extends LoopStmt { + protected WhileStmt(StructuredSleigh ctx, RVal cond, Stmt stmt) { + super(ctx, cond, stmt); + } + + @Override + protected String generate(Label next, Label fall) { + Label lTest = lContinue = ctx.new FreshLabel(); + Label lBegin = ctx.new FreshLabel(); + Label lExit = lBreak = next.freshOrBorrow(); + + String testGen = lExit.genGoto(cond.notb(), lBegin); + String stmtGen = stmt.generate(lTest, fall); + return lTest.genAnchor() + + testGen + + lBegin.genAnchor() + + stmtGen + + lExit.genAnchor(); + } +} diff --git a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/MemBufferAdapter.java b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/mem/MemBufferAdapter.java similarity index 98% rename from Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/MemBufferAdapter.java rename to Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/mem/MemBufferAdapter.java index 5b638dd414..724ceda515 100644 --- a/Ghidra/Debug/Framework-TraceModeling/src/main/java/ghidra/trace/util/MemBufferAdapter.java +++ b/Ghidra/Debug/ProposedUtils/src/main/java/ghidra/program/model/mem/MemBufferAdapter.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.trace.util; +package ghidra.program.model.mem; import java.math.BigInteger; import java.nio.ByteBuffer; diff --git a/Ghidra/Debug/ProposedUtils/src/main/java/utilities/util/AnnotationUtilities.java b/Ghidra/Debug/ProposedUtils/src/main/java/utilities/util/AnnotationUtilities.java new file mode 100644 index 0000000000..56daee83ba --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/main/java/utilities/util/AnnotationUtilities.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 utilities.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.HashSet; +import java.util.Set; + +/** + * Some utilities for reflection using annotations + */ +public enum AnnotationUtilities { + ; + + /** + * Collect from among the given class, its superclasses, and its interfaces all methods + * annotated with the given annotation type. + * + * @param annotCls the annotation type + * @param cls the class whose methods to examine + * @return the set of all methods having the given annotation type + */ + public static Set collectAnnotatedMethods(Class annotCls, + Class cls) { + Set defs = new HashSet<>(); + collectAnnotatedMethods(annotCls, cls, defs, new HashSet<>()); + return defs; + } + + private static void collectAnnotatedMethods(Class annotCls, Class cls, + Set defs, + Set> visited) { + if (!visited.add(cls)) { + return; + } + Class superCls = cls.getSuperclass(); + if (superCls != null) { + collectAnnotatedMethods(annotCls, superCls, defs, visited); + } + for (Class superIf : cls.getInterfaces()) { + collectAnnotatedMethods(annotCls, superIf, defs, visited); + } + collectAnnotatedMethodsForClass(annotCls, cls, defs); + } + + private static void collectAnnotatedMethodsForClass(Class annotCls, + Class cls, Set defs) { + for (Method method : cls.getDeclaredMethods()) { + Annotation annot = method.getAnnotation(annotCls); + if (annot == null) { + continue; + } + defs.add(method); + } + } +} 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 new file mode 100644 index 0000000000..179ce80ad9 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibraryTest.java @@ -0,0 +1,444 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.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 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.unix.*; +import ghidra.pcode.exec.*; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.data.PointerDataType; +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 { + protected final class LinuxAmd64PcodeEmulator extends PcodeEmulator { + protected EmuLinuxAmd64SyscallUseropLibrary syscalls; + + public LinuxAmd64PcodeEmulator() { + super((SleighLanguage) program.getLanguage()); + } + + @Override + protected PcodeUseropLibrary createUseropLibrary() { + syscalls = new EmuLinuxAmd64SyscallUseropLibrary<>(this, fs, program); + return syscalls; + } + } + + /** + * 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; + + 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; + Assembler asm; + + Register regRIP; + Register regRAX; + AddressSpace space; + Address start; + int size; + MemoryBlock block; + 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(); + arithmetic = BytesPcodeArithmetic.forLanguage(language); + + regRIP = program.getRegister("RIP"); + regRAX = program.getRegister("RAX"); + space = program.getAddressFactory().getDefaultAddressSpace(); + start = space.getAddress(0x00400000); + size = 0x1000; + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + block = program.getMemory() + .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"); + } + + fs = new BytesEmuUnixFileSystem(); + + // I don't like waiting on this, just to fail during setup. Put it last. + asm = Assemblers.getAssembler(program); + } + + @After + public void tearDown() { + if (program != null) { + program.release(this); + } + } + + public LinuxAmd64PcodeEmulator prepareEmulator() throws Exception { + LinuxAmd64PcodeEmulator emu = new LinuxAmd64PcodeEmulator(); + // 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)); + emu.getSharedState().setVar(space, start.getOffset(), size, true, buf); + return emu; + } + + public PcodeThread launchThread(LinuxAmd64PcodeEmulator emu, Address pc) { + PcodeThread thread = emu.newThread(); + thread.overrideCounter(start); + thread.overrideContextWithDefault(); + thread.reInitialize(); + return thread; + } + + public void stepGroupExit(PcodeThread thread) { + // Step up to the group_exit + thread.stepInstruction(2); + // Then verify the syscall interrupts execution + try { + thread.stepInstruction(); + fail(); + } + catch (EmuProcessExitedException e) { + // pass + } + } + + public void execute(PcodeThread thread) { + try { + thread.stepInstruction(1000); + fail(); + } + catch (EmuProcessExitedException e) { + } + } + + @Test + public void testWriteStdout() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV RAX," + SYSCALLNO_WRITE, + "MOV RDI," + EmuUnixFileDescriptor.FD_STDOUT, + "LEA RSI,[0x00400800]", + "MOV RDX," + BYTES_HW.length, + "SYSCALL", + "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RDI,0", + "SYSCALL"); + block.putBytes(space.getAddress(0x00400800), BYTES_HW); + } + + LinuxAmd64PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + // Capture stdout into a byte array + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + emu.syscalls.putDescriptor(EmuUnixFileDescriptor.FD_STDOUT, + new IOStreamEmuUnixFileHandle(emu, program.getCompilerSpec(), null, stdout)); + + // Step through write and verify return value and actual output effect + thread.stepInstruction(5); + assertArrayEquals(arithmetic.fromConst(BYTES_HW.length, regRAX.getNumBytes()), + thread.getState().getVar(regRAX)); + assertArrayEquals(BYTES_HW, stdout.toByteArray()); + + stepGroupExit(thread); + } + + @Test + public void testReadStdin() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV RAX," + SYSCALLNO_READ, + "MOV RDI," + EmuUnixFileDescriptor.FD_STDIN, + "LEA RSI,[0x00400800]", + "MOV RDX," + BYTES_HW.length, + "SYSCALL", + "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RDI,0", + "SYSCALL"); + } + + LinuxAmd64PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + // Provide stdin via a byte array + ByteArrayInputStream stdin = new ByteArrayInputStream(BYTES_HW); + emu.syscalls.putDescriptor(EmuUnixFileDescriptor.FD_STDIN, + new IOStreamEmuUnixFileHandle(emu, program.getCompilerSpec(), stdin, null)); + + // Step through write and verify return value and actual output effect + thread.stepInstruction(5); + assertArrayEquals(arithmetic.fromConst(BYTES_HW.length, regRAX.getNumBytes()), + thread.getState().getVar(regRAX)); + assertArrayEquals(BYTES_HW, + emu.getSharedState().getVar(space, 0x00400800, BYTES_HW.length, true)); + + stepGroupExit(thread); + } + + @Test + public void testWritevStdout() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + Address data = space.getAddress(0x00400800); + ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN); + + Address strHello = data.add(buf.position()); + buf.put(BYTES_HELLO); + Address endHello = data.add(buf.position()); + Address iov = data.add(buf.position()); + buf.putLong(strHello.getOffset()); + buf.putLong(endHello.subtract(strHello)); + int posIov1base = buf.position(); + buf.putLong(0); + int posIov1len = buf.position(); + buf.putLong(0); + Address strWorld = data.add(buf.position()); + buf.put(BYTES_WORLD); + Address endWorld = data.add(buf.position()); + // Backpatch + buf.putLong(posIov1base, strWorld.getOffset()); + buf.putLong(posIov1len, endWorld.subtract(strWorld)); + + asm.assemble(start, + "MOV RAX," + SYSCALLNO_WRITEV, + "MOV RDI," + EmuUnixFileDescriptor.FD_STDOUT, + "LEA RSI,[0x" + iov + "]", + "MOV RDX,2", + "SYSCALL", + "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RDI,0", + "SYSCALL"); + block.putBytes(data, buf.array()); + } + + LinuxAmd64PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + // Capture stdout into a byte array + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + emu.syscalls.putDescriptor(EmuUnixFileDescriptor.FD_STDOUT, + new IOStreamEmuUnixFileHandle(emu, program.getCompilerSpec(), null, stdout)); + + // 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))); + assertArrayEquals(BYTES_HW, stdout.toByteArray()); + + stepGroupExit(thread); + } + + @Test + public void testReadvStdin() throws Exception { + Address strHello; + Address strWorld; + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + Address data = space.getAddress(0x00400800); + ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN); + + strHello = data.add(buf.position()); + buf.put(new byte[BYTES_HELLO.length]); + Address endHello = data.add(buf.position()); + Address iov = data.add(buf.position()); + buf.putLong(strHello.getOffset()); + buf.putLong(endHello.subtract(strHello)); + int posIov1base = buf.position(); + buf.putLong(0); + int posIov1len = buf.position(); + buf.putLong(0); + strWorld = data.add(buf.position()); + buf.put(new byte[BYTES_WORLD.length]); + Address endWorld = data.add(buf.position()); + // Backpatch + buf.putLong(posIov1base, strWorld.getOffset()); + buf.putLong(posIov1len, endWorld.subtract(strWorld)); + + asm.assemble(start, + "MOV RAX," + SYSCALLNO_READV, + "MOV RDI," + EmuUnixFileDescriptor.FD_STDIN, + "LEA RSI,[0x" + iov + "]", + "MOV RDX,2", + "SYSCALL", + "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RDI,0", + "SYSCALL"); + block.putBytes(data, buf.array()); + } + + LinuxAmd64PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + // Provide stdin via a byte array + ByteArrayInputStream stdin = new ByteArrayInputStream(BYTES_HW); + emu.syscalls.putDescriptor(EmuUnixFileDescriptor.FD_STDIN, + new IOStreamEmuUnixFileHandle(emu, program.getCompilerSpec(), stdin, null)); + + // 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))); + assertArrayEquals(BYTES_HELLO, + emu.getSharedState().getVar(space, strHello.getOffset(), BYTES_HELLO.length, true)); + assertArrayEquals(BYTES_WORLD, + emu.getSharedState().getVar(space, strWorld.getOffset(), BYTES_WORLD.length, true)); + + stepGroupExit(thread); + } + + @Test + public void testOpenWriteClose() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV RAX," + SYSCALLNO_OPEN, + "LEA RDI,[0x00400880]", + "MOV RSI," + (AbstractEmuLinuxSyscallUseropLibrary.O_WRONLY | + AbstractEmuLinuxSyscallUseropLibrary.O_CREAT), + "MOV RDX," + (0600), + "SYSCALL", + "MOV RBP, RAX", + + "MOV RAX," + SYSCALLNO_WRITE, + "MOV RDI,RBP", + "LEA RSI,[0x00400800]", + "MOV RDX," + BYTES_HW.length, + "SYSCALL", + + "MOV RAX," + SYSCALLNO_CLOSE, + "MOV RDI,RBP", + + "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RDI,0", + "SYSCALL"); + block.putBytes(space.getAddress(0x00400800), BYTES_HW); + block.putBytes(space.getAddress(0x00400880), "myfile\0".getBytes()); + } + + LinuxAmd64PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + execute(thread); + + EmuUnixFile file = fs.getFile("myfile"); + byte[] bytes = new byte[BYTES_HW.length]; + file.read(arithmetic, arithmetic.fromConst(0, 8), bytes); + assertArrayEquals(BYTES_HW, bytes); + } + + @Test + public void testOpenReadClose() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV RAX," + SYSCALLNO_OPEN, + "LEA RDI,[0x00400880]", + "MOV RSI," + (AbstractEmuLinuxSyscallUseropLibrary.O_RDONLY), + "MOV RDX," + (0600), + "SYSCALL", + "MOV RBP, RAX", + + "MOV RAX," + SYSCALLNO_READ, + "MOV RDI,RBP", + "LEA RSI,[0x00400800]", + "MOV RDX," + BYTES_HW.length, + "SYSCALL", + + "MOV RAX," + SYSCALLNO_CLOSE, + "MOV RDI,RBP", + + "MOV RAX," + SYSCALLNO_GROUP_EXIT, + "MOV RDI,0", + "SYSCALL"); + block.putBytes(space.getAddress(0x00400880), "myfile\0".getBytes()); + } + + EmuUnixFile file = fs.createOrGetFile("myfile", 0600); + file.write(arithmetic, arithmetic.fromConst(0, 8), BYTES_HW); + + LinuxAmd64PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + execute(thread); + + assertArrayEquals(BYTES_HW, + emu.getSharedState().getVar(space, 0x00400800, BYTES_HW.length, true)); + } +} 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 new file mode 100644 index 0000000000..9eecda38e6 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibraryTest.java @@ -0,0 +1,444 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.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 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.unix.*; +import ghidra.pcode.exec.*; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.data.PointerDataType; +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 { + protected final class LinuxX86PcodeEmulator extends PcodeEmulator { + protected EmuLinuxX86SyscallUseropLibrary syscalls; + + public LinuxX86PcodeEmulator() { + super((SleighLanguage) program.getLanguage()); + } + + @Override + protected PcodeUseropLibrary createUseropLibrary() { + syscalls = new EmuLinuxX86SyscallUseropLibrary<>(this, fs, program); + return syscalls; + } + } + + /** + * 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; + + 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; + Assembler asm; + + Register regEIP; + Register regEAX; + AddressSpace space; + Address start; + int size; + MemoryBlock block; + 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); + language = (SleighLanguage) program.getLanguage(); + arithmetic = BytesPcodeArithmetic.forLanguage(language); + + regEIP = program.getRegister("EIP"); + regEAX = program.getRegister("EAX"); + space = program.getAddressFactory().getDefaultAddressSpace(); + start = space.getAddress(0x00400000); + size = 0x1000; + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + block = program.getMemory() + .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"); + } + + fs = new BytesEmuUnixFileSystem(); + + // I don't like waiting on this, just to fail during setup. Put it last. + asm = Assemblers.getAssembler(program); + } + + @After + public void tearDown() { + if (program != null) { + program.release(this); + } + } + + public LinuxX86PcodeEmulator prepareEmulator() throws Exception { + LinuxX86PcodeEmulator emu = new LinuxX86PcodeEmulator(); + // 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)); + emu.getSharedState().setVar(space, start.getOffset(), size, true, buf); + return emu; + } + + public PcodeThread launchThread(LinuxX86PcodeEmulator emu, Address pc) { + PcodeThread thread = emu.newThread(); + thread.overrideCounter(start); + thread.overrideContextWithDefault(); + thread.reInitialize(); + return thread; + } + + public void stepGroupExit(PcodeThread thread) { + // Step up to the group_exit + thread.stepInstruction(2); + // Then verify the syscall interrupts execution + try { + thread.stepInstruction(); + fail(); + } + catch (EmuProcessExitedException e) { + // pass + } + } + + public void execute(PcodeThread thread) { + try { + thread.stepInstruction(1000); + fail(); + } + catch (EmuProcessExitedException e) { + } + } + + @Test + public void testWriteStdout() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV EAX," + SYSCALLNO_WRITE, + "MOV EBX," + EmuUnixFileDescriptor.FD_STDOUT, + "LEA ECX,[0x00400800]", + "MOV EDX," + BYTES_HW.length, + "INT 0x80", + "MOV EAX," + SYSCALLNO_EXIT, + "MOV EBX,0", + "INT 0x80"); + block.putBytes(space.getAddress(0x00400800), BYTES_HW); + } + + LinuxX86PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + // Capture stdout into a byte array + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + emu.syscalls.putDescriptor(EmuUnixFileDescriptor.FD_STDOUT, + new IOStreamEmuUnixFileHandle(emu, program.getCompilerSpec(), null, stdout)); + + // Step through write and verify return value and actual output effect + thread.stepInstruction(5); + assertArrayEquals(arithmetic.fromConst(BYTES_HW.length, regEAX.getNumBytes()), + thread.getState().getVar(regEAX)); + assertArrayEquals(BYTES_HW, stdout.toByteArray()); + + stepGroupExit(thread); + } + + @Test + public void testReadStdin() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV EAX," + SYSCALLNO_READ, + "MOV EBX," + EmuUnixFileDescriptor.FD_STDIN, + "LEA ECX,[0x00400800]", + "MOV EDX," + BYTES_HW.length, + "INT 0x80", + "MOV EAX," + SYSCALLNO_EXIT, + "MOV EBX,0", + "INT 0x80"); + } + + LinuxX86PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + // Provide stdin via a byte array + ByteArrayInputStream stdin = new ByteArrayInputStream(BYTES_HW); + emu.syscalls.putDescriptor(EmuUnixFileDescriptor.FD_STDIN, + new IOStreamEmuUnixFileHandle(emu, program.getCompilerSpec(), stdin, null)); + + // Step through write and verify return value and actual output effect + thread.stepInstruction(5); + assertArrayEquals(arithmetic.fromConst(BYTES_HW.length, regEAX.getNumBytes()), + thread.getState().getVar(regEAX)); + assertArrayEquals(BYTES_HW, + emu.getSharedState().getVar(space, 0x00400800, BYTES_HW.length, true)); + + stepGroupExit(thread); + } + + @Test + public void testWritevStdout() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + Address data = space.getAddress(0x00400800); + ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN); + + Address strHello = data.add(buf.position()); + buf.put(BYTES_HELLO); + Address endHello = data.add(buf.position()); + Address iov = data.add(buf.position()); + buf.putInt((int) strHello.getOffset()); + buf.putInt((int) endHello.subtract(strHello)); + int posIov1base = buf.position(); + buf.putInt(0); + int posIov1len = buf.position(); + buf.putInt(0); + Address strWorld = data.add(buf.position()); + buf.put(BYTES_WORLD); + Address endWorld = data.add(buf.position()); + // Backpatch + buf.putInt(posIov1base, (int) strWorld.getOffset()); + buf.putInt(posIov1len, (int) endWorld.subtract(strWorld)); + + asm.assemble(start, + "MOV EAX," + SYSCALLNO_WRITEV, + "MOV EBX," + EmuUnixFileDescriptor.FD_STDOUT, + "LEA ECX,[0x" + iov + "]", + "MOV EDX,2", + "INT 0x80", + "MOV EAX," + SYSCALLNO_EXIT, + "MOV EBX,0", + "INT 0x80"); + block.putBytes(data, buf.array()); + } + + LinuxX86PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + // Capture stdout into a byte array + ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + emu.syscalls.putDescriptor(EmuUnixFileDescriptor.FD_STDOUT, + new IOStreamEmuUnixFileHandle(emu, program.getCompilerSpec(), null, stdout)); + + // 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))); + assertArrayEquals(BYTES_HW, stdout.toByteArray()); + + stepGroupExit(thread); + } + + @Test + public void testReadvStdin() throws Exception { + Address strHello; + Address strWorld; + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + Address data = space.getAddress(0x00400800); + ByteBuffer buf = ByteBuffer.allocate(64).order(ByteOrder.LITTLE_ENDIAN); + + strHello = data.add(buf.position()); + buf.put(new byte[BYTES_HELLO.length]); + Address endHello = data.add(buf.position()); + Address iov = data.add(buf.position()); + buf.putInt((int) strHello.getOffset()); + buf.putInt((int) endHello.subtract(strHello)); + int posIov1base = buf.position(); + buf.putInt(0); + int posIov1len = buf.position(); + buf.putInt(0); + strWorld = data.add(buf.position()); + buf.put(new byte[BYTES_WORLD.length]); + Address endWorld = data.add(buf.position()); + // Backpatch + buf.putInt(posIov1base, (int) strWorld.getOffset()); + buf.putInt(posIov1len, (int) endWorld.subtract(strWorld)); + + asm.assemble(start, + "MOV EAX," + SYSCALLNO_READV, + "MOV EBX," + EmuUnixFileDescriptor.FD_STDIN, + "LEA ECX,[0x" + iov + "]", + "MOV EDX,2", + "INT 0x80", + "MOV EAX," + SYSCALLNO_EXIT, + "MOV EBX,0", + "INT 0x80"); + block.putBytes(data, buf.array()); + } + + LinuxX86PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + // Provide stdin via a byte array + ByteArrayInputStream stdin = new ByteArrayInputStream(BYTES_HW); + emu.syscalls.putDescriptor(EmuUnixFileDescriptor.FD_STDIN, + new IOStreamEmuUnixFileHandle(emu, program.getCompilerSpec(), stdin, null)); + + // 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))); + assertArrayEquals(BYTES_HELLO, + emu.getSharedState().getVar(space, strHello.getOffset(), BYTES_HELLO.length, true)); + assertArrayEquals(BYTES_WORLD, + emu.getSharedState().getVar(space, strWorld.getOffset(), BYTES_WORLD.length, true)); + + stepGroupExit(thread); + } + + @Test + public void testOpenWriteClose() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV EAX," + SYSCALLNO_OPEN, + "LEA EBX,[0x00400880]", + "MOV ECX," + (AbstractEmuLinuxSyscallUseropLibrary.O_WRONLY | + AbstractEmuLinuxSyscallUseropLibrary.O_CREAT), + "MOV EDX," + (0600), + "INT 0x80", + "MOV EBP, EAX", + + "MOV EAX," + SYSCALLNO_WRITE, + "MOV EBX,EBP", + "LEA ECX,[0x00400800]", + "MOV EDX," + BYTES_HW.length, + "INT 0x80", + + "MOV EAX," + SYSCALLNO_CLOSE, + "MOV EBX,EBP", + + "MOV EAX," + SYSCALLNO_EXIT, + "MOV EBX,0", + "INT 0x80"); + block.putBytes(space.getAddress(0x00400800), BYTES_HW); + block.putBytes(space.getAddress(0x00400880), "myfile\0".getBytes()); + } + + LinuxX86PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + execute(thread); + + EmuUnixFile file = fs.getFile("myfile"); + byte[] bytes = new byte[BYTES_HW.length]; + file.read(arithmetic, arithmetic.fromConst(0, 8), bytes); + assertArrayEquals(BYTES_HW, bytes); + } + + @Test + public void testOpenReadClose() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV EAX," + SYSCALLNO_OPEN, + "LEA EBX,[0x00400880]", + "MOV ECX," + (AbstractEmuLinuxSyscallUseropLibrary.O_RDONLY), + "MOV EDX," + (0600), + "INT 0x80", + "MOV EBP, EAX", + + "MOV EAX," + SYSCALLNO_READ, + "MOV EBX,EBP", + "LEA ECX,[0x00400800]", + "MOV EDX," + BYTES_HW.length, + "INT 0x80", + + "MOV EAX," + SYSCALLNO_CLOSE, + "MOV EBX,EBP", + + "MOV EAX," + SYSCALLNO_EXIT, + "MOV EBX,0", + "INT 0x80"); + block.putBytes(space.getAddress(0x00400880), "myfile\0".getBytes()); + } + + EmuUnixFile file = fs.createOrGetFile("myfile", 0600); + file.write(arithmetic, arithmetic.fromConst(0, 8), BYTES_HW); + + LinuxX86PcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + execute(thread); + + assertArrayEquals(BYTES_HW, + emu.getSharedState().getVar(space, 0x00400800, BYTES_HW.length, true)); + } +} 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 new file mode 100644 index 0000000000..229919ae58 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/emu/sys/EmuAmd64SyscallUseropLibraryTest.java @@ -0,0 +1,215 @@ +/* ### + * 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_SPACE_NAME; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +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.*; +import ghidra.pcode.exec.*; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataTypeConflictHandler; +import ghidra.program.model.data.PointerDataType; +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 EmuAmd64SyscallUseropLibraryTest extends AbstractGhidraHeadlessIntegrationTest { + + /** + * A library with two 4-argument syscalls. + * + *

+ * For x86:LE:64:default:gcc, the storage for the 4th argument varies by calling convention. For + * __stdcall, it's RCX. For syscall, it's R10. Both syscalls just return the 4th argument. Thus, + * it's possible to detect whether the emulator heeds conventions by binding each to a different + * convention, then invoking them with distinct values placed in RCX and R10 and verifying that + * the correct value shows in RAX, the return register. + */ + protected final class SyscallTestUseropLibrary + extends AnnotatedEmuSyscallUseropLibrary { + protected final Register regRAX; + + public SyscallTestUseropLibrary(PcodeMachine machine, Program program) { + super(machine, program); + regRAX = machine.getLanguage().getRegister("RAX"); + } + + @Override + public long readSyscallNumber(PcodeExecutorStatePiece state) { + return machine.getArithmetic().toConcrete(state.getVar(regRAX)).longValue(); + } + + @PcodeUserop + @EmuSyscall("syscall0") + public byte[] test_syscall0(byte[] arg0, byte[] arg1, byte[] arg2, byte[] arg3) { + return arg3; + } + + @PcodeUserop + @EmuSyscall("syscall1") + public byte[] test_syscall1(byte[] arg0, byte[] arg1, byte[] arg2, byte[] arg3) { + return arg3; + } + + @Override + public boolean handleError(PcodeExecutor executor, PcodeExecutionException err) { + return false; + } + } + + protected final class SyscallTestPcodeEmulator extends PcodeEmulator { + protected SyscallTestUseropLibrary syscalls; + + public SyscallTestPcodeEmulator() { + super((SleighLanguage) program.getLanguage()); + } + + @Override + protected PcodeUseropLibrary createUseropLibrary() { + syscalls = new SyscallTestUseropLibrary(this, program); + return syscalls; + } + } + + Program program; + SleighLanguage language; + Assembler asm; + + Register regRAX; + AddressSpace space; + Address start; + int size; + MemoryBlock block; + PcodeArithmetic arithmetic; + + protected void placeSyscall(long number, String name, String convention) 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(convention); + } + + @Before + public void setUp() throws Exception { + program = createDefaultProgram("HelloSyscalls", "x86:LE:64:default", "gcc", this); + language = (SleighLanguage) program.getLanguage(); + arithmetic = BytesPcodeArithmetic.forLanguage(language); + + regRAX = program.getRegister("RAX"); + space = program.getAddressFactory().getDefaultAddressSpace(); + start = space.getAddress(0x00400000); + size = 0x1000; + + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + block = program.getMemory() + .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(0, "syscall0", "__stdcall"); + placeSyscall(1, "syscall1", "syscall"); + + asm = Assemblers.getAssembler(program); + } + } + + @After + public void tearDown() { + if (program != null) { + program.release(this); + } + } + + public SyscallTestPcodeEmulator prepareEmulator() throws Exception { + SyscallTestPcodeEmulator emu = new SyscallTestPcodeEmulator(); + byte[] buf = new byte[size]; + assertEquals(size, block.getBytes(start, buf)); + emu.getSharedState().setVar(space, start.getOffset(), size, true, buf); + return emu; + } + + public PcodeThread launchThread(PcodeEmulator emu, Address pc) { + PcodeThread thread = emu.newThread(); + thread.overrideCounter(start); + thread.overrideContextWithDefault(); + thread.reInitialize(); + return thread; + } + + @Test + public void testSyscallWithStdcallConvention() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV RAX,0", + "MOV RCX,0xbeef", + "MOV R10,0xdead", + "SYSCALL"); + } + + SyscallTestPcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + thread.stepInstruction(4); + + assertArrayEquals(arithmetic.fromConst(0xbeef, regRAX.getNumBytes()), + thread.getState().getVar(regRAX)); + } + + @Test + public void testSyscallWithSyscallConvention() throws Exception { + try (UndoableTransaction tid = UndoableTransaction.start(program, "Initialize", true)) { + asm.assemble(start, + "MOV RAX,1", + "MOV RCX,0xdead", + "MOV R10,0xbeef", + "SYSCALL"); + } + + SyscallTestPcodeEmulator emu = prepareEmulator(); + PcodeThread thread = launchThread(emu, start); + + thread.stepInstruction(4); + + assertArrayEquals(arithmetic.fromConst(0xbeef, regRAX.getNumBytes()), + thread.getState().getVar(regRAX)); + } +} 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 new file mode 100644 index 0000000000..0c2ebb30ea --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibraryTest.java @@ -0,0 +1,430 @@ +/* ### + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.util.List; + +import org.junit.Test; + +import ghidra.app.plugin.processors.sleigh.SleighException; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.utils.Utils; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.lang.Register; +import ghidra.program.model.pcode.Varnode; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; + +public class AnnotatedPcodeUseropLibraryTest extends AbstractGhidraHeadlessIntegrationTest { + private class TestUseropLibrary extends AnnotatedPcodeUseropLibrary { + @Override + protected Lookup getMethodLookup() { + return MethodHandles.lookup(); + } + } + + protected PcodeExecutor createBytesExecutor() throws Exception { + return createBytesExecutor("Toy:BE:64:default"); + } + + protected PcodeExecutor createBytesExecutor(String languageId) throws Exception { + SleighLanguage language = + (SleighLanguage) getLanguageService().getLanguage(new LanguageID(languageId)); + PcodeExecutorState state = new BytesPcodeExecutorState(language); + PcodeArithmetic arithmetic = BytesPcodeArithmetic.forLanguage(language); + return new PcodeExecutor<>(language, arithmetic, state); + } + + protected void executeSleigh(PcodeExecutor executor, PcodeUseropLibrary library, + String... lines) { + PcodeProgram program = SleighProgramCompiler.compileProgram(executor.getLanguage(), "test", + List.of(lines), library); + executor.execute(program, library); + } + + protected void executeSleigh(PcodeUseropLibrary library, String... lines) + throws Exception { + executeSleigh(createBytesExecutor(), library, lines); + } + + protected static void assertBytes(long expectedVal, int expectedSize, byte[] actual) { + assertEquals(expectedSize, actual.length); + assertEquals(expectedVal, Utils.bytesToLong(actual, expectedSize, true)); + } + + protected static void assertConstVarnode(long expectedVal, int expectedSize, Varnode actual) { + assertTrue(actual.getAddress().isConstantAddress()); + assertEquals(expectedVal, actual.getOffset()); + assertEquals(expectedSize, actual.getSize()); + } + + protected static void assertRegVarnode(Register expected, Varnode actual) { + assertEquals(expected.getAddress(), actual.getAddress()); + assertEquals(expected.getNumBytes(), actual.getSize()); + } + + @Test + public void testNoParams() throws Exception { + var library = new TestUseropLibrary() { + boolean invoked = false; + + @PcodeUserop + private void __testop() { + invoked = true; + } + }; + + executeSleigh(library, "__testop();"); + assertTrue(library.invoked); + } + + @Test + public void testOneInputFixedT() throws Exception { + var library = new TestUseropLibrary() { + byte[] input; + + @PcodeUserop + private void __testop(byte[] input) { + this.input = input; + } + }; + + executeSleigh(library, "__testop(1234:4);"); + assertBytes(1234, 4, library.input); + } + + @Test + public void testVariadicInputT() throws Exception { + var library = new TestUseropLibrary() { + byte[][] inputs; + + @PcodeUserop(variadic = true) + private void __testop(byte[][] inputs) { + this.inputs = inputs; + } + }; + + executeSleigh(library, "__testop(1234:4, 4567:2);"); + assertBytes(1234, 4, library.inputs[0]); + assertBytes(4567, 2, library.inputs[1]); + } + + @Test + public void testVariadicInputVars() throws Exception { + var library = new TestUseropLibrary() { + Varnode[] inputs; + + @PcodeUserop(variadic = true) + private void __testop(Varnode[] inputs) { + this.inputs = inputs; + } + }; + + executeSleigh(library, "__testop(1234:4, 4567:2);"); + assertConstVarnode(1234, 4, library.inputs[0]); + assertConstVarnode(4567, 2, library.inputs[1]); + } + + @Test + public void testReturnedOutput() throws Exception { + var library = new TestUseropLibrary() { + @PcodeUserop + private byte[] __testop() { + return Utils.longToBytes(1234, 8, true); + } + }; + + PcodeExecutor executor = createBytesExecutor(); + Register r0 = executor.getLanguage().getRegister("r0"); + + executeSleigh(executor, library, "r0 = __testop();"); + assertBytes(1234, 8, executor.getState().getVar(r0)); + } + + @Test + public void testReturnedOutputBinaryFunc() throws Exception { + var library = new TestUseropLibrary() { + @PcodeUserop + private byte[] __testop(byte[] aBytes, byte[] bBytes) { + long a = Utils.bytesToLong(aBytes, 8, true); + long b = Utils.bytesToLong(bBytes, 8, true); + return Utils.longToBytes(a * a + b, 8, true); + } + }; + + PcodeExecutor executor = createBytesExecutor(); + Register r0 = executor.getLanguage().getRegister("r0"); + Register r1 = executor.getLanguage().getRegister("r1"); + + executor.getState().setVar(r0, Utils.longToBytes(10, 8, true)); + executeSleigh(executor, library, "r1 = __testop(r0, 59:8);"); + assertBytes(159, 8, executor.getState().getVar(r1)); + } + + @Test + public void testOpExecutor() throws Exception { + var library = new TestUseropLibrary() { + PcodeExecutor executor; + + @PcodeUserop + private void __testop(@OpExecutor PcodeExecutor executor) { + this.executor = executor; + } + }; + + PcodeExecutor executor = createBytesExecutor(); + executeSleigh(executor, library, "__testop();"); + assertEquals(executor, library.executor); + } + + @Test + public void testOpState() throws Exception { + var library = new TestUseropLibrary() { + PcodeExecutorStatePiece state; + + @PcodeUserop + private void __testop(@OpState PcodeExecutorStatePiece state) { + this.state = state; + } + }; + + PcodeExecutor executor = createBytesExecutor(); + executeSleigh(executor, library, "__testop();"); + assertEquals(executor.getState(), library.state); + } + + @Test + public void testOpLibrary() throws Exception { + var library = new TestUseropLibrary() { + PcodeUseropLibrary lib; + + @PcodeUserop + private void __testop(@OpLibrary PcodeUseropLibrary lib) { + this.lib = lib; + } + }; + + PcodeExecutor executor = createBytesExecutor(); + executeSleigh(executor, library, "__testop();"); + assertEquals(library, library.lib); + } + + @Test + public void testOpOutput() throws Exception { + var library = new TestUseropLibrary() { + Varnode outVar; + + @PcodeUserop + private void __testop(@OpOutput Varnode outVar) { + this.outVar = outVar; + } + }; + + PcodeExecutor executor = createBytesExecutor(); + Register r0 = executor.getLanguage().getRegister("r0"); + executeSleigh(executor, library, "r0 = __testop();"); + assertRegVarnode(r0, library.outVar); + } + + @Test + public void testKitchenSink() throws Exception { + var library = new TestUseropLibrary() { + PcodeExecutor executor; + PcodeExecutorStatePiece state; + PcodeUseropLibrary lib; + Varnode outVar; + Varnode inVar0; + byte[] inVal1; + + @PcodeUserop + private byte[] __testop( + @OpOutput Varnode outVar, + @OpLibrary PcodeUseropLibrary lib, + @OpExecutor PcodeExecutor executor, + Varnode inVar0, + @OpState PcodeExecutorStatePiece state, + byte[] inVal1) { + this.executor = executor; + this.state = state; + this.lib = lib; + this.outVar = outVar; + this.inVar0 = inVar0; + this.inVal1 = inVal1; + + return inVal1; + } + }; + + PcodeExecutor executor = createBytesExecutor(); + Register r0 = executor.getLanguage().getRegister("r0"); + Register r1 = executor.getLanguage().getRegister("r1"); + executeSleigh(executor, library, "r0 = __testop(r1, 1234:8);"); + assertEquals(executor, library.executor); + assertEquals(executor.getState(), library.state); + assertEquals(library, library.lib); + assertRegVarnode(r0, library.outVar); + assertRegVarnode(r1, library.inVar0); + assertBytes(1234, 8, library.inVal1); + assertBytes(1234, 8, executor.getState().getVar(r0)); + } + + @Test(expected = SleighException.class) + public void testErrNotExported() throws Exception { + var library = new TestUseropLibrary() { + @SuppressWarnings("unused") + private void __testop() { + } + }; + + executeSleigh(library, "r0 = __testop();"); + } + + @Test(expected = PcodeExecutionException.class) + public void testErrParameterCountMismatch() throws Exception { + var library = new TestUseropLibrary() { + @PcodeUserop + private void __testop(Varnode in0) { + } + }; + + executeSleigh(library, "r0 = __testop();"); + } + + @Test(expected = IllegalArgumentException.class) + public void testErrAccess() throws Exception { + new AnnotatedPcodeUseropLibrary() { + @PcodeUserop + private void __testop(Varnode in0) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrReturnType() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private int __testop() { + return 0; + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrInputType() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(int in0) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrExecutorType() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpExecutor int executor) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrStateType() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpState int state) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrOutputType() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpOutput int out) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrVariadicInputsType() throws Exception { + new TestUseropLibrary() { + @PcodeUserop(variadic = true) + private void __testop(int[] ins) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrConflictingAnnotations() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpExecutor @OpState int in0) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrDuplicateExecutor() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpExecutor PcodeExecutor executor0, + @OpExecutor PcodeExecutor executor1) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrDuplicateState() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpState PcodeExecutorStatePiece state0, + @OpState PcodeExecutorStatePiece state1) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrDuplicateOutput() throws Exception { + new TestUseropLibrary() { + @PcodeUserop + private void __testop(@OpOutput Varnode out0, @OpOutput Varnode out1) { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrMissingVariadicInputs() throws Exception { + new TestUseropLibrary() { + @PcodeUserop(variadic = true) + private void __testop() { + } + }; + } + + @Test(expected = IllegalArgumentException.class) + public void testErrDuplicateVariadicInputs() throws Exception { + new TestUseropLibrary() { + @PcodeUserop(variadic = true) + private void __testop(Varnode[] ins0, Varnode[] ins1) { + } + }; + } + +} diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/PcodeFrameTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/PcodeFrameTest.java index ee636fe8e4..df7763cdc4 100644 --- a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/PcodeFrameTest.java +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/exec/PcodeFrameTest.java @@ -48,8 +48,8 @@ public class PcodeFrameTest extends AbstractGhidraHeadlessIntegrationTest { static final List SAMPLE_LIB_USEROP = List.of( "__lib_userop(r0);"); - static class MyLib extends AnnotatedSleighUseropLibrary { - @SleighUserop + static class MyLib extends AnnotatedPcodeUseropLibrary { + @PcodeUserop void __lib_userop() { } } diff --git a/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java new file mode 100644 index 0000000000..8ff1f351e0 --- /dev/null +++ b/Ghidra/Debug/ProposedUtils/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java @@ -0,0 +1,240 @@ +/* ### + * 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.struct.sub; + +import static org.junit.Assert.assertEquals; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodHandles.Lookup; +import java.util.List; + +import org.junit.Before; +import org.junit.Test; + +import ghidra.app.plugin.processors.sleigh.SleighException; +import ghidra.pcode.exec.PcodeUseropLibrary; +import ghidra.pcode.exec.SleighPcodeUseropDefinition; +import ghidra.pcode.struct.StructuredSleigh; +import ghidra.program.model.lang.*; +import ghidra.program.model.pcode.Varnode; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.test.ToyProgramBuilder; + +public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest { + private Language toy; + private CompilerSpec cs; + private Register r0; + + protected class TestStructuredSleigh extends StructuredSleigh { + protected TestStructuredSleigh() { + super(cs); + } + + @Override + protected Lookup getMethodLookup() { + return MethodHandles.lookup(); + } + } + + @Before + public void setUp() throws Exception { + toy = getLanguageService().getLanguage(new LanguageID(ToyProgramBuilder._TOY64_BE)); + cs = toy.getDefaultCompilerSpec(); + r0 = toy.getRegister("r0"); + } + + @Test + public void testSimpleReturn() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop(type = "int") + public void my_userop(@Param(type = "int", name = "param_1") Var param_1) { + _result(param_1.muli(2)); + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of("__op_output = (param_1 * (0x2:4));\n"), myUserop.getLines()); + } + + @Test(expected = SleighException.class) + public void testDuplicateSymbolErr() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop(type = "int") + public void my_userop(@Param(type = "int", name = "r0") Var r0) { + _result(r0.muli(2)); + } + }; + ss.generate().get("my_userop"); + } + + @Test + public void testUseRegister() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + final Var vR0 = reg(r0, type("int"));; + + @StructuredUserop(type = "int") + public void my_userop() { + _result(vR0.muli(2)); + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of("__op_output = (r0 * (0x2:4));\n"), myUserop.getLines()); + } + + @Test + public void testLocalVarNoInit() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop(type = "int") + public void my_userop() { + Var myVar = local("my_var", type("int")); + _result(myVar.muli(2)); + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of("" + + "local my_var:4;\n" + + "__op_output = (my_var * (0x2:4));\n"), + myUserop.getLines()); + // Verify the source compiles + myUserop.programFor(new Varnode(r0.getAddress(), r0.getNumBytes()), List.of(), + PcodeUseropLibrary.NIL); + } + + @Test + public void testUseropReturnTypeDefaultsVoid() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop + public void my_userop() { + // Don't need to do anything + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of(""), myUserop.getLines()); + } + + @Test + public void testIfElseStmt() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop + public void my_userop(@Param(name = "tmp", type = "int") Var tmp) { + _if(lit(1, 1), () -> { + tmp.set(1); + })._else(() -> { + tmp.set(2); + }); + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of("" + + "if (0x1:1) goto ;\n" + + "tmp = (0x2:4);\n" + + "goto ;\n" + + "\n" + + "tmp = (0x1:4);\n" + + "\n"), + myUserop.getLines()); + } + + @Test + public void testIfStmt() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop + public void my_userop(@Param(name = "tmp", type = "int") Var tmp) { + _if(lit(1, 1), () -> { + tmp.set(1); + }); + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of("" + + "if (!(0x1:1)) goto ;\n" + + "tmp = (0x1:4);\n" + + "\n"), + myUserop.getLines()); + } + + @Test + public void testForStmt() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop(type = "int") + public void my_userop(@Param(name = "n", type = "int") Var n) { + Var i = local("i", type("int")); + Var sum = local("sum", type("int")); + _for(i.set(0), i.ltiu(n), i.inc(), () -> { + sum.addiTo(i); + }); + _result(sum); + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of("" + "local i:4;\n" + + "local sum:4;\n" + + "i = (0x0:4);\n" + + "\n" + + "if (i >= n) goto ;\n" + + "sum = (sum + i);\n" + + "i = (i + (0x1:4));\n" + + "goto ;\n" + + "\n" + + "__op_output = sum;\n"), + myUserop.getLines()); + } + + @Test + public void testForWithBreakStmt() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop(type = "int") + public void my_userop(@Param(name = "n", type = "int") Var n) { + Var i = local("i", type("int")); + Var sum = local("sum", type("int")); + _for(i.set(0), i.ltiu(n), i.inc(), () -> { + sum.addiTo(i); + _if(sum.gteiu(1000), () -> { + _break(); + }); + }); + _result(sum); + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of("" + + "local i:4;\n" + + "local sum:4;\n" + + "i = (0x0:4);\n" + + "\n" + + "if (i >= n) goto ;\n" + + "sum = (sum + i);\n" + + "if (sum >= (0x3e8:4)) goto ;\n" + + "i = (i + (0x1:4));\n" + + "goto ;\n" + + "\n" + + "__op_output = sum;\n"), + myUserop.getLines()); + } + + @Test + public void testReturnStmt() throws Exception { + StructuredSleigh ss = new TestStructuredSleigh() { + @StructuredUserop + public void my_userop() { + _return(lit(0xdeadbeefL, 8).deref()); + } + }; + SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); + assertEquals(List.of("return (* (0xdeadbeef:8));\n"), myUserop.getLines()); + // TODO: Test that the generated code compiles in a slaspec file. + // It's rejected for injects because "return" is not valid there. + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcodeCPort/slgh_compile/PcodeCompile.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcodeCPort/slgh_compile/PcodeCompile.java index ae14df5cfd..cad533f941 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcodeCPort/slgh_compile/PcodeCompile.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/pcodeCPort/slgh_compile/PcodeCompile.java @@ -80,6 +80,7 @@ public abstract class PcodeCompile { /** * Handle a sleigh 'macro' invocation, returning the resulting p-code op templates (OpTpl) + * * @param location is the file/line where the macro is invoked * @param sym MacroSymbol is the macro symbol * @param param is the parsed list of operand expressions @@ -143,7 +144,8 @@ public abstract class PcodeCompile { if (vn.getOffset().equals(vt.getOffset())) { if ((size.getType() == ConstTpl.const_type.real) && (vn.getSize().getType() == ConstTpl.const_type.real) && - (vn.getSize().getReal() != 0) && (vn.getSize().getReal() != size.getReal())) { + (vn.getSize().getReal() != 0) && + (vn.getSize().getReal() != size.getReal())) { throw new SleighError(String.format("Localtemp size mismatch: %d vs %d", vn.getSize().getReal(), size.getReal()), op.location); } @@ -155,7 +157,8 @@ public abstract class PcodeCompile { if (vn.isLocalTemp() && (vn.getOffset().equals(vt.getOffset()))) { if ((size.getType() == ConstTpl.const_type.real) && (vn.getSize().getType() == ConstTpl.const_type.real) && - (vn.getSize().getReal() != 0) && (vn.getSize().getReal() != size.getReal())) { + (vn.getSize().getReal() != 0) && + (vn.getSize().getReal() != size.getReal())) { throw new SleighError(String.format("Input size mismatch: %d vs %d", vn.getSize().getReal(), size.getReal()), op.location); } @@ -194,10 +197,10 @@ public abstract class PcodeCompile { labsym.setPlaced(); VectorSTL res = new VectorSTL(); OpTpl op = new OpTpl(location, OpCode.CPUI_PTRADD); - VarnodeTpl idvn = - new VarnodeTpl(location, new ConstTpl(getConstantSpace()), new ConstTpl( - ConstTpl.const_type.real, labsym.getIndex()), new ConstTpl( - ConstTpl.const_type.real, 4)); + VarnodeTpl idvn = new VarnodeTpl(location, + new ConstTpl(getConstantSpace()), + new ConstTpl(ConstTpl.const_type.real, labsym.getIndex()), + new ConstTpl(ConstTpl.const_type.real, 4)); op.addInput(idvn); res.push_back(op); return res; @@ -346,7 +349,8 @@ public abstract class PcodeCompile { return res; } - public VectorSTL createOpNoOut(Location location, OpCode opc, ExprTree vn1, ExprTree vn2) { + public VectorSTL createOpNoOut(Location location, OpCode opc, ExprTree vn1, + ExprTree vn2) { // Create new expression by creating op with given -opc- // and inputs vn1 and vn2. Free the input expressions entry("createOpNoOut", opc, vn1, vn2); @@ -438,14 +442,14 @@ public abstract class PcodeCompile { return ExprTree.appendParams(op, param); } - public ExprTree createVariadic(Location location,OpCode opc,VectorSTL param) { + public ExprTree createVariadic(Location location, OpCode opc, VectorSTL param) { entry("createVariadic", location, opc, param); VarnodeTpl outvn = buildTemporary(location); ExprTree res = new ExprTree(location); OpTpl op = new OpTpl(location, opc); res.ops = ExprTree.appendParams(op, param); res.ops.back().setOutput(outvn); - res.outvn = new VarnodeTpl(location,outvn); + res.outvn = new VarnodeTpl(location, outvn); return res; } @@ -602,7 +606,8 @@ public abstract class PcodeCompile { // The result is truncated to the smallest byte size that can // contain the indicated number of bits. The result has the // desired bits shifted all the way to the right - public ExprTree createBitRange(Location location, SpecificSymbol sym, int bitoffset, int numbits) { + public ExprTree createBitRange(Location location, SpecificSymbol sym, int bitoffset, + int numbits) { entry("createBitRange", location, sym, bitoffset, numbits); String errmsg = ""; if (numbits == 0) { @@ -841,12 +846,12 @@ public abstract class PcodeCompile { break; case CPUI_CPOOLREF: if (op.getOut().isZeroSize() && (!op.getIn(0).isZeroSize())) { - force_size(op.getOut(),op.getIn(0).getSize(),ops); + force_size(op.getOut(), op.getIn(0).getSize(), ops); } if (op.getIn(0).isZeroSize() && (!op.getOut().isZeroSize())) { - force_size(op.getIn(0),op.getOut().getSize(),ops); + force_size(op.getIn(0), op.getOut().getSize(), ops); } - for(i=1;i operands) { + public Object findInternalFunction(Location location, String name, + VectorSTL operands) { ExprTree r = null; ExprTree s = null; if (operands.size() > 0) { @@ -990,13 +997,13 @@ public abstract class PcodeCompile { if (operands.size() >= 2) { return createVariadic(location, OpCode.CPUI_CPOOLREF, operands); } - reportError(location,name+"() expects at least two arguments"); + reportError(location, name + "() expects at least two arguments"); } if ("newobject".equals(name)) { if (operands.size() >= 1) { return createVariadic(location, OpCode.CPUI_NEW, operands); } - reportError(location,name+"() expects at least one argument"); + reportError(location, name + "() expects at least one argument"); } if ("popcount".equals(name) && hasOperands(1, operands, location, name)) { return createOp(location, OpCode.CPUI_POPCOUNT, r); @@ -1016,9 +1023,10 @@ public abstract class PcodeCompile { } /** - * EXTREMELY IMPORTANT: keep this up to date with findInternalFunction above!!! - * Determine if the given identifier is a sleigh internal function. Used to - * prevent user-defined p-code names from colliding with internal names + * EXTREMELY IMPORTANT: keep this up to date with findInternalFunction above!!! Determine if the + * given identifier is a sleigh internal function. Used to prevent user-defined p-code names + * from colliding with internal names + * * @param name is the given identifier to check * @return true if the identifier is a reserved internal function */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeParser.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeParser.java index 66601c28be..ad33e53752 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeParser.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/PcodeParser.java @@ -46,9 +46,9 @@ import ghidra.sleigh.grammar.SleighParser_SemanticParser.semantic_return; import ghidra.util.exception.AssertException; /** - * This class is intended to parse p-code snippets, typically from compiler specification files - * or extensions. This is outside the normal SLEIGH compilation process, and the parser is built - * on top of an existing SleighLanguage. + * This class is intended to parse p-code snippets, typically from compiler specification files or + * extensions. This is outside the normal SLEIGH compilation process, and the parser is built on top + * of an existing SleighLanguage. */ public class PcodeParser extends PcodeCompile { @@ -68,6 +68,7 @@ public class PcodeParser extends PcodeCompile { /** * Build parser from an existing SleighLanguage. + * * @param language is the existing language * @param ubase is the starting offset for allocating temporary registers */ @@ -90,9 +91,12 @@ public class PcodeParser extends PcodeCompile { } /** - * Inject a symbol representing an "operand" to the pcode snippet. This puts a placeholder in the - * resulting template, which gets filled in with the context specific storage locations when final - * p-code is generated + * Inject a symbol representing an "operand" to the pcode snippet. + * + *

+ * This puts a placeholder in the resulting template, which gets filled in with the context + * specific storage locations when final p-code is generated + * * @param loc is location information for the operand * @param name of operand symbol * @param index to use for the placeholder @@ -153,6 +157,10 @@ public class PcodeParser extends PcodeCompile { return PcodeParser.this.sleigh.findSymbol(nm); } + public SleighBase getSleigh() { + return sleigh; + } + @Override public AddrSpace getConstantSpace() { return sleigh.getConstantSpace(); @@ -217,10 +225,10 @@ public class PcodeParser extends PcodeCompile { /** * This class wraps on existing SleighLanguage with the SleighBase interface expected by - * PcodeCompile. It populates the symbol table with user-defined operations and the global + * PcodeCompile. It populates the symbol table with user-defined operations and the global * VarnodeSymbol objects, which typically includes all the general purpose registers. */ - private static class PcodeTranslate extends SleighBase { + public static class PcodeTranslate extends SleighBase { private void copySpaces(SleighLanguage language) { insertSpace(new ConstantSpace(this)); @@ -270,8 +278,9 @@ public class PcodeParser extends PcodeCompile { } /** - * Populate the predefined symbol table for the parser from the given SLEIGH language. - * We only use user-defined op symbols and varnode symbols. + * Populate the predefined symbol table for the parser from the given SLEIGH language. We + * only use user-defined op symbols and varnode symbols. + * * @param language is the SLEIGH language */ private void copySymbols(SleighLanguage language) { @@ -383,11 +392,12 @@ public class PcodeParser extends PcodeCompile { /** * Compile pcode semantic statements. + * * @param pcodeStatements is the raw source to parse * @param srcFile source filename from which pcodeStatements came ( * @param srcLine line number in srcFile corresponding to pcodeStatements - * @return ConstructTpl. A null may be returned or - * an exception thrown if parsing/compiling fails (see application log for errors). + * @return ConstructTpl. A null may be returned or an exception thrown if parsing/compiling + * fails (see application log for errors). * @throws SleighException pcode compile error */ public ConstructTpl compilePcode(String pcodeStatements, String srcFile, int srcLine)