From 9d186a3e30ef925b96ed77f052d67bf92738819e Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Wed, 2 Sep 2026 18:36:58 +0000 Subject: [PATCH] GP-5976: Implement constant folding and other optimizations in JIT emulator. --- .../emu/taint/lib/TaintEmuUnixFileSystem.java | 12 +- ...aintFileReadsLinuxAmd64SyscallLibrary.java | 15 +- .../DebuggerEmuExampleScript.java | 4 +- .../DemoPcodeUseropLibrary.java | 16 +- .../ghidra_scripts/DemoSyscallLibrary.java | 115 +- .../ghidra_scripts/EmuDeskCheckScript.java | 10 +- .../StandAloneStructuredSleighScript.java | 8 +- .../StandAloneSyscallEmuExampleScript.java | 6 +- .../EmuLinuxAmd64SyscallUseropLibrary.java | 31 +- .../EmuLinuxX86SyscallUseropLibrary.java | 74 +- .../sys/AnnotatedEmuSyscallUseropLibrary.java | 78 +- .../pcode/emu/sys/BytesEmuFileContents.java | 13 +- .../ghidra/pcode/emu/sys/EmuFileContents.java | 15 +- .../emu/sys/EmuProcessExitedException.java | 19 +- .../pcode/emu/sys/EmuSyscallLibrary.java | 216 +-- .../pcode/emu/sys/PairedEmuFileContents.java | 12 +- .../emu/sys/UseropEmuSyscallDefinition.java | 53 +- .../pcode/emu/unix/AbsEmuUnixFileHandle.java | 111 ++ .../pcode/emu/unix/AbstractEmuUnixFile.java | 25 +- .../emu/unix/AbstractEmuUnixFileHandle.java | 94 ++ .../AbstractEmuUnixSyscallUseropLibrary.java | 172 ++- .../unix/AbstractStreamEmuUnixFileHandle.java | 31 +- .../emu/unix/DefaultEmuUnixFileHandle.java | 119 +- .../ghidra/pcode/emu/unix/EmuUnixFile.java | 32 +- .../pcode/emu/unix/EmuUnixFileDescriptor.java | 53 +- .../emu/unix/IOStreamEmuUnixFileHandle.java | 18 +- .../java/ghidra/pcode/struct/IndexExpr.java | 10 +- .../ghidra/pcode/struct/StructuredSleigh.java | 89 +- ...EmuLinuxAmd64SyscallUseropLibraryTest.java | 469 ++++--- .../EmuLinuxX86SyscallUseropLibraryTest.java | 459 ++++--- ...JitLinuxAmd64SyscallUseropLibraryTest.java | 106 ++ .../JitLinuxX86SyscallUseropLibraryTest.java | 107 ++ .../sys/EmuAmd64SyscallUseropLibraryTest.java | 35 +- .../struct/sub/StructuredSleighTest.java | 32 +- .../util/datastruct/SemisparseByteArray.java | 20 +- .../ghidra/pcode/emu/DefaultPcodeThread.java | 26 +- .../ghidra/pcode/emu/ModifiedPcodeThread.java | 15 + .../ghidra/pcode/emu/jit/JitCompiler.java | 65 +- .../pcode/emu/jit/JitConfiguration.java | 54 +- .../java/ghidra/pcode/emu/jit/JitPassage.java | 136 +- .../pcode/emu/jit/JitPcodeEmulator.java | 14 +- .../ghidra/pcode/emu/jit/JitPcodeThread.java | 11 - .../emu/jit/analysis/JitAllocationModel.java | 24 +- .../emu/jit/analysis/JitControlFlowModel.java | 63 +- .../jit/analysis/JitDataFlowArithmetic.java | 17 +- .../analysis/JitDataFlowBlockAnalyzer.java | 6 - .../emu/jit/analysis/JitDataFlowExecutor.java | 178 ++- .../emu/jit/analysis/JitDataFlowModel.java | 56 +- .../emu/jit/analysis/JitDataFlowState.java | 42 +- .../analysis/JitDataFlowUseropLibrary.java | 120 +- .../pcode/emu/jit/analysis/JitOpUseModel.java | 133 +- .../jit/analysis/JitReachabilityModel.java | 164 +++ .../pcode/emu/jit/analysis/JitTypeModel.java | 88 +- .../emu/jit/analysis/JitVarScopeModel.java | 55 +- .../pcode/emu/jit/decode/CanDecode.java | 31 + .../pcode/emu/jit/decode/CollectExecutor.java | 78 ++ .../pcode/emu/jit/decode/DecodedStride.java | 4 +- .../pcode/emu/jit/decode/DecoderExecutor.java | 209 ++- .../emu/jit/decode/DecoderForOnePassage.java | 164 ++- .../emu/jit/decode/DecoderForOneStride.java | 24 +- .../emu/jit/decode/DecoderUseropLibrary.java | 102 +- .../emu/jit/decode/JitPassageDecoder.java | 17 +- .../emu/jit/folding/FoldRevalidator.java | 263 ++++ .../emu/jit/folding/FoldedArithmetic.java | 238 ++++ .../pcode/emu/jit/folding/FoldedState.java | 245 ++++ .../emu/jit/folding/FoldingExecutor.java | 252 ++++ .../pcode/emu/jit/folding/MaskedBytes.java | 473 +++++++ .../pcode/emu/jit/gen/ExceptionHandler.java | 9 +- .../emu/jit/gen/FieldForSpaceIndirect.java | 3 - .../pcode/emu/jit/gen/FieldForUserop.java | 5 +- .../pcode/emu/jit/gen/JitCodeGenerator.java | 341 ++++- .../pcode/emu/jit/gen/op/BranchIndOpGen.java | 91 +- .../pcode/emu/jit/gen/op/BranchOpGen.java | 12 +- .../pcode/emu/jit/gen/op/CBranchOpGen.java | 28 +- .../pcode/emu/jit/gen/op/CallOtherOpGen.java | 41 +- .../emu/jit/gen/tgt/JitCompiledPassage.java | 94 +- .../jit/gen/tgt/JitCompiledPassageClass.java | 4 - .../ghidra/pcode/emu/jit/gen/util/Lbl.java | 26 +- .../ghidra/pcode/emu/jit/gen/util/Misc.java | 7 + .../ghidra/pcode/emu/jit/gen/util/Op.java | 561 +++++++- .../ghidra/pcode/emu/jit/gen/var/VarGen.java | 28 +- .../ghidra/pcode/emu/jit/op/JitBinOp.java | 11 + .../pcode/emu/jit/op/JitBranchIndOp.java | 13 +- .../ghidra/pcode/emu/jit/op/JitBranchOp.java | 8 + .../ghidra/pcode/emu/jit/op/JitCBranchOp.java | 12 +- .../pcode/emu/jit/op/JitCallOtherDefOp.java | 19 +- .../emu/jit/op/JitCallOtherMissingOp.java | 9 + .../pcode/emu/jit/op/JitCallOtherOp.java | 20 +- .../pcode/emu/jit/op/JitCallOtherOpIf.java | 9 +- .../pcode/emu/jit/op/JitCatenateOp.java | 12 +- .../ghidra/pcode/emu/jit/op/JitLoadOp.java | 10 + .../ghidra/pcode/emu/jit/op/JitNopOp.java | 8 +- .../java/ghidra/pcode/emu/jit/op/JitOp.java | 36 +- .../ghidra/pcode/emu/jit/op/JitPhiOp.java | 14 + .../ghidra/pcode/emu/jit/op/JitStoreOp.java | 10 + .../pcode/emu/jit/op/JitSynthSubPieceOp.java | 16 +- .../java/ghidra/pcode/emu/jit/op/JitUnOp.java | 10 + .../pcode/emu/jit/op/JitUnimplementedOp.java | 7 + .../pcode/emu/jit/var/AbstractJitVar.java | 5 - .../emu/jit/var/AbstractJitVarnodeVar.java | 7 + .../ghidra/pcode/emu/jit/var/JitConstVal.java | 8 +- .../ghidra/pcode/emu/jit/var/JitFailVal.java | 6 + .../emu/jit/var/JitIndirectMemoryVar.java | 7 +- .../java/ghidra/pcode/emu/jit/var/JitVal.java | 9 + .../ghidra/pcode/emu/util/CountsPcodeOps.java | 36 + .../ghidra/pcode/emu/util/PcodeOpCounter.java | 80 ++ .../java/ghidra/pcode/emulate/Emulate.java | 375 +++-- .../AbstractSleighPcodeUseropDefinition.java | 51 +- .../exec/AnnotatedPcodeUseropLibrary.java | 227 ++- .../FixedSleighPcodeUseropDefinition.java | 17 +- ...OverloadedSleighPcodeUseropDefinition.java | 17 +- .../ghidra/pcode/exec/PcodeArithmetic.java | 123 +- .../java/ghidra/pcode/exec/PcodeExecutor.java | 51 +- .../pcode/exec/PcodeExecutorStatePiece.java | 1 - .../ghidra/pcode/exec/PcodeExpression.java | 13 +- .../java/ghidra/pcode/exec/PcodeFrame.java | 42 +- .../java/ghidra/pcode/exec/PcodeProgram.java | 75 +- .../ghidra/pcode/exec/PcodeUseropLibrary.java | 209 ++- .../exec/SleighPcodeUseropDefinition.java | 59 +- .../pcode/exec/SleighProgramCompiler.java | 43 +- .../java/ghidra/pcode/exec/SymbolsCache.java | 64 + .../pcode/opbehavior/BinaryOpBehavior.java | 52 +- .../ghidra/pcode/opbehavior/OpBehavior.java | 98 +- .../pcode/opbehavior/OpBehaviorBoolAnd.java | 17 +- .../opbehavior/OpBehaviorBoolNegate.java | 18 +- .../pcode/opbehavior/OpBehaviorBoolOr.java | 18 +- .../pcode/opbehavior/OpBehaviorBoolXor.java | 17 +- .../pcode/opbehavior/OpBehaviorCopy.java | 22 +- .../pcode/opbehavior/OpBehaviorEqual.java | 17 +- .../pcode/opbehavior/OpBehaviorFactory.java | 179 ++- .../pcode/opbehavior/OpBehaviorFloatAbs.java | 18 +- .../pcode/opbehavior/OpBehaviorFloatAdd.java | 17 +- .../pcode/opbehavior/OpBehaviorFloatCeil.java | 18 +- .../pcode/opbehavior/OpBehaviorFloatDiv.java | 17 +- .../opbehavior/OpBehaviorFloatEqual.java | 18 +- .../OpBehaviorFloatFloat2Float.java | 18 +- .../opbehavior/OpBehaviorFloatFloor.java | 18 +- .../opbehavior/OpBehaviorFloatInt2Float.java | 18 +- .../pcode/opbehavior/OpBehaviorFloatLess.java | 17 +- .../opbehavior/OpBehaviorFloatLessEqual.java | 18 +- .../pcode/opbehavior/OpBehaviorFloatMult.java | 17 +- .../pcode/opbehavior/OpBehaviorFloatNan.java | 17 +- .../pcode/opbehavior/OpBehaviorFloatNeg.java | 18 +- .../opbehavior/OpBehaviorFloatNotEqual.java | 18 +- .../opbehavior/OpBehaviorFloatRound.java | 18 +- .../pcode/opbehavior/OpBehaviorFloatSqrt.java | 18 +- .../pcode/opbehavior/OpBehaviorFloatSub.java | 18 +- .../opbehavior/OpBehaviorFloatTrunc.java | 17 +- .../pcode/opbehavior/OpBehaviorInt2Comp.java | 17 +- .../pcode/opbehavior/OpBehaviorIntAdd.java | 18 +- .../pcode/opbehavior/OpBehaviorIntAnd.java | 17 +- .../pcode/opbehavior/OpBehaviorIntCarry.java | 13 +- .../pcode/opbehavior/OpBehaviorIntDiv.java | 19 +- .../pcode/opbehavior/OpBehaviorIntLeft.java | 18 +- .../pcode/opbehavior/OpBehaviorIntLess.java | 18 +- .../opbehavior/OpBehaviorIntLessEqual.java | 18 +- .../pcode/opbehavior/OpBehaviorIntMult.java | 18 +- .../pcode/opbehavior/OpBehaviorIntNegate.java | 18 +- .../pcode/opbehavior/OpBehaviorIntOr.java | 18 +- .../pcode/opbehavior/OpBehaviorIntRem.java | 13 +- .../pcode/opbehavior/OpBehaviorIntRight.java | 18 +- .../opbehavior/OpBehaviorIntSborrow.java | 17 +- .../pcode/opbehavior/OpBehaviorIntScarry.java | 18 +- .../pcode/opbehavior/OpBehaviorIntSdiv.java | 18 +- .../pcode/opbehavior/OpBehaviorIntSext.java | 34 +- .../pcode/opbehavior/OpBehaviorIntSless.java | 18 +- .../opbehavior/OpBehaviorIntSlessEqual.java | 18 +- .../pcode/opbehavior/OpBehaviorIntSrem.java | 18 +- .../pcode/opbehavior/OpBehaviorIntSright.java | 18 +- .../pcode/opbehavior/OpBehaviorIntSub.java | 18 +- .../pcode/opbehavior/OpBehaviorIntXor.java | 17 +- .../pcode/opbehavior/OpBehaviorIntZext.java | 25 +- .../pcode/opbehavior/OpBehaviorLzcount.java | 12 +- .../pcode/opbehavior/OpBehaviorNotEqual.java | 17 +- .../pcode/opbehavior/OpBehaviorPiece.java | 17 +- .../pcode/opbehavior/OpBehaviorPopcount.java | 9 +- .../pcode/opbehavior/OpBehaviorSubpiece.java | 18 +- .../pcode/opbehavior/SpecialOpBehavior.java | 40 +- .../pcode/opbehavior/UnaryOpBehavior.java | 43 +- .../utilities/util/AnnotationUtilities.java | 7 +- .../folding/AbstractFoldedArithmeticTest.java | 258 ++++ .../jit/folding/BEFoldedArithmeticTest.java | 25 + .../jit/folding/LEFoldedArithmeticTest.java | 25 + .../ghidra/pcode/exec/PcodeFrameTest.java | 34 +- .../opbehavior/AbstractOpBehaviorTest.java | 18 +- .../opbehavior/OpBehaviorBoolAndTest.java | 41 +- .../opbehavior/OpBehaviorBoolNegateTest.java | 31 +- .../opbehavior/OpBehaviorBoolOrTest.java | 43 +- .../opbehavior/OpBehaviorBoolXorTest.java | 43 +- .../opbehavior/OpBehaviorFloatAbsTest.java | 83 +- .../opbehavior/OpBehaviorFloatAddTest.java | 115 +- .../opbehavior/OpBehaviorFloatCeilTest.java | 83 +- .../opbehavior/OpBehaviorFloatDivTest.java | 75 +- .../opbehavior/OpBehaviorFloatEqualTest.java | 72 +- .../OpBehaviorFloatFloat2FloatTest.java | 85 +- .../opbehavior/OpBehaviorFloatFloorTest.java | 95 +- .../OpBehaviorFloatInt2FloatTest.java | 57 +- .../OpBehaviorFloatLessEqualTest.java | 94 +- .../opbehavior/OpBehaviorFloatLessTest.java | 93 +- .../opbehavior/OpBehaviorFloatMultTest.java | 75 +- .../opbehavior/OpBehaviorFloatNanTest.java | 36 +- .../opbehavior/OpBehaviorFloatNegTest.java | 83 +- .../OpBehaviorFloatNotEqualTest.java | 70 +- .../opbehavior/OpBehaviorFloatRoundTest.java | 132 +- .../opbehavior/OpBehaviorFloatSqrtTest.java | 37 +- .../opbehavior/OpBehaviorFloatSubTest.java | 116 +- .../opbehavior/OpBehaviorFloatTruncTest.java | 63 +- .../opbehavior/OpBehaviorInt2CompTest.java | 59 +- .../opbehavior/OpBehaviorIntAddTest.java | 136 +- .../opbehavior/OpBehaviorIntAndTest.java | 119 +- .../opbehavior/OpBehaviorIntCarryTest.java | 67 +- .../opbehavior/OpBehaviorIntDivTest.java | 123 +- .../opbehavior/OpBehaviorIntLeftTest.java | 77 +- .../OpBehaviorIntLessEqualTest.java | 144 +- .../opbehavior/OpBehaviorIntLessTest.java | 146 +- .../opbehavior/OpBehaviorIntMultTest.java | 128 +- .../opbehavior/OpBehaviorIntNegateTest.java | 76 +- .../pcode/opbehavior/OpBehaviorIntOrTest.java | 123 +- .../opbehavior/OpBehaviorIntRemTest.java | 26 +- .../opbehavior/OpBehaviorIntRightTest.java | 74 +- .../OpBehaviorIntSLessEqualTest.java | 147 +- .../opbehavior/OpBehaviorIntSLessTest.java | 147 +- .../opbehavior/OpBehaviorIntSRightTest.java | 101 +- .../opbehavior/OpBehaviorLzcountTest.java | 78 +- .../opbehavior/OpBehaviorPopcountTest.java | 77 +- .../java/ghidra/app/util/pcode/Appender.java | 6 +- .../Aarch64PcodeUseropLibraryFactory.java | 63 +- .../emulation/Aarch64PcodeLibraryTest.java | 48 +- .../ArmPcodeUseropLibraryFactory.java | 45 +- .../HexagonPcodeUseropLibraryFactory.java | 177 ++- .../PpcPcodeUseropLibraryFactory.java | 28 +- .../XtensaPcodeUseropLibraryFactory.java | 66 +- .../TricorePcodeUseropLibraryFactory.java | 26 +- .../Test/DebuggerIntegrationTest/build.gradle | 2 + .../pcode/emu/demo/ModelingScriptTest.java | 211 +++ .../pcode/emu/AbstractPcodeEmulatorTest.java | 15 +- .../ghidra/pcode/emu/jit/AbstractJitTest.java | 46 +- .../jit/analysis/JitAllocationModelTest.java | 45 +- .../jit/analysis/JitControlFlowModelTest.java | 183 ++- .../jit/analysis/JitDataFlowModelTest.java | 133 +- .../emu/jit/analysis/JitOpUseModelTest.java | 64 +- .../analysis/JitReachabilityModelTest.java | 125 ++ .../emu/jit/analysis/JitTypeModelTest.java | 95 +- .../emu/jit/decode/JitPassageDecoderTest.java | 81 ++ .../decode/JitPassageDecoderTestAccess.java | 13 +- .../jit/gen/AbstractJitCodeGeneratorTest.java | 85 +- .../gen/AbstractToyJitCodeGeneratorTest.java | 47 +- .../jit/gen/FoldingJitCodeGeneratorTest.java | 311 +++++ ...oveUnusedOpsToyBEJitCodeGeneratorTest.java | 29 + ...eepUnusedOpsToyBEJitCodeGeneratorTest.java | 29 + .../GhidraClass/Debugger/B4-Modeling.html | 1219 +++++++++-------- .../GhidraClass/Debugger/B4-Modeling.md | 216 +-- .../ghidra_scripts/CustomLibraryScript.java | 3 +- .../InstallCustomLibraryScript.java | 2 +- .../ghidra_scripts/ModelingScript.java | 104 +- GhidraDocs/build.gradle | 14 +- 256 files changed, 11499 insertions(+), 6268 deletions(-) create mode 100644 Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbsEmuUnixFileHandle.java create mode 100644 Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFileHandle.java create mode 100644 Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/emu/linux/JitLinuxAmd64SyscallUseropLibraryTest.java create mode 100644 Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/emu/linux/JitLinuxX86SyscallUseropLibraryTest.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/analysis/JitReachabilityModel.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/CanDecode.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/decode/CollectExecutor.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/folding/FoldRevalidator.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/folding/FoldedArithmetic.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/folding/FoldedState.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/folding/FoldingExecutor.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/jit/folding/MaskedBytes.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/util/CountsPcodeOps.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/util/PcodeOpCounter.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SymbolsCache.java create mode 100644 Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/folding/AbstractFoldedArithmeticTest.java create mode 100644 Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/folding/BEFoldedArithmeticTest.java create mode 100644 Ghidra/Framework/Emulation/src/test/java/ghidra/pcode/emu/jit/folding/LEFoldedArithmeticTest.java create mode 100644 Ghidra/Test/DebuggerIntegrationTest/src/test/java/ghidra/pcode/emu/demo/ModelingScriptTest.java create mode 100644 Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/analysis/JitReachabilityModelTest.java create mode 100644 Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/decode/JitPassageDecoderTest.java create mode 100644 Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/FoldingJitCodeGeneratorTest.java create mode 100644 Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/KeepUnreachableBlocksRemoveUnusedOpsToyBEJitCodeGeneratorTest.java create mode 100644 Ghidra/Test/IntegrationTest/src/test.slow/java/ghidra/pcode/emu/jit/gen/RemoveUnreachableBlocksKeepUnusedOpsToyBEJitCodeGeneratorTest.java diff --git a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintEmuUnixFileSystem.java b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintEmuUnixFileSystem.java index a7e95c857f..c11eff3ce1 100644 --- a/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintEmuUnixFileSystem.java +++ b/Ghidra/Debug/TaintAnalysis/src/main/java/ghidra/pcode/emu/taint/lib/TaintEmuUnixFileSystem.java @@ -4,9 +4,9 @@ * 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. @@ -31,13 +31,13 @@ public class TaintEmuUnixFileSystem extends AbstractEmuUnixFileSystem { @Override - public long read(long offset, TaintVec buf, long fileSize) { + public int read(long offset, TaintVec buf, long fileSize) { buf.setEmpties(); return buf.length; } @Override - public long write(long offset, TaintVec buf, long curSize) { + public int write(long offset, TaintVec buf, long curSize) { return 0; // I don't care } @@ -57,13 +57,13 @@ public class TaintEmuUnixFileSystem extends AbstractEmuUnixFileSystem * This library is not currently accessible from the UI. It can be used with scripts by overriding a * taint emulator's userop library factory method. - * *

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

* TODO: Example scripts. */ @@ -51,12 +48,14 @@ public class TaintFileReadsLinuxAmd64SyscallLibrary super(machine, fs, program); } - @Override - public Pair unix_read(PcodeExecutorState> state, + @PcodeUserop + @EmuSyscall(value = "read", override = true) + public Pair unix_read( + @OpState PcodeExecutorState> state, Pair fd, Pair bufPtr, Pair count) { - Pair result = super.unix_read(state, fd, bufPtr, count); + Pair result = abstract_unix_read(state, fd, bufPtr, count); TaintVec taintResult = result.getRight(); // TODO: Some representation of a "min" function. For now, just mix everything diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java index 436a143567..99b75fb282 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java @@ -80,7 +80,7 @@ public class DebuggerEmuExampleScript extends GhidraScript implements FlatDebugg .getProjectData() .getRootFolder() .createFile("emu_example", program, monitor); - try (Transaction tx = program.openTransaction("Init")) { + try (Transaction _ = program.openTransaction("Init")) { AddressSpace space = program.getAddressFactory().getDefaultAddressSpace(); entry = space.getAddress(0x00400000); Address dataEntry = space.getAddress(0x00600000); @@ -167,7 +167,7 @@ public class DebuggerEmuExampleScript extends GhidraScript implements FlatDebugg */ TraceTimeManager time = trace.getTimeManager(); TraceSnapshot snapshot = time.getSnapshot(0, true); - try (Transaction tx = trace.openTransaction("Emulate")) { + try (Transaction _ = trace.openTransaction("Emulate")) { for (int i = 0; i < 10; i++) { println("Executing: " + thread.getCounter()); thread.stepInstruction(); diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/DemoPcodeUseropLibrary.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/DemoPcodeUseropLibrary.java index 1314f40d14..18bc4ed43e 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/DemoPcodeUseropLibrary.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/DemoPcodeUseropLibrary.java @@ -4,9 +4,9 @@ * 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. @@ -66,12 +66,12 @@ public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary * *

* 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. + * employ the {@link ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.OpExecutor} 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 executor the calling thread's executor * @param start the offset of the first character * @return the length of the string in bytes */ @@ -108,11 +108,9 @@ public class DemoPcodeUseropLibrary extends AnnotatedPcodeUseropLibrary /** * 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 diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/DemoSyscallLibrary.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/DemoSyscallLibrary.java index bca15ca36d..6387126d98 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/DemoSyscallLibrary.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/DemoSyscallLibrary.java @@ -4,9 +4,9 @@ * 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. @@ -24,28 +24,26 @@ import ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary; import ghidra.pcode.emu.sys.EmuSyscallLibrary; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeArithmetic.Purpose; -import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.SleighPcodeUseropDefinition.BuilderStage1; 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 conflicts among userops when several - * libraries are composed. - * + * methods. {@link EmuSyscallLibrary#emu_syscall} is the system call dispatcher, and it requires + * that each system call implement + * {@link ghidra.pcode.emu.sys.EmuSyscallLibrary.EmuSyscallDefinition}. System call libraries + * typically implement that interface by annotating p-code userops with + * {@link ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary.EmuSyscall}. This allows system + * calls to be implemented via Java callback or Sleigh. Conventionally, the Java method names of + * system calls should be platform_name. This is to prevent name conflicts among + * userops when several libraries are composed. *

* Stock implementations for a limited set of Linux system calls are provided for x86 and amd64 in * {@link EmuLinuxX86SyscallUseropLibrary} and {@link EmuLinuxAmd64SyscallUseropLibrary}, @@ -53,7 +51,6 @@ import ghidra.program.model.listing.Program; * 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. @@ -61,74 +58,21 @@ import ghidra.program.model.listing.Program; 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. Use the system call analyzer on your program to populate this space. The * program and its compiler spec are also used to derive (what it can of) the system call ABI. - * Notably, it applies the calling convention of the functions placed in syscall overlay. Those - * parts which cannot (yet) be derived from the program are instead implemented as abstract - * methods of this class, e.g., {@link #readSyscallNumber(PcodeExecutorStatePiece)} and - * {@link #handleError(PcodeExecutor, PcodeExecutionException)}. + * Notably, it applies the calling convention of the functions placed in the syscall overlay. * * @param machine the emulator * @param program the program being emulated + * @param script the script */ 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"); - } - } - - /** - * {@inheritDoc} - * - * 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(PcodeExecutorState state, Reason reason) { - return Utils.bytesToLong(state.getVar(regRAX, reason), 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; } /** @@ -152,31 +96,28 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary /** * 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 - * composed libraries. - * + * The {@link ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary.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 composed libraries. *

* 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 executor the emulator's underlying p-code executor * @param str a pointer to the start of the buffer * @param end a pointer to the end (exclusive) of the buffer + * @implNote Because we have concrete {@code byte[]}, we could use {@link 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. */ @PcodeUserop @EmuSyscall("write") public void demo_write(@OpExecutor PcodeExecutor executor, 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.toLong(str, Purpose.LOAD); long endLong = arithmetic.toLong(end, Purpose.OTHER); @@ -191,7 +132,8 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary /** * The nested class for syscalls implemented using Structured Sleigh. Note that no matter the - * implementation type, the Java method is annotated with {@link EmuSyscall}. We declare the + * implementation type, the Java method is annotated with + * {@link ghidra.pcode.emu.sys.AnnotatedEmuSyscallUseropLibrary.EmuSyscall}. We declare the * class public so that the annotation processor can access the methods. Alternatively, we could * override {@link #getMethodLookup()} to provide the processor private access. */ @@ -218,4 +160,13 @@ public class DemoSyscallLibrary extends AnnotatedEmuSyscallUseropLibrary write.call(str, end); } } + + // Finally, the actual syscall userop, which we implement in plain Sleigh. + + @PcodeUserop + public SleighPcodeUseropDefinition syscall(BuilderStage1 builder) { + return builder.params().body(_ -> """ + RAX = emu_syscall(RAX); + """).build(); + } } diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/EmuDeskCheckScript.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/EmuDeskCheckScript.java index 4559616de9..3c0a45b817 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/EmuDeskCheckScript.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/EmuDeskCheckScript.java @@ -32,6 +32,7 @@ import ghidra.pcode.emu.BytesPcodeThread; import ghidra.pcode.emu.PcodeEmulator; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.pcode.exec.trace.TraceEmulationIntegration.Writer; import ghidra.pcode.struct.StructuredSleigh; import ghidra.pcode.utils.Utils; @@ -169,8 +170,9 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI } }; - for (SleighPcodeUseropDefinition inject : new Injects().generate().values()) { - String source = inject.getBody(); + for (PcodeUseropDefinition inject : new Injects().generate().values()) { + String source = + inject instanceof SleighPcodeUseropDefinition sleigh ? sleigh.getBody() : "(java)"; println("Injecting " + inject.getName() + ":\n" + source); for (Symbol sym : currentProgram.getSymbolTable() .getExternalSymbols(inject.getName())) { @@ -190,9 +192,9 @@ public class EmuDeskCheckScript extends GhidraScript implements FlatDebuggerAPI schedule.execute(trace, emu, monitor); } - /////////////////////////////////////////// + // //////////////////////////////////////// // Configuration and support kruft below // - /////////////////////////////////////////// + // //////////////////////////////////////// public record Watch(String expression, TypeRec type, Settings settings) {} diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneStructuredSleighScript.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneStructuredSleighScript.java index 20bf2320f9..e5a72a3bca 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneStructuredSleighScript.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneStructuredSleighScript.java @@ -28,7 +28,7 @@ import java.util.stream.Collectors; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.script.GhidraScript; import ghidra.pcode.exec.FixedSleighPcodeUseropDefinition; -import ghidra.pcode.exec.SleighPcodeUseropDefinition; +import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; import ghidra.pcode.exec.SleighPcodeUseropDefinition.SignatureDef; import ghidra.pcode.struct.StructuredSleigh; import ghidra.program.model.lang.LanguageID; @@ -61,7 +61,7 @@ public class StandAloneStructuredSleighScript extends GhidraScript { */ language = (SleighLanguage) getLanguage(new LanguageID("DATA:BE:64:default")); - Map> ops = new LookupStructuredSleigh() { + Map> ops = new LookupStructuredSleigh() { /** * Add two in-memory vectors of 16 longs and store the result in memory * @@ -101,8 +101,8 @@ public class StandAloneStructuredSleighScript extends GhidraScript { /* * Now, dump the generated Sleigh source */ - for (SleighPcodeUseropDefinition userop : ops.values()) { - if (!(userop instanceof FixedSleighPcodeUseropDefinition fixed)) { + for (PcodeUseropDefinition userop : ops.values()) { + if (!(userop instanceof FixedSleighPcodeUseropDefinition fixed)) { println("WARN: Unexpected userop type for " + userop.getName()); continue; } diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneSyscallEmuExampleScript.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneSyscallEmuExampleScript.java index ad03ea3fba..72958f0e85 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneSyscallEmuExampleScript.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneSyscallEmuExampleScript.java @@ -4,9 +4,9 @@ * 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. @@ -70,7 +70,7 @@ public class StandAloneSyscallEmuExampleScript extends GhidraScript { program = new ProgramDB("syscall_example", language, language.getCompilerSpecByID(new CompilerSpecID("gcc")), this); - try (Transaction tx = program.openTransaction("Init")) { + try (Transaction _ = program.openTransaction("Init")) { AddressSpace space = program.getAddressFactory().getDefaultAddressSpace(); entry = space.getAddress(0x00400000); Address dataEntry = space.getAddress(0x00600000); diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java index c3f4138623..95a89923ba 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/linux/EmuLinuxAmd64SyscallUseropLibrary.java @@ -4,9 +4,9 @@ * 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. @@ -24,13 +24,10 @@ import ghidra.framework.Application; import ghidra.pcode.emu.PcodeMachine; import ghidra.pcode.emu.unix.EmuUnixFileSystem; import ghidra.pcode.emu.unix.EmuUnixUser; -import ghidra.pcode.exec.PcodeArithmetic.Purpose; -import ghidra.pcode.exec.PcodeExecutor; -import ghidra.pcode.exec.PcodeExecutorState; -import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.SleighPcodeUseropDefinition; +import ghidra.pcode.exec.SleighPcodeUseropDefinition.BuilderStage1; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.FileDataTypeManager; -import ghidra.program.model.lang.Register; import ghidra.program.model.listing.Program; /** @@ -40,8 +37,6 @@ import ghidra.program.model.listing.Program; */ public class EmuLinuxAmd64SyscallUseropLibrary extends AbstractEmuLinuxSyscallUseropLibrary { - protected final Register regRAX; - protected FileDataTypeManager clib64; /** @@ -55,7 +50,6 @@ public class EmuLinuxAmd64SyscallUseropLibrary extends AbstractEmuLinuxSyscal public EmuLinuxAmd64SyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, Program program) { super(machine, fs, program); - regRAX = machine.getLanguage().getRegister("RAX"); } /** @@ -70,7 +64,6 @@ public class EmuLinuxAmd64SyscallUseropLibrary extends AbstractEmuLinuxSyscal public EmuLinuxAmd64SyscallUseropLibrary(PcodeMachine machine, EmuUnixFileSystem fs, Program program, EmuUnixUser user) { super(machine, fs, program, user); - regRAX = machine.getLanguage().getRegister("RAX"); } @Override @@ -91,16 +84,10 @@ public class EmuLinuxAmd64SyscallUseropLibrary extends AbstractEmuLinuxSyscal clib64.close(); } - @Override - public long readSyscallNumber(PcodeExecutorState state, Reason reason) { - return machine.getArithmetic().toLong(state.getVar(regRAX, reason), Purpose.OTHER); - } - - @Override - protected boolean returnErrno(PcodeExecutor executor, int errno) { - executor.getState() - .setVar(regRAX, - executor.getArithmetic().fromConst(-errno, regRAX.getMinimumByteSize())); - return true; + @PcodeUserop + public SleighPcodeUseropDefinition syscall(BuilderStage1 builder) { + return builder.params().body(_ -> """ + RAX = emu_syscall(RAX); + """).build(); } } diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java index 711f19e5a2..b80347bbb4 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/linux/EmuLinuxX86SyscallUseropLibrary.java @@ -4,9 +4,9 @@ * 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. @@ -20,19 +20,18 @@ import java.util.Collection; import java.util.List; import generic.jar.ResourceFile; +import ghidra.app.util.PseudoInstruction; import ghidra.framework.Application; -import ghidra.pcode.emu.DefaultPcodeThread.PcodeThreadExecutor; import ghidra.pcode.emu.PcodeMachine; +import ghidra.pcode.emu.jit.decode.CanDecode; import ghidra.pcode.emu.unix.EmuUnixFileSystem; import ghidra.pcode.emu.unix.EmuUnixUser; import ghidra.pcode.exec.*; -import ghidra.pcode.exec.PcodeArithmetic.Purpose; -import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.program.model.address.Address; import ghidra.program.model.data.DataTypeManager; import ghidra.program.model.data.FileDataTypeManager; -import ghidra.program.model.lang.Register; import ghidra.program.model.listing.Program; -import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; /** * A system call library simulating Linux for x86 (32-bit) @@ -40,8 +39,6 @@ import ghidra.program.model.pcode.PcodeOp; * @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; @@ -70,8 +67,6 @@ public class EmuLinuxX86SyscallUseropLibrary extends AbstractEmuLinuxSyscallU 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 @@ -92,45 +87,40 @@ public class EmuLinuxX86SyscallUseropLibrary extends AbstractEmuLinuxSyscallU clib32.close(); } - @Override - public long readSyscallNumber(PcodeExecutorState state, Reason reason) { - return machine.getArithmetic().toLong(state.getVar(regEAX, reason), Purpose.OTHER); - } - - @Override - protected boolean returnErrno(PcodeExecutor executor, int errno) { - executor.getState() - .setVar(regEAX, - executor.getArithmetic().fromConst(-errno, regEAX.getMinimumByteSize())); - return true; - } - /** * Implement this to detect and interpret the {@code INT 0x80} instruction as the syscall * convention * * @param executor to receive the executor * @param library to receive the userop library, presumably replete with syscalls - * @param number the interrupt number - * @return the address of the fall-through, to hack the {@link PcodeOp#CALLIND} + * @param out the output varnode + * @param intNo the interrupt number */ - @PcodeUserop - public T swi(@OpExecutor PcodeExecutor executor, @OpLibrary PcodeUseropLibrary library, - T number) { - PcodeArithmetic arithmetic = executor.getArithmetic(); - long intNo = arithmetic.toLong(number, Purpose.OTHER); + @PcodeUserop(canInline = true) + public void swi(@OpExecutor PcodeExecutor executor, @OpLibrary PcodeUseropLibrary library, + @OpOutput Varnode out, int intNo) { + // A CALLIND follows to the return of swi().... OK. + // We'll just cause that to "fall through" instead + // Thus, we need the instruction, and we must compile invocation-specific p-code 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, executor.getReason()); - PcodeThreadExecutor te = (PcodeThreadExecutor) executor; - int pcSize = regEIP.getNumBytes(); - int iLen = te.getThread().getInstruction().getLength(); - next = arithmetic.binaryOp(PcodeOp.INT_ADD, pcSize, pcSize, next, pcSize, - arithmetic.fromConst(iLen, pcSize)); - syscall(executor, library); - return next; + if (!(executor instanceof CanDecode decoder)) { + throw new PcodeExecutionException( + "Cannot interpret swi(0x80) without the instruction decoder"); + } + PseudoInstruction instruction = decoder.decodeInstruction(); + Address next = instruction.getAddress().add(instruction.getLength()); + PcodeProgram prog = + SleighProgramCompiler.compileUserop(executor.getLanguage(), "swi", + List.of(SleighPcodeUseropDefinition.OUT_SYMBOL_NAME), """ + EAX = emu_syscall(EAX); + %s = 0x%x; + """.formatted( + SleighPcodeUseropDefinition.OUT_SYMBOL_NAME, next.getOffset()), + library, List.of(out)); + executor.execute(prog, library); + } + else { + throw new PcodeExecutionException("Unknown interrupt: 0x%x".formatted(intNo)); } - throw new PcodeExecutionException("Unknown interrupt: 0x" + Long.toString(intNo, 16)); } } diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java index 6a9aa07e73..b8488358ed 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/AnnotatedEmuSyscallUseropLibrary.java @@ -35,7 +35,6 @@ 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 @@ -55,9 +54,38 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode return AnnotationUtilities.collectAnnotatedMethods(EmuSyscall.class, cls); } + private static Method chooseOrThrow(Method m1, Method m2) { + Class cls1 = m1.getDeclaringClass(); + Class cls2 = m2.getDeclaringClass(); + if (cls1 != cls2) { + EmuSyscall an1 = m1.getAnnotation(EmuSyscall.class); + EmuSyscall an2 = m2.getAnnotation(EmuSyscall.class); + if (cls1.isAssignableFrom(cls2) && an2.override()) { + return m2; + } + if (cls2.isAssignableFrom(cls1) && an1.override()) { + return m1; + } + } + throw new IllegalArgumentException("Duplicate @" + + EmuSyscall.class.getSimpleName() + " annotated methods with name " + m1.getName()); + } + + private static Map resolveOverrides(Set methods) { + Map result = new HashMap<>(); + for (Method method : methods) { + String name = method.getName(); + Method exists = result.get(name); + if (exists != null) { + method = chooseOrThrow(exists, method); + } + result.put(name, method); + } + return result; + } + /** * 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 ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.PcodeUserop @PcodeUserop}. @@ -65,11 +93,25 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface EmuSyscall { + /** + * The name of the syscall, which must match the actual name given in the syscall map. + * + * @return the name + * @see EmuSyscallLibrary#loadSyscallNumberMap(String) + */ String value(); - } - private final SyscallPcodeUseropDefinition syscallUserop = - new SyscallPcodeUseropDefinition<>(this); + /** + * Set to indicate this name overrides the same name inherited from any + * superclass/interface. + *

+ * If names collide from the same class, or that from the extension class does not have this + * flag, an error occurs at library construction. + * + * @return true to allow overrides. + */ + boolean override() default false; + } protected final PcodeMachine machine; protected final CompilerSpec cSpec; @@ -81,7 +123,11 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode protected final Collection additionalArchives; /** - * Construct a new library including the "syscall" userop + * Construct a new library including the "emu_syscall" userop + *

+ * Note that the final library must export the system call entry point. Often, this is the + * "syscall" userop. That userop must read the system call number, pass it as an argument to + * "emu_syscall," and store the result according to the ABI of the target platform. * * @param machine the machine using this library * @param program a program from which to derive syscall configuration, conventions, etc. @@ -121,12 +167,12 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode /** * Export a userop as a system call * - * @param number the opIndex assigned to the userop + * @param number the syscall number * @param opdef the userop * @param convention the syscall calling convention for the emulated platform * @return the syscall definition */ - public UseropEmuSyscallDefinition newBoundSyscall(long number, + protected UseropEmuSyscallDefinition newBoundSyscall(long number, PcodeUseropDefinition opdef, PrototypeModel convention) { return new UseropEmuSyscallDefinition<>(number, opdef, program, convention, dtMachineWord); } @@ -136,8 +182,8 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode new DualHashBidiMap<>(EmuSyscallLibrary.loadSyscallNumberMap(program)); Map mapConventions = EmuSyscallLibrary.loadSyscallConventionMap(program); - Set methods = collectSyscalls(cls); - for (Method m : methods) { + Map methods = resolveOverrides(collectSyscalls(cls)); + for (Method m : methods.values()) { String name = m.getAnnotation(EmuSyscall.class).value(); Long number = mapNames.getKey(name); if (number == null) { @@ -151,12 +197,7 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode " must also be a p-code userop"); } PrototypeModel convention = mapConventions.get(number); - EmuSyscallDefinition existed = - syscallMap.put(number, newBoundSyscall(number, opdef, convention)); - if (existed != null) { - throw new IllegalArgumentException("Duplicate @" + - EmuSyscall.class.getSimpleName() + " annotated methods with name " + name); - } + syscallMap.put(number, newBoundSyscall(number, opdef, convention)); } } @@ -164,11 +205,6 @@ public abstract class AnnotatedEmuSyscallUseropLibrary extends AnnotatedPcode mapAndBindSyscalls(this.getClass()); } - @Override - public PcodeUseropDefinition getSyscallUserop() { - return syscallUserop; - } - @Override public Map> getSyscalls() { return syscallMap; diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/BytesEmuFileContents.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/BytesEmuFileContents.java index 3d8b891227..81cd3a54a8 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/BytesEmuFileContents.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/BytesEmuFileContents.java @@ -4,9 +4,9 @@ * 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. @@ -17,7 +17,6 @@ package ghidra.pcode.emu.sys; /** * A concrete in-memory bytes store for simulated file contents - * *

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

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

* TODO: Actually, a better idea might be to introduce an address factory with custom spaces into * the emulator. Then a library/file could just create an address space and use the state to store @@ -39,9 +37,9 @@ public interface EmuFileContents { * @param offset the offset in the file to read * @param buf the destination buffer, whose size must be known * @param fileSize the size of the file - * @return the number of bytes (not necessarily concrete) read + * @return the number of bytes read */ - long read(long offset, T buf, long fileSize); + int read(long offset, T buf, long fileSize); /** * Write values from the given buffer into the file @@ -49,13 +47,12 @@ public interface EmuFileContents { * @param offset the offset in the file to write * @param buf the source buffer, whose size must be known * @param curSize the current size of the file - * @return the number of bytes (not necessarily concrete) written + * @return the number of bytes written */ - long write(long offset, T buf, long curSize); + int write(long offset, T buf, long curSize); /** * Erase the contents - * *

* Note that the file's size will be set to 0, so actual erasure of the contents may not be * necessary, but if the contents are expensive to store, they ought to be disposed. diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java index d4bde08d83..2bc964e439 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuProcessExitedException.java @@ -4,9 +4,9 @@ * 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. @@ -30,7 +30,7 @@ import ghidra.pcode.exec.PcodeArithmetic.Purpose; public class EmuProcessExitedException extends EmuSystemException { /** - * Attempt to concretize a value and convert it to hex + * Attempt to concretize a value and convert it to decimal * * @param the type of the status * @param arithmetic the arithmetic to operate on the value @@ -51,20 +51,29 @@ public class EmuProcessExitedException extends EmuSystemException { /** * 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 + * @param status the status code */ public EmuProcessExitedException(PcodeArithmetic arithmetic, T status) { super("Process exited with status " + tryConcereteToString(arithmetic, status)); this.status = status; } + /** + * Construct a process-exited exception with the given status code + * + * @param status the status code + */ + public EmuProcessExitedException(long status) { + super("Process exited with status %d".formatted(status)); + this.status = status; + } + /** * Get the status code as a {@code T} of the throwing machine * diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java index f6a6268091..2577f71b00 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/EmuSyscallLibrary.java @@ -16,16 +16,17 @@ package ghidra.pcode.emu.sys; import java.io.*; -import java.lang.reflect.Method; -import java.util.*; +import java.util.HashMap; +import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import generic.jar.ResourceFile; import ghidra.framework.Application; +import ghidra.pcode.emu.jit.folding.MaskedBytes; import ghidra.pcode.exec.*; import ghidra.pcode.exec.AnnotatedPcodeUseropLibrary.*; -import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.listing.Function; @@ -36,16 +37,15 @@ 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, PcodeUseropLibrary)}, 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 userops as system calls. + * {@link #emu_syscall(PcodeExecutor, PcodeUseropLibrary, PcodeOp, Varnode)}, 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 userops as system calls. * * @param the type of data processed by the system calls, typically {@code byte[]} */ @@ -143,77 +143,6 @@ public interface EmuSyscallLibrary extends PcodeUseropLibrary { .collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getCallingConvention())); } - /** - * The {@link EmuSyscallLibrary#syscall(PcodeExecutor, PcodeUseropLibrary)} 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, - PcodeOp op, Varnode outVar, List inVars) { - syslib.syscall(executor, library); - } - - @Override - public boolean isFunctional() { - return false; - } - - @Override - public boolean hasSideEffects() { - return true; - } - - @Override - public boolean modifiesContext() { - return false; - } - - @Override - public boolean canInlinePcode() { - return false; - } - - @Override - public Class getOutputType() { - return void.class; - } - - @Override - public PcodeUseropLibrary getDefiningLibrary() { - return syslib; - } - - @Override - public Method getJavaMethod() { - try { - return syslib.getClass() - .getMethod("syscall", PcodeExecutor.class, PcodeUseropLibrary.class); - } - catch (NoSuchMethodException | SecurityException e) { - throw new AssertionError(e); - } - } - } - /** * The definition of a system call * @@ -230,83 +159,86 @@ public interface EmuSyscallLibrary extends PcodeUseropLibrary { } /** - * In case this is not an {@link AnnotatedEmuSyscallUseropLibrary} or - * {@link AnnotatedPcodeUseropLibrary}, get the definition of the "syscall" userop for inclusion - * in the {@link PcodeUseropLibrary}. - * + * A Java-callback implementation of "emu_syscall" that reads the syscall number and dispatches + * to the appropriate system call implementation at run time. *

- * 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 - * @param reason the reason for reading state, probably {@link Reason#EXECUTE_READ}, but should - * be taken from the executor - * @return the system call number - */ - long readSyscallNumber(PcodeExecutorState state, Reason reason); - - /** - * 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(PcodeExecutorState, Reason)}, retrieve the relevant system call - * definition, and invoke it. + * This is the normal behavior for the interpretation-based p-code emulator anyway. However, for + * an execution engine that attempts to resolve system calls "just in time," it may be necessary + * to explicitly defer to this implementation to prevent any further attempt to resolve the + * system call, when it becomes impossible to do so. * * @param executor the executor - * @param library the library + * @param library the p-code userop library (often including exported system calls) + * @param syscallNumber the system call number */ - @PcodeUserop - default void syscall(@OpExecutor PcodeExecutor executor, - @OpLibrary PcodeUseropLibrary library) { - long syscallNumber = readSyscallNumber(executor.getState(), executor.getReason()); + @PcodeUserop(canInline = false) + default void emu_rt_syscall(@OpExecutor PcodeExecutor executor, + @OpLibrary PcodeUseropLibrary library, long syscallNumber) { EmuSyscallDefinition syscall = getSyscalls().get(syscallNumber); if (syscall == null) { throw new EmuInvalidSystemCallException(syscallNumber); } + syscall.invoke(executor, library); + } + + /** + * The Sleigh code to defer to run-time system call resolution + */ + SleighPcodeUseropDefinition FALLBACK_EMU_SYSCALL = + SleighPcodeUseropDefinition.FACTORY.define("emu_syscall") + .params("number") + .body(_ -> """ + __op_output = emu_rt_syscall(number); + """) + .build(); + + /** + * 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. The extended or composed library must provide the "syscall" and/or other + * relevant userops defined in the Sleigh. That "syscall" userop should invoke this userop, + * passing the system call number then place the result where it belongs according to the target + * ABI. + * + * @param executor the executor + * @param library the library + * @param op the callother userop + * @param syscallNumber the varnode containing the syscall number + * @implNote At run time, the logic here will do exactly as it says. For the + * interpretation-based emulator, nothing special happens. We read the system call + * number and defer to {@link #emu_rt_syscall}, which looks it up and executes it. The + * try-catch for {@link ConcretionError} appears totally useless. + *

+ * For the JIT-accelerated emulator, things are more interesting. Because + * {@link PcodeUserop#canInline()} is set here, we receive this callback during the + * decode phase of the translation. We read the system call number and defer to + * {@link #emu_rt_syscall} as before. (Note that {@link PcodeUserop#canInline()} being + * false does not prevent us from invoking it from Java, even though the engine is + * currently trying to inline us.) The try-catch thus protects the attempt to + * concretize the syscall number. During decode, {@code T := }{@link MaskedBytes}, so + * if the syscall turns out to be a constant (which it often does), then we can + * dispatch it at decode time. That syscall (or at least the logic that reads + * arguments and stores the result) can thus be inlined. If concretization fails, then + * we fall back to invoking {#emu_rt_syscall} at run time. + */ + @PcodeUserop(canInline = true) + default void emu_syscall(@OpExecutor PcodeExecutor executor, + @OpLibrary PcodeUseropLibrary library, @OpOp PcodeOp op, Varnode syscallNumber) { + T tSyscallNo = executor.getState().getVar(syscallNumber, executor.getReason()); try { - syscall.invoke(executor, library); + long lSyscallNo = executor.getArithmetic().toLong(tSyscallNo, Purpose.OTHER); + emu_rt_syscall(executor, library, lSyscallNo); } - catch (PcodeExecutionException e) { - if (!handleError(executor, e)) { - throw e; - } + catch (ConcretionError e) { + FALLBACK_EMU_SYSCALL. cast().execute(executor, library, op); } } /** * Get the map of syscalls by number - * *

- * Note this method will be invoked for every emulated syscall, so it should be a simple + * Note this method will be invoked for every interpreted 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 diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/PairedEmuFileContents.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/PairedEmuFileContents.java index ed4ec315f7..a5cea4b51c 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/PairedEmuFileContents.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/PairedEmuFileContents.java @@ -4,9 +4,9 @@ * 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. @@ -41,15 +41,15 @@ public class PairedEmuFileContents implements EmuFileContents> } @Override - public long read(long offset, Pair buf, long fileSize) { - long result = left.read(offset, buf.getLeft(), fileSize); + public int read(long offset, Pair buf, long fileSize) { + int result = left.read(offset, buf.getLeft(), fileSize); right.read(offset, buf.getRight(), fileSize); return result; } @Override - public long write(long offset, Pair buf, long curSize) { - long result = left.write(offset, buf.getLeft(), curSize); + public int write(long offset, Pair buf, long curSize) { + int result = left.write(offset, buf.getLeft(), curSize); right.write(offset, buf.getRight(), curSize); return result; } diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java index 27e707da3e..1d83a76e45 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/sys/UseropEmuSyscallDefinition.java @@ -15,15 +15,18 @@ */ package ghidra.pcode.emu.sys; -import java.util.Arrays; import java.util.List; +import java.util.stream.Stream; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.lifecycle.Unfinished; import ghidra.pcode.emu.sys.EmuSyscallLibrary.EmuSyscallDefinition; import ghidra.pcode.exec.*; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; +import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropSymbolMap; import ghidra.program.model.address.Address; import ghidra.program.model.data.DataType; +import ghidra.program.model.data.VoidDataType; import ghidra.program.model.lang.PrototypeModel; import ghidra.program.model.listing.Program; import ghidra.program.model.listing.VariableStorage; @@ -31,7 +34,6 @@ import ghidra.program.model.pcode.*; /** * 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 @@ -57,7 +59,7 @@ public class UseropEmuSyscallDefinition implements EmuSyscallDefinition { return dtPointer; } - protected final PcodeOp op; // fabricated for analyses that provide originating op info + protected PcodeOp op; // fabricate the CALLOTHER, so the executor can choose what to do protected final PcodeUseropDefinition opdef; protected final List inVars; protected final Varnode outVar; @@ -83,43 +85,52 @@ public class UseropEmuSyscallDefinition implements EmuSyscallDefinition { " cannot be used as a syscall"); } DataType[] locs = new DataType[inputCount + 1]; - for (int i = 0; i < locs.length; i++) { + locs[0] = opdef.getOutputType() == void.class ? VoidDataType.dataType : dtMachineWord; + for (int i = 1; i < locs.length; i++) { locs[i] = dtMachineWord; } VariableStorage[] vss = convention.getStorageLocations(program, locs, false, false); outVar = getSingleVnStorage(vss[0]); - inVars = Arrays.asList(new Varnode[inputCount]); - Varnode[] opIns = new Varnode[inputCount + 1]; - opIns[0] = new Varnode(program.getAddressFactory().getConstantAddress(number), 4); - for (int i = 0; i < inputCount; i++) { - Varnode vnIn = getSingleVnStorage(vss[i + 1]); - inVars.set(i, vnIn); - opIns[i + 1] = vnIn; - } - - op = new PcodeOp(new SequenceNumber(Address.NO_ADDRESS, 0), PcodeOp.CALLOTHER, opIns, - outVar); + inVars = Stream.of(vss).skip(1).map(this::getSingleVnStorage).toList(); } /** - * Assert variable storage is a single varnode, and get that varnode + * Assert variable storage is empty or a single varnode, and get that varnode * * @param vs the storage - * @return the single varnode + * @return the single varnode, or null if empty */ protected Varnode getSingleVnStorage(VariableStorage vs) { Varnode[] vns = vs.getVarnodes(); - if (vns.length != 1) { - Unfinished.TODO(); + return switch (vns.length) { + case 0 -> null; + case 1 -> vns[0]; + default -> Unfinished.TODO(); + }; + } + + PcodeOp constructOp(SleighLanguage language, PcodeUseropLibrary library) { + PcodeUseropSymbolMap userops = library.getSymbols(language); + int opNumber = userops.getUseropIndex(opdef.getName()); + if (opNumber == -1) { + throw new AssertionError(); } - return vns[0]; + Varnode[] opIns = Stream.concat( + Stream.of(new Varnode(language.getAddressFactory().getConstantAddress(opNumber), 4)), + inVars.stream()).toArray(Varnode[]::new); + return new PcodeOp(new SequenceNumber(Address.NO_ADDRESS, 0), PcodeOp.CALLOTHER, opIns, + outVar); } @Override public void invoke(PcodeExecutor executor, PcodeUseropLibrary library) { + SleighLanguage language = executor.getLanguage(); + if (op == null) { + op = constructOp(language, library); + } try { - opdef.execute(executor, library, op, outVar, inVars); + executor.execute(List.of(op), library); } catch (PcodeExecutionException e) { throw e; diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbsEmuUnixFileHandle.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbsEmuUnixFileHandle.java new file mode 100644 index 0000000000..765eefc7d0 --- /dev/null +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbsEmuUnixFileHandle.java @@ -0,0 +1,111 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.emu.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.Purpose; +import ghidra.program.model.lang.CompilerSpec; +import ghidra.program.model.pcode.PcodeOp; + +/** + * A file descriptor for non-concrete types + * + * @param the type + */ +public class AbsEmuUnixFileHandle extends AbstractEmuUnixFileHandle { + private T offset; + + public AbsEmuUnixFileHandle(PcodeMachine machine, CompilerSpec cSpec, EmuUnixFile file, + Set flags, EmuUnixUser user) { + super(machine, cSpec, file, flags, user); + long off = flags.contains(OpenFlag.O_APPEND) ? file.getStat().st_size : 0; + this.offset = arithmetic.fromConst(off, offsetBytes); + } + + /** + * Advance the handle's offset (negative to rewind) + * + * @param len the number of bytes to advance + */ + protected void advanceOffset(T len) { + int sizeofLen = (int) arithmetic.sizeOf(len); + offset = + arithmetic.binaryOp(PcodeOp.INT_ADD, offsetBytes, offsetBytes, offset, sizeofLen, len); + } + + protected boolean isPositive(T len) { + int sizeofLen = (int) arithmetic.sizeOf(len); + return arithmetic.isTrue( + arithmetic.binaryOp(PcodeOp.INT_SLESS, 1, sizeofLen, arithmetic.fromConst(0, sizeofLen), + sizeofLen, len), + Purpose.OTHER); + } + + @Override + public T getAbstractOffset() { + return offset; + } + + @Override + public long getOffset() { + return arithmetic.toLong(offset, Purpose.OTHER); + } + + @Override + public void seek(T offset) throws EmuIOException { + // TODO: Bounds check? + this.offset = offset; + } + + @Override + public void seek(long offset) throws EmuIOException { + seek(arithmetic.fromConst(offset, offsetBytes)); + } + + @Override + public T readAbstract(T buf) throws EmuIOException { + checkReadable(); + T len = file.read(arithmetic, offset, buf); + if (isPositive(len)) { + advanceOffset(len); + } + return len; + } + + @Override + public int read(T buf) throws EmuIOException { + return (int) arithmetic.toLong(readAbstract(buf), Purpose.OTHER); + } + + @Override + public T writeAbstract(T buf) throws EmuIOException { + checkWritable(); + T len = file.write(arithmetic, offset, buf); + if (isPositive(len)) { + advanceOffset(len); + } + return len; + } + + @Override + public int write(T buf) throws EmuIOException { + return (int) arithmetic.toLong(writeAbstract(buf), Purpose.OTHER); + } +} diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java index bc34cfa28f..47cadd1bdd 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFile.java @@ -4,9 +4,9 @@ * 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. @@ -22,7 +22,6 @@ import ghidra.util.MathUtilities; /** * 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. @@ -37,7 +36,6 @@ public abstract class AbstractEmuUnixFile implements EmuUnixFile { /** * 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. @@ -79,18 +77,31 @@ public abstract class AbstractEmuUnixFile implements EmuUnixFile { return stat; } + @Override + public int read(long offset, T buf) { + return contents.read(offset, buf, stat.st_size); + } + @Override public T read(PcodeArithmetic arithmetic, T offset, T buf) { long off = arithmetic.toLong(offset, Purpose.OTHER); - long len = contents.read(off, buf, stat.st_size); + long len = read(off, buf); // Assure signed extension return arithmetic.fromConst(len, (int) arithmetic.sizeOf(offset)); } + @Override + public int write(long offset, T buf) { + int len = contents.write(offset, buf, stat.st_size); + if (len > 0) { + stat.st_size = MathUtilities.unsignedMax(stat.st_size, offset + len); + } + return len; + } + @Override public T write(PcodeArithmetic arithmetic, T offset, T buf) { long off = arithmetic.toLong(offset, Purpose.OTHER); - long len = contents.write(off, buf, stat.st_size); - stat.st_size = MathUtilities.unsignedMax(stat.st_size, off + len); + long len = write(off, buf); // Assure signed extension return arithmetic.fromConst(len, (int) arithmetic.sizeOf(offset)); } diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFileHandle.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFileHandle.java new file mode 100644 index 0000000000..f7a968fb72 --- /dev/null +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixFileHandle.java @@ -0,0 +1,94 @@ +/* ### + * 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.program.model.lang.CompilerSpec; + +/** + * Abstract for a file descriptor associated with a file on a simulated UNIX file system + * + * @param the type + */ +public abstract class AbstractEmuUnixFileHandle 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; + + /** + * Construct a new handle on the given file + * + * @see AbstractEmuUnixSyscallUseropLibrary#createHandle(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 AbstractEmuUnixFileHandle(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 + } + + /** + * Get the file opened to this handle + * + * @return the file + */ + public EmuUnixFile getFile() { + return file; + } + + /** + * Check if the file is readable, throwing {@link EmuIOException} if not + */ + 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"); + } + } + + @Override + public EmuUnixFileStat stat() { + return file.getStat(); + } + + @Override + public void close() { + // TODO: Let the file know a handle was closed? + } +} diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java index 570c1d8abc..da4cf947b1 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractEmuUnixSyscallUseropLibrary.java @@ -22,8 +22,9 @@ 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.pcode.exec.PcodeArithmetic; import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorState; import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; import ghidra.program.model.address.AddressSpace; import ghidra.program.model.data.StringDataInstance; @@ -33,11 +34,9 @@ import ghidra.program.model.mem.MemBuffer; /** * An abstract library of UNIX system calls, suitable for use with any processor - * *

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

* TODO: The rest of the system calls common to UNIX. * @@ -105,7 +104,6 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary /** * Claim the lowest available file descriptor number for the given descriptor object - * *

* The descriptor will be added to the descriptor table for the claimed number * @@ -206,121 +204,115 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary } } - /** - * Place the errno into the machine as expected by the simulated platform's ABI - * - * @param executor the executor for the thread running this system call - * @param errno the error number - * @return true if the errno was successfully placed - */ - protected abstract boolean returnErrno(PcodeExecutor executor, int errno); - - @Override - 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; - } - /** * The UNIX {@code exit} system call - * *

* This just throws an exception, which the overall simulator or script should catch. * * @param status the status code - * @return never * @throws EmuProcessExitedException always */ - @PcodeUserop + @PcodeUserop(functional = true) @EmuSyscall("exit") - public T unix_exit(T status) { - throw new EmuProcessExitedException(machine.getArithmetic(), status); + public void unix_exit(long status) { + throw new EmuProcessExitedException(status); + } + + protected T abstract_unix_read(PcodeExecutorState state, T fd, T bufPtr, T count) { + PcodeArithmetic arithmetic = machine.getArithmetic(); + try { + int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER); + EmuUnixFileDescriptor desc = findFd(ifd); + AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); + int icount = (int) arithmetic.toLong(count, Purpose.OTHER); + T buf = arithmetic.fromConst(0, icount); + int result = desc.read(buf); + machine.getSharedState().setVar(space, bufPtr, result, true, buf); + return arithmetic.fromConst((long) result, intSize); + } + catch (EmuUnixException e) { + // TODO: Does this generalize to all UNIX, or just Linux x86/amd64? + return arithmetic.fromConst((long) -e.getErrno(), intSize); + } } /** * The UNIX {@code read} system call * - * @param state to receive the thread's state * @param fd the file descriptor * @param bufPtr the pointer to the buffer to receive the data * @param count the number of bytes to read * @return the number of bytes successfully read */ - @PcodeUserop + @PcodeUserop(functional = true, signed = true) @EmuSyscall("read") - public T unix_read(@OpState PcodeExecutorState state, T fd, T bufPtr, T count) { + public int unix_read(int fd, long bufPtr, int count) { PcodeArithmetic arithmetic = machine.getArithmetic(); - int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER); - EmuUnixFileDescriptor desc = findFd(ifd); - AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); - // TODO: Not ideal to require concrete size, but gets unwieldy to leave it abstract - int size = (int) arithmetic.toLong(count, Purpose.OTHER); - T buf = arithmetic.fromConst(0, size); - T result = desc.read(buf); - int iresult = (int) arithmetic.toLong(result, Purpose.OTHER); - state.setVar(space, bufPtr, iresult, true, buf); - return result; + try { + EmuUnixFileDescriptor desc = findFd(fd); + AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); + T buf = arithmetic.fromConst(0, count); + int result = desc.read(buf); + machine.getSharedState().setVar(space, bufPtr, result, true, buf); + return result; + } + catch (EmuUnixException e) { + // TODO: Does this generalize to all UNIX, or just Linux x86/amd64? + return -e.getErrno(); + } } /** * The UNIX {@code write} system call * - * @param state to receive the thread's state * @param fd the file descriptor * @param bufPtr the pointer to the buffer of data to write * @param count the number of bytes to write * @return the number of bytes successfully written */ - @PcodeUserop + @PcodeUserop(functional = true, signed = true) @EmuSyscall("write") - public T unix_write(@OpState PcodeExecutorState state, T fd, T bufPtr, T count) { - PcodeArithmetic arithmetic = machine.getArithmetic(); - int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER); - EmuUnixFileDescriptor desc = findFd(ifd); - AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); - // TODO: Not ideal to require concrete size. What are the alternatives, though? - // TODO: size should actually be long (size_t) - int size = (int) arithmetic.toLong(count, Purpose.OTHER); - T buf = state.getVar(space, bufPtr, size, true, Reason.EXECUTE_READ); - // TODO: Write back into state? "write" shouldn't touch the buffer.... - return desc.write(buf); + public int unix_write(int fd, long bufPtr, int count) { + try { + EmuUnixFileDescriptor desc = findFd(fd); + AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); + T buf = + machine.getSharedState().getVar(space, bufPtr, count, true, Reason.EXECUTE_READ); + return desc.write(buf); + } + catch (EmuUnixException e) { + return -e.getErrno(); + } } /** * The UNIX {@code open} system call * - * @param state to receive the thread's state * @param pathnamePtr the file's path (pointer to character string) * @param flags the flags * @param mode the mode * @return the file descriptor */ - @PcodeUserop + @PcodeUserop(functional = true, signed = true) @EmuSyscall("open") - public T unix_open(@OpState PcodeExecutorState state, T pathnamePtr, T flags, T mode) { - PcodeArithmetic arithmetic = machine.getArithmetic(); - int iflags = (int) arithmetic.toLong(flags, Purpose.OTHER); - int imode = (int) arithmetic.toLong(mode, Purpose.OTHER); - long pathnameOff = arithmetic.toLong(pathnamePtr, Purpose.OTHER); - AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); - - SettingsImpl settings = new SettingsImpl(); - MemBuffer buffer = state.getConcreteBuffer(space.getAddress(pathnameOff), Purpose.OTHER); - 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); + public int unix_open(long pathnamePtr, int flags, int mode) { + try { + AddressSpace space = machine.getLanguage().getAddressFactory().getDefaultAddressSpace(); + SettingsImpl settings = new SettingsImpl(); + MemBuffer buffer = machine.getSharedState() + .getConcreteBuffer(space.getAddress(pathnamePtr), Purpose.OTHER); + 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(flags), user, mode); + return claimFd(createHandle(file, flags)); + } + catch (EmuUnixException e) { + return -e.getErrno(); + } } /** @@ -329,30 +321,32 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary * @param fd the file descriptor * @return 0 for success */ - @PcodeUserop + @PcodeUserop(functional = true, signed = true) @EmuSyscall("close") - public T unix_close(T fd) { - PcodeArithmetic arithmetic = machine.getArithmetic(); - int ifd = (int) arithmetic.toLong(fd, Purpose.OTHER); - // TODO: Some fs.close or file.close, when all handles have released it? - EmuUnixFileDescriptor desc = releaseFd(ifd); - desc.close(); - return arithmetic.fromConst(0, intSize); + public int unix_close(int fd) { + try { + // TODO: Some fs.close or file.close, when all handles have released it? + EmuUnixFileDescriptor desc = releaseFd(fd); + desc.close(); + return 0; + } + catch (EmuUnixException e) { + return -e.getErrno(); + } } /** * The UNIX {@code group_exit} system call - * *

* This just throws an exception, which the overall simulator or script should catch. * * @param status the status code * @throws EmuProcessExitedException always */ - @PcodeUserop + @PcodeUserop(functional = true) @EmuSyscall("group_exit") - public void unix_group_exit(T status) { - throw new EmuProcessExitedException(machine.getArithmetic(), status); + public void unix_group_exit(long status) { + throw new EmuProcessExitedException(status); } /** @@ -368,7 +362,6 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary /** * 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 from @@ -385,6 +378,7 @@ public abstract class AbstractEmuUnixSyscallUseropLibrary 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)); + _if(tmp_ret.ltis(0), () -> _result(tmp_ret)); tmp_total.addiTo(tmp_ret); _if(tmp_ret.ltiu(tmp_len), () -> _break()); // We got less than this buffer }); diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractStreamEmuUnixFileHandle.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractStreamEmuUnixFileHandle.java index 5a31cd13d8..47a677b6d9 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractStreamEmuUnixFileHandle.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/AbstractStreamEmuUnixFileHandle.java @@ -4,9 +4,9 @@ * 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. @@ -30,8 +30,6 @@ public abstract class AbstractStreamEmuUnixFileHandle implements EmuUnixFileD protected final PcodeArithmetic arithmetic; protected final int offsetBytes; - private final T offset; - /** * Construct a new handle * @@ -42,12 +40,16 @@ public abstract class AbstractStreamEmuUnixFileHandle implements EmuUnixFileD 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; + public T getAbstractOffset() { + return arithmetic.fromConst(0, offsetBytes); + } + + @Override + public long getOffset() { + return 0; } @Override @@ -55,6 +57,21 @@ public abstract class AbstractStreamEmuUnixFileHandle implements EmuUnixFileD // No effect } + @Override + public void seek(long offset) throws EmuIOException { + // No effect + } + + @Override + public T readAbstract(T buf) throws EmuIOException { + return arithmetic.fromConst(read(buf), offsetBytes); + } + + @Override + public T writeAbstract(T buf) throws EmuIOException { + return arithmetic.fromConst(write(buf), offsetBytes); + } + @Override public EmuUnixFileStat stat() { return Unfinished.TODO(); diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java index 1cfbea0121..e73b147c15 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/DefaultEmuUnixFileHandle.java @@ -4,9 +4,9 @@ * 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. @@ -20,25 +20,16 @@ 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.exec.PcodeArithmetic.Purpose; 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 + * A concrete file descriptor + * + * @param the type of values stored in 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; +public class DefaultEmuUnixFileHandle extends AbstractEmuUnixFileHandle { + long offset; /** * Construct a new handle on the given file @@ -52,90 +43,58 @@ public class DefaultEmuUnixFileHandle implements EmuUnixFileDescriptor { */ 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); - } - - /** - * Get the file opened to this handle - * - * @return the file - */ - public EmuUnixFile getFile() { - return file; - } - - /** - * Check if the file is readable, throwing {@link EmuIOException} if not - */ - 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 = (int) arithmetic.sizeOf(len); - offset = - arithmetic.binaryOp(PcodeOp.INT_ADD, offsetBytes, offsetBytes, offset, sizeofLen, len); + super(machine, cSpec, file, flags, user); + this.offset = flags.contains(OpenFlag.O_APPEND) ? file.getStat().st_size : 0; } @Override - public T getOffset() { + public long getOffset() { return offset; } + @Override + public T getAbstractOffset() { + return arithmetic.fromConst(offset, offsetBytes); + } + @Override public void seek(T offset) throws EmuIOException { + seek(arithmetic.toLong(offset, Purpose.OTHER)); + } + + @Override + public void seek(long offset) throws EmuIOException { // TODO: Where does bounds check happen? this.offset = offset; } @Override - public T read(T buf) throws EmuIOException { + public int 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); + int len = file.read(offset, buf); + if (len > 0) { + offset += len; } - T len = file.write(arithmetic, offset, buf); - advanceOffset(len); return len; } @Override - public EmuUnixFileStat stat() { - return file.getStat(); + public T readAbstract(T buf) throws EmuIOException { + return arithmetic.fromConst(read(buf), offsetBytes); } @Override - public void close() { - // TODO: Let the file know a handle was closed? + public int write(T buf) throws EmuIOException { + checkWritable(); + int len = file.write(offset, buf); + if (len > 0) { + offset += len; + } + return len; + } + + @Override + public T writeAbstract(T buf) throws EmuIOException { + return arithmetic.fromConst(write(buf), offsetBytes); } } diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/EmuUnixFile.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/EmuUnixFile.java index e57837e2ae..2986143f4d 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/EmuUnixFile.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/EmuUnixFile.java @@ -4,9 +4,9 @@ * 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. @@ -20,7 +20,6 @@ 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. @@ -31,7 +30,6 @@ 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 @@ -43,7 +41,6 @@ public interface EmuUnixFile { /** * 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. @@ -56,8 +53,19 @@ public interface EmuUnixFile { T read(PcodeArithmetic arithmetic, T offset, T buf); /** - * Write contents into the file starting at the given offset from the given buffer + * 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 offset the offset + * @param buf the buffer + * @return the number of bytes read + */ + int read(long 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. @@ -69,6 +77,18 @@ public interface EmuUnixFile { */ T write(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 offset the offset + * @param buf the buffer + * @return the number of bytes written + */ + int write(long offset, T buf); + /** * Erase the contents of the file */ diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileDescriptor.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileDescriptor.java index cf4d890b14..e88d6fadb5 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileDescriptor.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/EmuUnixFileDescriptor.java @@ -4,9 +4,9 @@ * 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. @@ -15,6 +15,7 @@ */ package ghidra.pcode.emu.unix; +import ghidra.lifecycle.Experimental; import ghidra.pcode.emu.sys.EmuIOException; /** @@ -22,6 +23,7 @@ import ghidra.pcode.emu.sys.EmuIOException; * * @param the type of values stored in the file */ +@Experimental public interface EmuUnixFileDescriptor { /** * The default file descriptor for stdin (standard input) @@ -37,20 +39,31 @@ public interface EmuUnixFileDescriptor { int FD_STDERR = 2; /** - * Get the current offset of the file, or 0 if not applicable - * - * @return the offset + * {@return the current offset of the file, or 0 if not applicable} */ - T getOffset(); + T getAbstractOffset(); /** - * See to the given offset + * {@return the current offset of the file, or 0 if not applicable} + */ + long getOffset(); + + /** + * Seek to the given offset * * @param offset the desired offset * @throws EmuIOException if an error occurred */ void seek(T offset) throws EmuIOException; + /** + * Seek to the given offset + * + * @param offset the desired offset + * @throws EmuIOException if an error occurred + */ + void seek(long offset) throws EmuIOException; + /** * Read from the file opened by this handle * @@ -58,19 +71,37 @@ public interface EmuUnixFileDescriptor { * @return the number of bytes read * @throws EmuIOException if an error occurred */ - T read(T buf) throws EmuIOException; + T readAbstract(T buf) throws EmuIOException; /** - * Read into the file opened by this handle + * 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 + */ + int read(T buf) throws EmuIOException; + + /** + * Write 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; + T writeAbstract(T buf) throws EmuIOException; /** - * Obtain the {@code stat} structure of the file opened by this handle + * Write into the file opened by this handle + * + * @param buf the source buffer + * @return the number of bytes written + * @throws EmuIOException if an error occurred + */ + int write(T buf) throws EmuIOException; + + /** + * {@return the {@code stat} structure of the file opened by this handle} */ EmuUnixFileStat stat(); diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/IOStreamEmuUnixFileHandle.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/IOStreamEmuUnixFileHandle.java index 43c03a2f4a..e0ee6f1684 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/IOStreamEmuUnixFileHandle.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/emu/unix/IOStreamEmuUnixFileHandle.java @@ -4,9 +4,9 @@ * 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. @@ -67,7 +67,6 @@ public class IOStreamEmuUnixFileHandle extends AbstractStreamEmuUnixFileHandle * WARNING: Think carefully before proxying any host resource to a temperamental target * program. @@ -85,13 +84,12 @@ public class IOStreamEmuUnixFileHandle extends AbstractStreamEmuUnixFileHandle * 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 @@ -75,14 +73,12 @@ import utilities.util.AnnotationUtilities; * } * } * - * *

* 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: * @@ -103,7 +99,6 @@ import utilities.util.AnnotationUtilities; * System.out.println(myUserop.programFor(new Varnode(r0.getAddress(), r0.getNumBytes()), List.of(), * PcodeUseropLibrary.NIL)); * - * *

* Known limitations: *