From 6ced49d1d7fd91663e7627b11fc4e68dd06bca1c Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Tue, 3 Feb 2026 16:08:01 +0000 Subject: [PATCH] GP-4241: Discoverable per-arch userop-lib framework and AARCH64 port. --- .../DebuggerEmuExampleScript.java | 3 +- .../StandAloneStructuredSleighScript.java | 15 +- .../ghidra/pcode/struct/StructuredSleigh.java | 10 +- .../struct/sub/StructuredSleighTest.java | 4 +- .../Emulation/certification.manifest | 1 + .../Emulation/data/ExtensionPoint.manifest | 1 + .../ghidra/app/emulator/AdaptedEmulator.java | 2 +- .../pcode/emu/AbstractPcodeMachine.java | 4 +- .../ghidra/pcode/emu/ModifiedPcodeThread.java | 2 +- .../java/ghidra/pcode/emu/PcodeEmulator.java | 5 - .../pcode/emu/auxiliary/AuxPcodeEmulator.java | 3 +- .../AbstractSleighPcodeUseropDefinition.java | 164 ++++++++ .../exec/AnnotatedPcodeUseropLibrary.java | 11 +- .../exec/ComposedPcodeUseropLibrary.java | 19 +- .../pcode/exec/DefaultPcodeUseropLibrary.java | 46 ++ .../FixedSleighPcodeUseropDefinition.java | 67 +++ ...OverloadedSleighPcodeUseropDefinition.java | 89 ++++ .../pcode/exec/PairedPcodeArithmetic.java | 1 - .../ghidra/pcode/exec/PcodeUseropLibrary.java | 24 +- .../pcode/exec/PcodeUseropLibraryFactory.java | 176 ++++++++ .../exec/SleighPcodeUseropDefinition.java | 254 +++++------ .../AARCH64/data/languages/AARCH64.pspec | 1 + .../AARCH64/src/main/java/DecodeBitMasks.java | 228 ---------- ...ARCH64EmulateInstructionStateModifier.java | 46 +- .../AARCH64PcodeUseropLibraryFactory.java | 107 +++++ .../emulation/AARCH64PcodeLibraryTest.java | 395 ++++++++++++++++++ 26 files changed, 1237 insertions(+), 441 deletions(-) create mode 100644 Ghidra/Framework/Emulation/data/ExtensionPoint.manifest create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractSleighPcodeUseropDefinition.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DefaultPcodeUseropLibrary.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/FixedSleighPcodeUseropDefinition.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/OverloadedSleighPcodeUseropDefinition.java create mode 100644 Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibraryFactory.java delete mode 100644 Ghidra/Processors/AARCH64/src/main/java/DecodeBitMasks.java create mode 100644 Ghidra/Processors/AARCH64/src/main/java/ghidra/program/emulation/AARCH64PcodeUseropLibraryFactory.java create mode 100644 Ghidra/Processors/AARCH64/src/test.slow/java/ghidra/program/emulation/AARCH64PcodeLibraryTest.java diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java index bdebd2aad4..436a143567 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/DebuggerEmuExampleScript.java @@ -141,7 +141,8 @@ public class DebuggerEmuExampleScript extends GhidraScript implements FlatDebugg PcodeEmulator emulator = new PcodeEmulator(access.getLanguage(), writer.callbacks()) { @Override protected PcodeUseropLibrary createUseropLibrary() { - return new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this); + return super.createUseropLibrary().compose( + new DemoPcodeUseropLibrary(language, DebuggerEmuExampleScript.this)); } }; // Conventionally, emulator threads are named after their trace thread's path. diff --git a/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneStructuredSleighScript.java b/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneStructuredSleighScript.java index 17becfef78..20bf2320f9 100644 --- a/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneStructuredSleighScript.java +++ b/Ghidra/Features/SystemEmulation/ghidra_scripts/StandAloneStructuredSleighScript.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. @@ -27,7 +27,9 @@ 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.SleighPcodeUseropDefinition.SignatureDef; import ghidra.pcode.struct.StructuredSleigh; import ghidra.program.model.lang.LanguageID; @@ -100,10 +102,15 @@ public class StandAloneStructuredSleighScript extends GhidraScript { * Now, dump the generated Sleigh source */ for (SleighPcodeUseropDefinition userop : ops.values()) { + if (!(userop instanceof FixedSleighPcodeUseropDefinition fixed)) { + println("WARN: Unexpected userop type for " + userop.getName()); + continue; + } print(userop.getName() + "("); - print(userop.getInputs().stream().collect(Collectors.joining(","))); + SignatureDef definition = fixed.getSignatureDef(); + print(definition.signature().stream().collect(Collectors.joining(","))); print(") {\n"); - print(userop.getBody()); + print(definition.generateBody()); print("}\n\n"); } } diff --git a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/struct/StructuredSleigh.java b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/struct/StructuredSleigh.java index ce6f26b715..f7cdb0d2fd 100644 --- a/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/struct/StructuredSleigh.java +++ b/Ghidra/Features/SystemEmulation/src/main/java/ghidra/pcode/struct/StructuredSleigh.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.app.plugin.processors.sleigh.SleighLanguage; import ghidra.lifecycle.Internal; import ghidra.pcode.emu.unix.AbstractEmuUnixSyscallUseropLibrary; import ghidra.pcode.exec.*; -import ghidra.pcode.exec.SleighPcodeUseropDefinition.Builder; +import ghidra.pcode.exec.SleighPcodeUseropDefinition.BuilderStage1; import ghidra.pcode.exec.SleighPcodeUseropDefinition.Factory; import ghidra.pcode.floatformat.FloatFormatFactory; import ghidra.pcode.struct.DefaultVar.Check; @@ -1691,7 +1691,7 @@ public class StructuredSleigh { throw new IllegalArgumentException("Cannot access " + method + " having @" + StructuredUserop.class.getSimpleName() + " annotation. Override getMethodLookup()"); } - Builder builder = factory.define(method.getName()); + BuilderStage1 builder = factory.define(method.getName()); DataType retType = type(annot.type()); @@ -1733,7 +1733,7 @@ public class StructuredSleigh { } }); StringTree source = root.generate(FALL, FALL); - builder.body(source.toString()); + builder.body(args -> source.toString()); return builder.build(); } diff --git a/Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java b/Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java index bce0110059..32e1e9f98f 100644 --- a/Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java +++ b/Ghidra/Features/SystemEmulation/src/test/java/ghidra/pcode/struct/sub/StructuredSleighTest.java @@ -107,7 +107,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest __op_output = (my_var * 0x2:4); """, myUserop.getBody()); // Verify the source compiles - myUserop.programFor(new Varnode(r0.getAddress(), r0.getNumBytes()), List.of(), + myUserop.programFor(List.of(new Varnode(r0.getAddress(), r0.getNumBytes())), PcodeUseropLibrary.NIL); } @@ -246,7 +246,7 @@ public class StructuredSleighTest extends AbstractGhidraHeadlessIntegrationTest } }; SleighPcodeUseropDefinition myUserop = ss.generate().get("my_userop"); - PcodeProgram program = myUserop.programFor(null, List.of(), PcodeUseropLibrary.nil()); + PcodeProgram program = myUserop.programFor(List.of(), PcodeUseropLibrary.nil()); assertTrue(program.getCode().isEmpty()); } } diff --git a/Ghidra/Framework/Emulation/certification.manifest b/Ghidra/Framework/Emulation/certification.manifest index a71cbedad6..85facc7d66 100644 --- a/Ghidra/Framework/Emulation/certification.manifest +++ b/Ghidra/Framework/Emulation/certification.manifest @@ -2,6 +2,7 @@ ##MODULE IP: INRIA License Module.manifest||GHIDRA||||END| README.md||GHIDRA||||END| +data/ExtensionPoint.manifest||GHIDRA||||END| src/test/resources/mock.cspec||GHIDRA||||END| src/test/resources/mock.ldefs||GHIDRA||||END| src/test/resources/mock.pspec||GHIDRA||||END| diff --git a/Ghidra/Framework/Emulation/data/ExtensionPoint.manifest b/Ghidra/Framework/Emulation/data/ExtensionPoint.manifest new file mode 100644 index 0000000000..4eae462c32 --- /dev/null +++ b/Ghidra/Framework/Emulation/data/ExtensionPoint.manifest @@ -0,0 +1 @@ +PcodeUseropLibraryFactory \ No newline at end of file diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java index 9cebdeac78..e4c48b227b 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/AdaptedEmulator.java @@ -89,7 +89,7 @@ public class AdaptedEmulator implements Emulator { @Override protected PcodeUseropLibrary createUseropLibrary() { - return new AdaptedPcodeUseropLibrary(); + return new AdaptedPcodeUseropLibrary().compose(super.createUseropLibrary()); } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java index ec8af866d6..ea8ceae914 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/AbstractPcodeMachine.java @@ -115,7 +115,9 @@ public abstract class AbstractPcodeMachine implements PcodeMachine { * * @return the library */ - protected abstract PcodeUseropLibrary createUseropLibrary(); + protected PcodeUseropLibrary createUseropLibrary() { + return PcodeUseropLibraryFactory.createUseropLibraryForLanguage(language, arithmetic); + } @Override public SleighLanguage getLanguage() { diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java index cd7a58858f..efcb48dd9d 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ModifiedPcodeThread.java @@ -254,7 +254,7 @@ public class ModifiedPcodeThread extends DefaultPcodeThread { @Override protected PcodeUseropLibrary createUseropLibrary() { - return super.createUseropLibrary().compose(new ModifierUseropLibrary()); + return new ModifierUseropLibrary().compose(super.createUseropLibrary(), true); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java index 2df5e19b9c..a695dbd490 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java @@ -166,9 +166,4 @@ public class PcodeEmulator extends AbstractPcodeMachine { PcodeStateCallbacks scb = cb.wrapFor(thread); return new BytesPcodeExecutorState(language, scb); } - - @Override - protected PcodeUseropLibrary createUseropLibrary() { - return PcodeUseropLibrary.nil(); - } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java index fa96b99871..859be241f4 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/auxiliary/AuxPcodeEmulator.java @@ -62,7 +62,8 @@ public abstract class AuxPcodeEmulator extends AbstractPcodeMachine> createUseropLibrary() { - return getPartsFactory().createSharedUseropLibrary(this); + return super.createUseropLibrary() + .compose(getPartsFactory().createSharedUseropLibrary(this)); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractSleighPcodeUseropDefinition.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractSleighPcodeUseropDefinition.java new file mode 100644 index 0000000000..2802759613 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AbstractSleighPcodeUseropDefinition.java @@ -0,0 +1,164 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import java.lang.reflect.Method; +import java.util.*; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; + +/** + * A abstract Sleigh userop definition + * + * @param no type in particular, except to match any executor + */ +public abstract class AbstractSleighPcodeUseropDefinition + implements SleighPcodeUseropDefinition { + + /** + * A builder for a particular userop + * + * @see SleighPcodeUseropDefinition.Factory + */ + public static class Builder implements BuilderStage1 { + private final Factory factory; + private final String name; + private final Map definitions = new HashMap<>(); + private final List params = new ArrayList<>(); + private final List body = new ArrayList<>(); + + protected Builder(Factory factory, String name) { + this.factory = factory; + this.name = name; + + params(OUT_SYMBOL_NAME); + } + + @Override + public Builder params(Collection additionalParams) { + this.params.addAll(additionalParams); + return this; + } + + @Override + public Builder body(BodyFunc additionalBody) { + body.add(additionalBody); + return this; + } + + @Override + public BuilderStage1 overload() { + SignatureDef exists = definitions.put(params.size(), + new SignatureDef(List.copyOf(params), List.copyOf(body))); + + params.clear(); + body.clear(); + params(OUT_SYMBOL_NAME); + + if (exists != null) { + throw new IllegalArgumentException("Definition for this signature already exists"); + } + return this; + } + + /** + * Build the actual definition + * + *

+ * NOTE: Compilation of the sleigh source is delayed until the first invocation, since the + * compiler must know about the varnodes used as parameters. TODO: There may be some way to + * template it at the p-code level instead of the Sleigh source level. + * + * @param no particular type, except to match the executor + * @return the definition + */ + @Override + public SleighPcodeUseropDefinition build() { + overload(); + if (definitions.size() == 1) { + return new FixedSleighPcodeUseropDefinition(factory.language, name, + definitions.values().iterator().next()); + } + return new OverloadedSleighPcodeUseropDefinition<>(factory.language, name, + Map.copyOf(definitions)); + } + } + + protected final SleighLanguage language; + protected final String name; + + protected final Map, PcodeProgram> cacheByArgs = new HashMap<>(); + + protected AbstractSleighPcodeUseropDefinition(SleighLanguage language, String name) { + this.language = language; + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean isFunctional() { + return false; + } + + @Override + public boolean hasSideEffects() { + return true; + } + + /** + * {@inheritDoc} + * + * @implNote We could scan the p-code ops for any that write to the contextreg; however, at the + * moment, that is highly unconventional and perhaps even considered an error. If that + * becomes more common, or even recommended, then we can detect it and behave + * accordingly during interpretation (whether for execution or translation). + */ + @Override + public boolean modifiesContext() { + return false; + } + + @Override + public boolean canInlinePcode() { + return true; + } + + @Override + public Method getJavaMethod() { + return null; + } + + @Override + public PcodeUseropLibrary getDefiningLibrary() { + return null; + } + + @Override + public void execute(PcodeExecutor executor, PcodeUseropLibrary library, PcodeOp op, + Varnode outArg, List inArgs) { + List args = new ArrayList<>(inArgs.size() + 1); + args.add(outArg); + args.addAll(inArgs); + PcodeProgram program = programFor(args, library); + executor.execute(program, library); + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java index c8d9100716..073656deae 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/AnnotatedPcodeUseropLibrary.java @@ -41,7 +41,7 @@ import utilities.util.AnnotationUtilities; * * @param the type of data processed by the library */ -public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibrary { +public abstract class AnnotatedPcodeUseropLibrary extends DefaultPcodeUseropLibrary { private static final Map, Set> CACHE_BY_CLASS = new HashMap<>(); private static Set collectDefinitions( @@ -825,10 +825,6 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra public @interface OpOp { } - protected Map> ops = new HashMap<>(); - private Map> unmodifiableOps = - Collections.unmodifiableMap(ops); - /** * Default constructor, usually invoked implicitly */ @@ -864,9 +860,4 @@ public abstract class AnnotatedPcodeUseropLibrary implements PcodeUseropLibra protected Lookup getMethodLookup() { return MethodHandles.lookup(); } - - @Override - public Map> getUserops() { - return unmodifiableOps; - } } diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/ComposedPcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/ComposedPcodeUseropLibrary.java index 03915eecbf..9bec07e88a 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/ComposedPcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/ComposedPcodeUseropLibrary.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. @@ -32,21 +32,22 @@ public class ComposedPcodeUseropLibrary implements PcodeUseropLibrary { * * @param the type of values processed by the libraries * @param libraries the libraries whose userops to collect + * @param override allow libraries to the right to override userops from libraries to the left * @return the resulting map */ public static Map> composeUserops( - Collection> libraries) { + Collection> libraries, boolean override) { Map> userops = new HashMap<>(); for (PcodeUseropLibrary lib : libraries) { for (PcodeUseropDefinition def : lib.getUserops().values()) { - if (userops.put(def.getName(), def) != null) { + if (userops.put(def.getName(), def) != null && !override) { throw new IllegalArgumentException( "Cannot compose libraries with conflicting definitions on " + def.getName()); } } } - return userops; + return Collections.unmodifiableMap(userops); } private final Map> userops; @@ -55,12 +56,14 @@ public class ComposedPcodeUseropLibrary implements PcodeUseropLibrary { * Construct a composed userop library from the given libraries * *

- * This uses {@link #composeUserops(Collection)}, so its restrictions apply here, too. + * This uses {@link #composeUserops(Collection, boolean)}, so its restrictions apply here, too. * + * @param override allow libraries to the right to override userops from libraries to the left * @param libraries the libraries */ - public ComposedPcodeUseropLibrary(Collection> libraries) { - this.userops = composeUserops(libraries); + public ComposedPcodeUseropLibrary(Collection> libraries, + boolean override) { + this.userops = composeUserops(libraries, override); } @Override diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DefaultPcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DefaultPcodeUseropLibrary.java new file mode 100644 index 0000000000..a13e43cbf9 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/DefaultPcodeUseropLibrary.java @@ -0,0 +1,46 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import java.util.*; + +/** + * The default implemenation of a userop library + * + *

+ * Userops are added by calling {@link #putOp(PcodeUseropDefinition)}, usually in the constructor. + * + * @param the type of data processed by the library + */ +public class DefaultPcodeUseropLibrary implements PcodeUseropLibrary { + protected Map> ops = new HashMap<>(); + private Map> unmodifiableOps = + Collections.unmodifiableMap(ops); + + /** + * Add the given userop to this library + * + * @param userop the userop + */ + protected void putOp(PcodeUseropDefinition userop) { + ops.put(userop.getName(), userop); + } + + @Override + public Map> getUserops() { + return unmodifiableOps; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/FixedSleighPcodeUseropDefinition.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/FixedSleighPcodeUseropDefinition.java new file mode 100644 index 0000000000..078af40d15 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/FixedSleighPcodeUseropDefinition.java @@ -0,0 +1,67 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import java.util.List; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.program.model.pcode.Varnode; + +/** + * A Sleigh userop definition with one signature + * + * @param no type in particular, except to match any executor + */ +public class FixedSleighPcodeUseropDefinition extends AbstractSleighPcodeUseropDefinition { + protected final SignatureDef definition; + + protected FixedSleighPcodeUseropDefinition(SleighLanguage language, String name, + SignatureDef definition) { + super(language, name); + this.definition = definition; + } + + @Override + public int getInputCount() { + return definition.signature().size() - 1; // account for __op_output + } + + /** + * Get the single signature and definition + * + * @return the definition + */ + public SignatureDef getSignatureDef() { + return definition; + } + + @Override + public String getBody(List args) { + return definition.generateBody(args); + } + + @Override + public PcodeProgram programFor(List args, PcodeUseropLibrary library) { + return cacheByArgs.computeIfAbsent(args, + a -> SleighProgramCompiler.compileUserop(language, name, definition.signature(), + definition.generateBody(a), library, a)); + } + + @Override + public Class getOutputType() { + return null; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/OverloadedSleighPcodeUseropDefinition.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/OverloadedSleighPcodeUseropDefinition.java new file mode 100644 index 0000000000..63442b2fc1 --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/OverloadedSleighPcodeUseropDefinition.java @@ -0,0 +1,89 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import java.util.*; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.program.model.pcode.Varnode; + +/** + * A Sleigh userop definition with multiple signatures + * + * @param no type in particular, except to match any executor + */ +public class OverloadedSleighPcodeUseropDefinition + extends AbstractSleighPcodeUseropDefinition { + protected final Map definitions; + + protected OverloadedSleighPcodeUseropDefinition(SleighLanguage language, String name, + Map definitions) { + super(language, name); + this.definitions = definitions; + } + + @Override + public int getInputCount() { + return -1; + } + + /** + * Get the signature and definition for the given argument count + * + * @param argCount the argument (or parameter) count + * @return the definition, or null if not defined + */ + public SignatureDef getSignatureDef(int argCount) { + return definitions.get(argCount); + } + + /** + * Get all the signatures and definitions for this userop + * + * @return the collection of definitions + */ + public Collection getAllSignatures() { + return definitions.values(); + } + + private SignatureDef requireSignatureDef(List args) { + SignatureDef definition = definitions.get(args.size()); + if (definition == null) { + throw new SleighLinkException("Incorrect number of arguments to " + getName()); + } + return definition; + } + + @Override + public String getBody(List args) { + SignatureDef definition = requireSignatureDef(args); + return definition.generateBody(args); + } + + @Override + public PcodeProgram programFor(List args, PcodeUseropLibrary library) { + return cacheByArgs.computeIfAbsent(args, a -> { + SignatureDef definition = requireSignatureDef(a); + return SleighProgramCompiler.compileUserop(language, name, definition.signature(), + definition.generateBody(a), library, a); + }); + } + + @Override + public Class getOutputType() { + return null; + } +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java index 78cebff1ee..40dbad15a7 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PairedPcodeArithmetic.java @@ -187,7 +187,6 @@ public class PairedPcodeArithmetic implements PcodeArithmetic> @Override public long sizeOf(Pair value) { return leftArith.sizeOf(value.getLeft()); - // TODO: Assert that the right agrees? Nah. Some aux types have no size. } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java index 98f3621df8..7dfc73c216 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibrary.java @@ -49,6 +49,11 @@ public interface PcodeUseropLibrary { public Map> getUserops() { return Map.of(); } + + @Override + public PcodeUseropLibrary compose(PcodeUseropLibrary lib) { + return lib; + } } /** @@ -269,13 +274,24 @@ public interface PcodeUseropLibrary { * Compose this and the given library into a new library. * * @param lib the other library + * @param override allow the given library to override userops from this library + * @return a new library having all userops defined between the two + */ + default PcodeUseropLibrary compose(PcodeUseropLibrary lib, boolean override) { + if (lib == null || lib == NIL) { + return this; + } + return new ComposedPcodeUseropLibrary<>(List.of(this, lib), override); + } + + /** + * Compose this and the given library into a new library, forbidding overrides + * + * @param lib the other library * @return a new library having all userops defined between the two */ default PcodeUseropLibrary compose(PcodeUseropLibrary lib) { - if (lib == null) { - return this; - } - return new ComposedPcodeUseropLibrary<>(List.of(this, lib)); + return compose(lib, false); } /** diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibraryFactory.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibraryFactory.java new file mode 100644 index 0000000000..e1fb1eb52f --- /dev/null +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/PcodeUseropLibraryFactory.java @@ -0,0 +1,176 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.pcode.exec; + +import java.lang.annotation.*; +import java.util.*; +import java.util.stream.Collectors; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.util.Msg; +import ghidra.util.classfinder.ClassSearcher; +import ghidra.util.classfinder.ExtensionPoint; + +/** + * A discoverable factory for creating a pluggable userop library, automatically picked up by the + * default emulator. + * + *

+ * The factory must have a public default constructor. + */ +public interface PcodeUseropLibraryFactory extends ExtensionPoint { + /** The property key for useropLib ids in pspec files */ + public static final String KEY_USEROP_LIBS = "useropLibs"; + + /** + * A required annotation for identifying the library in pspec files + */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.RUNTIME) + @interface UseropLibrary { + /** + * The id of this library (factory) + * + *

+ * This id should not contain any commas. While other symbols are allowed, only hyphens and + * periods are recommended. We request all unqualified ids be reserved for use by the core + * Ghidra distribution. Extensions, scripts, and other 3rd-party additions should use + * periods to briefly qualify their names, e.g., {@code "com.example.my-userop-lib"}. + * + * @return the id + */ + String value(); + } + + /** + * Create the userop library as identified for the given language and arithmetic + * + *

+ * While not strictly enforced by the framework, some care should be taken to ensure the library + * is prepared to handle the given language, since it may only expect those whose pspec files + * identify it. The library (or its factory) may throw an exception, or otherwise exhibit + * undefined behavior, if it cannot find the resources, e.g., a specific named register, that it + * expects. + * + *

+ * The given arithmetic must also be compatible with both the library and the language. In + * particular, the language and arithmetic must agree in endianness. (The default emulator + * should already ensure this is the case.) The library must also understand the type of the + * arithmetic, i.e., the type of values in the emulator. If either is not the case the emulator + * may exhibit undefined behavior. (The default emulator does not guarantee type + * compatibility.) Ideally, such incompatibilities are checked and reported by the userop + * library as early as possible, e.g., in the library's constructor. + * + *

+ * If the given id cannot be found, an empty library ({@link PcodeUseropLibrary#nil()}) is + * returned and a warning logged. If multiple factories have the given id (this is considered a + * bug), then a warning is logged and a factory is selected non-deterministically. + * + * @param the type of values in the emulator's state + * @param id the id of the userop library + * @param language the language + * @param arithmetic the arithmetic + * @return the userop library + */ + static PcodeUseropLibrary createUseropLibraryFromId(String id, SleighLanguage language, + PcodeArithmetic arithmetic) { + List matches = + ClassSearcher.getInstances((PcodeUseropLibraryFactory.class)) + .stream() + .filter(f -> Objects.equals(id, f.getId())) + .toList(); + if (matches.isEmpty()) { + Msg.warn(PcodeUseropLibraryFactory.class, "No userop library with the id: " + id); + return PcodeUseropLibrary.nil(); + } + if (matches.size() > 1) { + Msg.warn(PcodeUseropLibraryFactory.class, + "Multiple userop libraries with the id: " + id + ". Selection is undefined."); + } + return matches.getFirst().create(language, arithmetic); + } + + /** + * Create the userop library for the given language + * + *

+ * This composes all of the libraries named in the language's pspec file in the + * {@value #KEY_USEROP_LIBS} property. That property is a comma-separated list of the ids to + * compose. + * + *

+ * See the caveats in + * {@link #createUseropLibraryFromId(String, SleighLanguage, PcodeArithmetic)} regarding + * agreement between language and arithmetic. + * + * @param the type of values in the emulator's state + * @param language the language + * @param arithmetic the arithmetic + * @return the userop library + * @implNote currently, duplicate userops (by name) are not permitted. This may change in future + * versions. Thus, we compose libraries in the order listed, in case of that change, + * as it would matter. + */ + static PcodeUseropLibrary createUseropLibraryForLanguage(SleighLanguage language, + PcodeArithmetic arithmetic) { + List libIds = List.of(language.getProperty(KEY_USEROP_LIBS, "").split(",")); + Map matches = + ClassSearcher.getInstances((PcodeUseropLibraryFactory.class)) + .stream() + .filter(f -> libIds.contains(f.getId())) + .collect(Collectors.toMap(f -> f.getId(), f -> f)); + PcodeUseropLibrary result = PcodeUseropLibrary.nil(); + for (String id : libIds) { + PcodeUseropLibraryFactory factory = matches.get(id); + if (factory == null) { + continue; + } + result = result.compose(factory.create(language, arithmetic)); + } + return result; + } + + /** + * Get the id of this factory + * + *

+ * This gets the id from the {@link UseropLibrary} annotation. You should not override this + * function without a good reason. + * + * @return the id + */ + default String getId() { + UseropLibrary annot = this.getClass().getAnnotation(UseropLibrary.class); + if (annot == null) { + Msg.warn(this, + "%s %s is missing @%s annotation".formatted( + PcodeUseropLibraryFactory.class.getSimpleName(), this.getClass(), + UseropLibrary.class.getSimpleName())); + return null; + } + return annot.value(); + } + + /** + * Create the userop library + * + * @param the type of values in the emulator + * @param language the language of the emulator + * @param arithmetic the arithmetic of the emulator + * @return the userop library + */ + PcodeUseropLibrary create(SleighLanguage language, PcodeArithmetic arithmetic); +} diff --git a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java index 86d3688ea7..7907f28e91 100644 --- a/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java +++ b/Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/exec/SleighPcodeUseropDefinition.java @@ -15,12 +15,11 @@ */ package ghidra.pcode.exec; -import java.lang.reflect.Method; import java.util.*; +import java.util.stream.Collectors; import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.pcode.exec.PcodeUseropLibrary.PcodeUseropDefinition; -import ghidra.program.model.pcode.PcodeOp; import ghidra.program.model.pcode.Varnode; /** @@ -28,14 +27,15 @@ import ghidra.program.model.pcode.Varnode; * * @param no type in particular, except to match any executor */ -public class SleighPcodeUseropDefinition implements PcodeUseropDefinition { - public static final String OUT_SYMBOL_NAME = "__op_output"; +public interface SleighPcodeUseropDefinition extends PcodeUseropDefinition { + /** The name of the output symbol */ + String OUT_SYMBOL_NAME = "__op_output"; /** * A factory for building {@link SleighPcodeUseropDefinition}s. */ public static class Factory { - private final SleighLanguage language; + final SleighLanguage language; /** * Construct a factory for the given language @@ -52,59 +52,49 @@ public class SleighPcodeUseropDefinition implements PcodeUseropDefinition * @param name the name of the new userop * @return a builder for the userop */ - public Builder define(String name) { - return new Builder(this, name); + public BuilderStage1 define(String name) { + return new AbstractSleighPcodeUseropDefinition.Builder(this, name); } } /** - * A builder for a particular userop - * - * @see Factory + * A function body, as it depends on the given arguments */ - public static class Builder { - private final Factory factory; - private final String name; - private final List params = new ArrayList<>(); - private final StringBuffer body = new StringBuffer(); - - protected Builder(Factory factory, String name) { - this.factory = factory; - this.name = name; - - params(OUT_SYMBOL_NAME); - } - + public interface BodyFunc { /** - * Add parameters with the given names (to the end) + * Generate the body, given the arguments * - * @param additionalParams the additional parameter names - * @return this builder + *

+ * In general, to refer to an argument, the source can use the corresponding parameter by + * name. Ideally, this is always the case, and the generated source does not depend on the + * arguments. Where it's useful to have the varnode, for example, is when the size of the + * argument needs to be known. In this case, the argument can be retrieved by index, where 0 + * is the output varnode, and 1-n is each respective input varnode. + * + * @param args the varnode argument list + * @return the generated source */ - public Builder params(Collection additionalParams) { - this.params.addAll(additionalParams); - return this; - } - - /** - * @see #params(Collection) - * @param additionalParams the additional parameter names - * @return this builder - */ - public Builder params(String... additionalParams) { - return this.params(Arrays.asList(additionalParams)); - } + CharSequence generate(List args); + } + /** + * Stage two of the builder, where parameters can no longer be added + */ + public interface BuilderStage2 { /** * Add Sleigh source to the body * * @param additionalBody the additional source - * @return this builder + * @return the builder */ - public Builder body(CharSequence additionalBody) { - body.append(additionalBody); - return this; - } + BuilderStage2 body(BodyFunc additionalBody); + + /** + * Start a new definition for a different signature + * + * @return the builder + */ + BuilderStage1 overload(); /** * Build the actual definition @@ -117,25 +107,83 @@ public class SleighPcodeUseropDefinition implements PcodeUseropDefinition * @param no particular type, except to match the executor * @return the definition */ - public SleighPcodeUseropDefinition build() { - return new SleighPcodeUseropDefinition<>(factory.language, name, List.copyOf(params), - body.toString()); + SleighPcodeUseropDefinition build(); + } + + /** + * Stage one of the builder, where any operation is allowed + */ + public interface BuilderStage1 extends BuilderStage2 { + /** + * Add parameters with the given names (to the end) + * + * @param additionalParams the additional parameter names + * @return the builder + */ + BuilderStage1 params(Collection additionalParams); + + /** + * @see #params(Collection) + * @param additionalParams the additional parameter names + * @return the builder + */ + default BuilderStage1 params(String... additionalParams) { + return params(Arrays.asList(additionalParams)); } } - private final SleighLanguage language; - private final String name; - private final List params; - private final String body; + /** + * One definition for a userop for a given signature (parameters, including output) + * + * @param signature the names of the arguments, index 0 being the output + * @param body the body source, possibly a function of the arguments + */ + public record SignatureDef(List signature, List body) { + /** + * Generate the body's source code for the given arguments + * + * @param args the argument varnodes + * @return the body + */ + public String generateBody(List args) { + return body.stream().map(b -> b.generate(args)).collect(Collectors.joining()); + } - private final Map, PcodeProgram> cacheByArgs = new HashMap<>(); + /** + * Generate the body's source code for the given arguments + * + * @param args the argument varnodes + * @return the body + */ + public String generateBody(Varnode... args) { + return generateBody(Arrays.asList(args)); + } + } - protected SleighPcodeUseropDefinition(SleighLanguage language, String name, List params, - String body) { - this.language = language; - this.name = name; - this.params = params; - this.body = body; + /** + * Get the Sleigh source that defines this userop + * + *

+ * The body may or may not actually depend on the arguments. Ideally, it does not, but sometimes + * the body may vary depending on the sizes of the arguments. In cases where it is + * known the body is fixed, the args parameter may be null or an empty list. When the arguments + * are required, index 0 must be the output varnode. If the userop has no output, index 0 may be + * null. + * + * @param args the argument varnodes + * @return the body + */ + String getBody(List args); + + /** + * Get the Sleigh source that defines this userop + * + * @see #getBody(List) + * @param args the argument varnodes + * @return the body + */ + default String getBody(Varnode... args) { + return getBody(Arrays.asList(args)); } /** @@ -144,96 +192,10 @@ public class SleighPcodeUseropDefinition implements PcodeUseropDefinition *

* This will compile and cache a program for each new combination of arguments seen. * - * @param outArg the output operand, if applicable - * @param inArgs the input operands + * @param args the operands, output at index 0, and inputs following * @param library the complete userop library * @return the p-code program to be fed to the same executor as invoked this userop, but in a * new frame */ - public PcodeProgram programFor(Varnode outArg, List inArgs, - PcodeUseropLibrary library) { - List args = new ArrayList<>(inArgs.size() + 1); - args.add(outArg); - args.addAll(inArgs); - return cacheByArgs.computeIfAbsent(args, - a -> SleighProgramCompiler.compileUserop(language, name, params, body, library, a)); - } - - @Override - public String getName() { - return name; - } - - @Override - public int getInputCount() { - return params.size() - 1; // account for __op_output - } - - @Override - public void execute(PcodeExecutor executor, PcodeUseropLibrary library, - PcodeOp op, Varnode outArg, List inArgs) { - PcodeProgram program = programFor(outArg, inArgs, library); - executor.execute(program, library); - } - - /** - * Get the names of the inputs in order - * - * @return the input names - */ - public List getInputs() { - return params; - } - - /** - * Get the Sleigh source that defines this userop - * - * @return the lines - */ - public String getBody() { - return body; - } - - @Override - public boolean isFunctional() { - return false; - } - - @Override - public boolean hasSideEffects() { - return true; - } - - /** - * {@inheritDoc} - * - * @implNote We could scan the p-code ops for any that write to the contextreg; however, at the - * moment, that is highly unconventional and perhaps even considered an error. If that - * becomes more common, or even recommended, then we can detect it and behave - * accordingly during interpretation (whether for execution or translation). - */ - @Override - public boolean modifiesContext() { - return false; - } - - @Override - public boolean canInlinePcode() { - return true; - } - - @Override - public Class getOutputType() { - return void.class; - } - - @Override - public Method getJavaMethod() { - return null; - } - - @Override - public PcodeUseropLibrary getDefiningLibrary() { - return null; - } + PcodeProgram programFor(List args, PcodeUseropLibrary library); } diff --git a/Ghidra/Processors/AARCH64/data/languages/AARCH64.pspec b/Ghidra/Processors/AARCH64/data/languages/AARCH64.pspec index 0a17d5c15b..abd08719fc 100644 --- a/Ghidra/Processors/AARCH64/data/languages/AARCH64.pspec +++ b/Ghidra/Processors/AARCH64/data/languages/AARCH64.pspec @@ -7,6 +7,7 @@ + diff --git a/Ghidra/Processors/AARCH64/src/main/java/DecodeBitMasks.java b/Ghidra/Processors/AARCH64/src/main/java/DecodeBitMasks.java deleted file mode 100644 index 9baecfc31a..0000000000 --- a/Ghidra/Processors/AARCH64/src/main/java/DecodeBitMasks.java +++ /dev/null @@ -1,228 +0,0 @@ -/* ### - * IP: GHIDRA - * REVIEWED: YES - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.math.BigInteger; - -public class DecodeBitMasks { - - long tmask; - long wmask; - - long immN, immr, imms; - - int M; - - int HighestSetBit(long x, int bitSize) { - long mask = 0x1 << (bitSize - 1); - for (int i = bitSize - 1; i >= 0; i--) { - if ((mask & x) == mask) { - return i; - } - mask = mask >> 1; - } - return -1; - } - - long ZeroExtend(long x, int bitSize, int extSize) { - long mask = Ones(bitSize); - - x = x & mask; - - return x; - } - - private long Ones(int bitSize) { - long mask = 0x0; - - for (int i = 0; i < bitSize; i++) { - mask = (mask << 1) | 1; - } - - return mask; - } - - long Replicate(long x, int bitSize, int startBit, int repSize, int extSize) { - long repval = (x >> startBit) & Ones(repSize); - int times = extSize / repSize; - long val = 0; - for (int i = 0; i < times; i++) { - val = (val << repSize) | repval; - } - repval = val << startBit; - - x = x | repval; - return x; - } - - long ROR(long x, int esize, long rotate) { - long a = x << (esize - rotate) & Ones(esize); - long r = x >> (rotate) & Ones(esize); - return ((a | r) & Ones(esize)); - } - - boolean decode(long iN, long is, long ir, boolean immediate, int Msize) { - - immN = iN; - imms = is; - immr = ir; - - M = Msize; - - tmask = wmask = 0; - - long levels; - - // Compute log2 of element size - // 2^len must be in range [2, M] - // immN:NOT(imms)); - int len = HighestSetBit(immN << 6 | ((~imms) & Ones(6)), 7); - - if (len < 1) { - System.out.println("bad value " + immN + ":" + immr + ":" + imms); - return false; - } - - assert (M >= (1 << len)); - - // Determine S, R and S - R parameters - levels = ZeroExtend(Ones(len), 6, 6); - - // For logical immediates an all-ones value of S is reserved - // since it would generate a useless all-ones result (many times) - if (immediate && (imms & levels) == levels) { - System.out.println("All-Ones " + immN + ":" + immr + ":" + imms); - return false; - } - - long S = imms & levels; - long R = immr & levels; - - long diff = S - R; // 6-bit subtract with borrow - - int esize = 1 << len; - - long d = diff & Ones(len - 1); - - long welem = ZeroExtend(Ones((int) (S + 1)), esize, esize); - long telem = ZeroExtend(Ones((int) (d + 1)), esize, esize); - - //wmask = Replicate(ROR(welem, R)); - - wmask = Replicate(ROR(welem, esize, R), esize, 0, esize, M); - - // Replicate(telem); - tmask = Replicate(telem, esize, 0, esize, M); - - return true; - } - - static String bitStr(long value, int bitSize) { - BigInteger val = BigInteger.valueOf(value); - val = val.and(new BigInteger("FFFFFFFFFFFFFFFF", 16)); - - String str = val.toString(2); - int len = str.length(); - for (; len < bitSize; len++) { - str = "0" + str; - } - return str; - } - - void printit() { - System.out.println(bitStr(immN, 1) + ":" + bitStr(immr, 6) + ":" + bitStr(imms, 6) + " = " + - bitStr(wmask, M) + " " + bitStr(tmask, M)); - } - - /** - * @param args - */ - public static void main(String[] args) { - DecodeBitMasks bm = new DecodeBitMasks(); - boolean valid; - - valid = bm.decode(0, 0, 0, true, 64); - if (valid) { - bm.printit(); - } - - int immN = 0; - //for (int immN = 0; immN <= 1; immN++) { - for (int immr = 0; immr <= 0x3f; immr++) { - for (int imms = 0; imms <= 0x3f; imms++) { - valid = bm.decode(immN, imms, immr, true, 32); - if (valid) { - bm.printit(); - } - } - } - //} - - //for (int immr = 0; immr <= 0x3f; immr++) { - for (int imms = 0; imms <= 0x3f; imms++) { - valid = bm.decode(immN, imms, 0, true, 32); - if (valid) { - bm.printit(); - } - } - //} - - if (bm.decode(0, 0x1E, 0x1F, true, 32)) { - bm.printit(); - } - - if (bm.decode(0, 0x1D, 0x1E, true, 32)) { - bm.printit(); - } - - - immN = 0; - for (int immr = 0; immr <= 0x3f; immr++) { - for (int imms = 0; imms <= 0x3f; imms++) { - valid = bm.decode(immN, imms, immr, true, 64); - if (valid) { - bm.printit(); - } - } - } - - immN = 1; - for (int immr = 0; immr <= 0x3f; immr++) { - for (int imms = 0; imms <= 0x3f; imms++) { - valid = bm.decode(immN, imms, immr, true, 64); - if (valid) { - bm.printit(); - } - } - } - - immN = 0; - for (int imms = 0; imms <= 0x3f; imms++) { - valid = bm.decode(immN, imms, 0, true, 64); - if (valid) { - bm.printit(); - } - } - - immN = 1; - for (int imms = 0; imms <= 0x3f; imms++) { - valid = bm.decode(immN, imms, 0, true, 64); - if (valid) { - bm.printit(); - } - } - } - -} diff --git a/Ghidra/Processors/AARCH64/src/main/java/ghidra/program/emulation/AARCH64EmulateInstructionStateModifier.java b/Ghidra/Processors/AARCH64/src/main/java/ghidra/program/emulation/AARCH64EmulateInstructionStateModifier.java index af6a59e8f2..361a4bbf97 100644 --- a/Ghidra/Processors/AARCH64/src/main/java/ghidra/program/emulation/AARCH64EmulateInstructionStateModifier.java +++ b/Ghidra/Processors/AARCH64/src/main/java/ghidra/program/emulation/AARCH64EmulateInstructionStateModifier.java @@ -57,7 +57,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt // INT_SEXT: // registerPcodeOpBehavior("SIMD_INT_SEXT", new SIMD_INT_SEXT()); // INT_ABS (no equivalent SLEIGH primitive): - registerPcodeOpBehavior("MP_INT_ABS", new MP_INT_ABS()); +// registerPcodeOpBehavior("MP_INT_ABS", new MP_INT_ABS()); // registerPcodeOpBehavior("SIMD_INT_ABS", new SIMD_INT_ABS()); // INT_ADD: // registerPcodeOpBehavior("SIMD_INT_ADD", new SIMD_INT_ADD()); @@ -175,7 +175,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt long sign = (fbits >>> 31) & 0x1; long exp = (fbits >>> 23) & 0xff - 127 + 15; long mant = (fbits & 0x7fffff) >>> 13; - return (long) (sign << 15 | exp << 10 | mant); + return sign << 15 | exp << 10 | mant; } // Convert a byte array to a long @@ -1264,7 +1264,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt float fx = Float.intBitsToFloat((int) x); float fy = Float.intBitsToFloat((int) y); float fz = fx + fy; - return (long) Float.floatToIntBits(fz); + return Float.floatToIntBits(fz); } else if (esize == 8) { double fx = Double.longBitsToDouble(x); double fy = Double.longBitsToDouble(y); @@ -1287,10 +1287,10 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt return floatToShortBits(fz); } if (oesize == 4) { - return (long) Float.floatToIntBits(fz); + return Float.floatToIntBits(fz); } if (oesize == 8) { - return Double.doubleToLongBits((double) fz); + return Double.doubleToLongBits(fz); } } else if (iesize == 4) { float fx = Float.intBitsToFloat((int) x); @@ -1300,10 +1300,10 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt return floatToShortBits(fz); } if (oesize == 4) { - return (long) Float.floatToIntBits(fz); + return Float.floatToIntBits(fz); } if (oesize == 8) { - return Double.doubleToLongBits((double) fz); + return Double.doubleToLongBits(fz); } } else if (iesize == 8) { double fx = Double.longBitsToDouble(x); @@ -1313,7 +1313,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt return floatToShortBits((float) fz); } if (oesize == 4) { - return (long) Float.floatToIntBits((float) fz); + return Float.floatToIntBits((float) fz); } if (oesize == 8) { return Double.doubleToLongBits(fz); @@ -1335,7 +1335,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt float fx = Float.intBitsToFloat((int) x); float fy = Float.intBitsToFloat((int) y); float fz = fx / fy; - return (long) Float.floatToIntBits(fz); + return Float.floatToIntBits(fz); } else if (esize == 8) { double fx = Double.longBitsToDouble(x); double fy = Double.longBitsToDouble(y); @@ -1358,7 +1358,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt float fx = Float.intBitsToFloat((int) x); float fy = Float.intBitsToFloat((int) y); float fz = fx * fy; - return (long) Float.floatToIntBits(fz); + return Float.floatToIntBits(fz); } else if (esize == 8) { double fx = Double.longBitsToDouble(x); double fy = Double.longBitsToDouble(y); @@ -1381,7 +1381,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt float fx = Float.intBitsToFloat((int) x); float fy = Float.intBitsToFloat((int) y); float fz = fx - fy; - return (long) Float.floatToIntBits(fz); + return Float.floatToIntBits(fz); } else if (esize == 8) { double fx = Double.longBitsToDouble(x); double fy = Double.longBitsToDouble(y); @@ -1402,7 +1402,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt } else if (esize == 4) { float fx = Float.intBitsToFloat((int) x); float fz = - fx; - return (long) Float.floatToIntBits(fz); + return Float.floatToIntBits(fz); } else if (esize == 8) { double fx = Double.longBitsToDouble(x); double fz = - fx; @@ -1422,7 +1422,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt } else if (esize == 4) { float fx = Float.intBitsToFloat((int) x); float fz = (fx < 0.0F) ? (0.0F - fx) : fx; - return (long) Float.floatToIntBits(fz); + return Float.floatToIntBits(fz); } else if (esize == 8) { double fx = Double.longBitsToDouble(x); double fz = (fx < 0.0D) ? (0.0F - fx) : fx; @@ -1438,12 +1438,12 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt if (s_size == d_size) return x; if (s_size == 2) { float fx = shortBitsToFloat(x); - if (d_size == 4) return (long) Float.floatToIntBits(fx); - else if (d_size == 8) return Double.doubleToLongBits((double) fx); + if (d_size == 4) return Float.floatToIntBits(fx); + else if (d_size == 8) return Double.doubleToLongBits(fx); } else if (s_size == 4) { float fx = Float.intBitsToFloat((int) x); if (d_size == 2) return floatToShortBits(fx); - else if (d_size == 8) return Double.doubleToLongBits((double) fx); + else if (d_size == 8) return Double.doubleToLongBits(fx); } else if (s_size == 8) { double fx = Double.longBitsToDouble(x); if (d_size == 2) return floatToShortBits((float) fx); @@ -1459,16 +1459,16 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt if (s_size == d_size) return x; if (s_size == 2) { float fx = shortBitsToFloat(x); - if (d_size == 4) return (long) ((int) fx); + if (d_size == 4) return ((int) fx); else if (d_size == 8) return (long) fx; } else if (s_size == 4) { float fx = Float.intBitsToFloat((int) x); - if (d_size == 2) return (long) ((short) fx); + if (d_size == 2) return ((short) fx); else if (d_size == 8) return (long) fx; } else if (s_size == 8) { double fx = Double.longBitsToDouble(x); - if (d_size == 2) return (long) ((short) fx); - else if (d_size == 4) return (long) ((int) fx); + if (d_size == 2) return ((short) fx); + else if (d_size == 4) return ((int) fx); } return x; } @@ -1604,7 +1604,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt // Implement the TBL/TBX instructions // - // Vd = a64_TBL(Vinit, Vn1, [Vn2, Vn3, Vn3,] Vm) + // Vd = a64_TBL(Vinit, Vn1, [Vn2, Vn3, Vn4,] Vm) // // Vd: destination varnode (8 or 16 bytes) // Vinit: varnode to update (e.g. 0 or Vd) @@ -1654,7 +1654,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt byte[] table = new byte[64]; for (int i = 0; i < regs; i++) { - byte[] vn = memoryState.getBigInteger(inputs[2 + i], false).toByteArray(); + byte[] vn = memoryState.getBigInteger(inputs[1 + i], false).toByteArray(); for (int j = 0; j < vn.length && i * 16 + j < 64; j++) { table[i*16 + j] = vn[vn.length - j - 1]; } @@ -1676,7 +1676,7 @@ public class AARCH64EmulateInstructionStateModifier extends EmulateInstructionSt // size, it's just a simple lookup now for (int i = 0; i < elements; i++) { - int index = (int) (indices[i] & 0xff); + int index = indices[i] & 0xff; if (index < 16 * regs) { result[i] = table[index]; } diff --git a/Ghidra/Processors/AARCH64/src/main/java/ghidra/program/emulation/AARCH64PcodeUseropLibraryFactory.java b/Ghidra/Processors/AARCH64/src/main/java/ghidra/program/emulation/AARCH64PcodeUseropLibraryFactory.java new file mode 100644 index 0000000000..76d3d35c2d --- /dev/null +++ b/Ghidra/Processors/AARCH64/src/main/java/ghidra/program/emulation/AARCH64PcodeUseropLibraryFactory.java @@ -0,0 +1,107 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.program.emulation; + +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeUseropLibraryFactory.UseropLibrary; +import ghidra.program.model.pcode.Varnode; + +/** + * The userop library for AARCH64. + * + * For the TBL and TBX instructions, see + * https://developer.arm.com/documentation/ddi0602/2024-12/SIMD-FP-Instructions/TBL--Table-vector-lookup- + */ +@UseropLibrary("aarch64") +public class AARCH64PcodeUseropLibraryFactory implements PcodeUseropLibraryFactory { + @Override + public PcodeUseropLibrary create(SleighLanguage language, + PcodeArithmetic arithmetic) { + return new AARCH64PcodeUseropLibrary<>(language); + } + + public static class AARCH64PcodeUseropLibrary extends DefaultPcodeUseropLibrary { + public AARCH64PcodeUseropLibrary(SleighLanguage language) { + SleighPcodeUseropDefinition.Factory factory = + new SleighPcodeUseropDefinition.Factory(language); + + putOp(factory.define("MP_INT_ABS").params("n").body(args -> """ + if (n >= 0) goto ; + __op_output = -n; + goto ; + : + __op_output = n; + : + """).build()); + + putOp(factory.define("SIMD_PIECE").params("simdBytes", "offset").body(args -> """ + __op_output = simdBytes(%d*offset); + """.formatted(args.get(1).getSize())).build()); + + putOp(factory.define("a64_TBL").params("init", "n1", "m").body(args -> { + return genA64_TBL(args.get(0), "n1"); + }).overload().params("init", "n1", "n2", "m").body(args -> { + return genA64_TBL(args.get(0), "n1", "n2"); + }).overload().params("init", "n1", "n2", "n3", "m").body(args -> { + return genA64_TBL(args.get(0), "n1", "n2", "n3"); + }).overload().params("init", "n1", "n2", "n3", "n4", "m").body(args -> { + return genA64_TBL(args.get(0), "n1", "n2", "n3", "n4"); + }).build()); + } + + protected String genA64_TBL(Varnode out, String... regs) { + int size = out.getSize(); + String body = genBuildTable(regs) + genIndex(size, regs.length); + return body; + } + + protected String genBuildTable(String... regs) { + if (regs.length == 1) { + return "local table:16 = %s;\n".formatted(regs[0]); + } + int size = 16; // Table is always made up of 16-byte (128-bit) regs + StringBuffer buf = new StringBuffer(); + buf.append("local table:%d;\n".formatted(size * regs.length)); + for (int i = 0; i < regs.length; i++) { + buf.append("table[%d,%d] = %s;\n".formatted(8 * size * i, 8 * size, regs[i])); + } + return buf.toString(); + } + + protected String genIndex(int size, int regCount) { + int tableSize = 16 * regCount; + StringBuffer buf = new StringBuffer(); + buf.append("local indicies:%d = m;\n".formatted(size)); + buf.append("local result:%d = init;\n".formatted(size)); + for (int i = 0; i < size; i++) { + /** + * TODO: Measure JIT performance with different uses of variables + * + * TODO: It might be nice to have a separate a64_TLX, so we don't have to put this + * blasted conditional in here. + */ + buf.append("local idx%d:1 = indicies[%d,8];\n".formatted(i, 8 * i)); + buf.append("if (idx%d >= %d) goto ;\n".formatted(i, tableSize, i)); + buf.append(" tmp%d:1 = table(idx%d);\n".formatted(i, i)); + buf.append(" result[%d,8] = tmp%d;\n".formatted(8 * i, i)); + buf.append("\n".formatted(i)); + } + buf.append("__op_output = result;"); + return buf.toString(); + } + } +} diff --git a/Ghidra/Processors/AARCH64/src/test.slow/java/ghidra/program/emulation/AARCH64PcodeLibraryTest.java b/Ghidra/Processors/AARCH64/src/test.slow/java/ghidra/program/emulation/AARCH64PcodeLibraryTest.java new file mode 100644 index 0000000000..92472ce2c7 --- /dev/null +++ b/Ghidra/Processors/AARCH64/src/test.slow/java/ghidra/program/emulation/AARCH64PcodeLibraryTest.java @@ -0,0 +1,395 @@ +/* ### + * 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.program.emulation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.math.BigInteger; +import java.util.List; +import java.util.Map; + +import org.junit.*; + +import ghidra.app.plugin.assembler.*; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.pcode.emu.*; +import ghidra.pcode.emulate.BreakTableCallBack; +import ghidra.pcode.emulate.Emulate; +import ghidra.pcode.exec.*; +import ghidra.pcode.exec.PcodeArithmetic.Purpose; +import ghidra.pcode.exec.PcodeExecutorStatePiece.Reason; +import ghidra.pcode.memstate.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSpace; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.lang.Register; +import ghidra.program.model.pcode.PcodeOp; +import ghidra.program.model.pcode.Varnode; +import ghidra.program.util.DefaultLanguageService; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.util.exception.MultipleCauses; +import ghidra.util.task.ConsoleTaskMonitor; +import ghidra.util.task.TaskMonitor; + +public class AARCH64PcodeLibraryTest extends AbstractGhidraHeadlessIntegrationTest { + static final LanguageID LANG_ID_AARCH64 = new LanguageID("AARCH64:LE:64:v8A"); + + private SleighLanguage aarch64; + private TaskMonitor monitor; + + @Before + public void setup() throws Exception { + aarch64 = (SleighLanguage) DefaultLanguageService.getLanguageService() + .getLanguage(LANG_ID_AARCH64); + monitor = new ConsoleTaskMonitor(); + } + + public Varnode vn(long offset, int size) { + AddressSpace space = aarch64.getDefaultSpace(); + return new Varnode(space.getAddress(offset), size); + } + + @Test + public void testA64_TBL_1_8B() throws Exception { + PcodeUseropLibrary lib = PcodeUseropLibraryFactory.createUseropLibraryFromId( + "aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64)); + + SleighPcodeUseropDefinition tbl = + (SleighPcodeUseropDefinition) lib.getUserops().get("a64_TBL"); + + Varnode vnDest = vn(0, 8); + Varnode vnInit = vn(16, 8); + Varnode vnN1 = vn(32, 16); + Varnode vnM = vn(96, 8); + PcodeProgram program = tbl.programFor( + List.of(vnDest, vnInit, vnN1, vnM), + lib); + + assertFalse(program.getCode().isEmpty()); + } + + @Test + public void testA64_TBL_2_8B() throws Exception { + PcodeUseropLibrary lib = PcodeUseropLibraryFactory.createUseropLibraryFromId( + "aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64)); + + SleighPcodeUseropDefinition tbl = + (SleighPcodeUseropDefinition) lib.getUserops().get("a64_TBL"); + + Varnode vnDest = vn(0, 8); + Varnode vnInit = vn(16, 8); + Varnode vnN1 = vn(32, 16); + Varnode vnN2 = vn(48, 16); + Varnode vnM = vn(96, 8); + PcodeProgram program = tbl.programFor( + List.of(vnDest, vnInit, vnN1, vnN2, vnM), + lib); + + assertFalse(program.getCode().isEmpty()); + } + + @Test + public void testA64_TBL_3_8B() throws Exception { + PcodeUseropLibrary lib = PcodeUseropLibraryFactory.createUseropLibraryFromId( + "aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64)); + + SleighPcodeUseropDefinition tbl = + (SleighPcodeUseropDefinition) lib.getUserops().get("a64_TBL"); + + Varnode vnDest = vn(0, 8); + Varnode vnInit = vn(16, 8); + Varnode vnN1 = vn(32, 16); + Varnode vnN2 = vn(48, 16); + Varnode vnN3 = vn(64, 16); + Varnode vnM = vn(96, 8); + PcodeProgram program = tbl.programFor( + List.of(vnDest, vnInit, vnN1, vnN2, vnN3, vnM), + lib); + + assertFalse(program.getCode().isEmpty()); + } + + @Test + public void testA64_TBL_4_8B() throws Exception { + PcodeUseropLibrary lib = PcodeUseropLibraryFactory.createUseropLibraryFromId( + "aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64)); + + SleighPcodeUseropDefinition tbl = + (SleighPcodeUseropDefinition) lib.getUserops().get("a64_TBL"); + + Varnode vnDest = vn(0, 8); + Varnode vnInit = vn(16, 8); + Varnode vnN1 = vn(32, 16); + Varnode vnN2 = vn(48, 16); + Varnode vnN3 = vn(64, 16); + Varnode vnN4 = vn(80, 16); + Varnode vnM = vn(96, 8); + PcodeProgram program = tbl.programFor( + List.of(vnDest, vnInit, vnN1, vnN2, vnN3, vnN4, vnM), + lib); + + assertFalse(program.getCode().isEmpty()); + } + + @Test + public void testA64_TBL_1_16B() throws Exception { + PcodeUseropLibrary lib = PcodeUseropLibraryFactory.createUseropLibraryFromId( + "aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64)); + + SleighPcodeUseropDefinition tbl = + (SleighPcodeUseropDefinition) lib.getUserops().get("a64_TBL"); + + Varnode vnDest = vn(0, 16); + Varnode vnInit = vn(16, 16); + Varnode vnN1 = vn(32, 16); + Varnode vnM = vn(96, 16); + PcodeProgram program = tbl.programFor( + List.of(vnDest, vnInit, vnN1, vnM), + lib); + + assertFalse(program.getCode().isEmpty()); + } + + @Test + public void testA64_TBL_2_16B() throws Exception { + PcodeUseropLibrary lib = PcodeUseropLibraryFactory.createUseropLibraryFromId( + "aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64)); + + SleighPcodeUseropDefinition tbl = + (SleighPcodeUseropDefinition) lib.getUserops().get("a64_TBL"); + + Varnode vnDest = vn(0, 16); + Varnode vnInit = vn(16, 16); + Varnode vnN1 = vn(32, 16); + Varnode vnN2 = vn(48, 16); + Varnode vnM = vn(96, 16); + PcodeProgram program = tbl.programFor( + List.of(vnDest, vnInit, vnN1, vnN2, vnM), + lib); + + assertFalse(program.getCode().isEmpty()); + } + + @Test + public void testA64_TBL_3_16B() throws Exception { + PcodeUseropLibrary lib = PcodeUseropLibraryFactory.createUseropLibraryFromId( + "aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64)); + + SleighPcodeUseropDefinition tbl = + (SleighPcodeUseropDefinition) lib.getUserops().get("a64_TBL"); + + Varnode vnDest = vn(0, 16); + Varnode vnInit = vn(16, 16); + Varnode vnN1 = vn(32, 16); + Varnode vnN2 = vn(48, 16); + Varnode vnN3 = vn(64, 16); + Varnode vnM = vn(96, 16); + PcodeProgram program = tbl.programFor( + List.of(vnDest, vnInit, vnN1, vnN2, vnN3, vnM), + lib); + + assertFalse(program.getCode().isEmpty()); + } + + @Test + public void testA64_TBL_4_16B() throws Exception { + PcodeUseropLibrary lib = PcodeUseropLibraryFactory.createUseropLibraryFromId( + "aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64)); + + SleighPcodeUseropDefinition tbl = + (SleighPcodeUseropDefinition) lib.getUserops().get("a64_TBL"); + + Varnode vnDest = vn(0, 16); + Varnode vnInit = vn(16, 16); + Varnode vnN1 = vn(32, 16); + Varnode vnN2 = vn(48, 16); + Varnode vnN3 = vn(64, 16); + Varnode vnN4 = vn(80, 16); + Varnode vnM = vn(96, 16); + PcodeProgram program = tbl.programFor( + List.of(vnDest, vnInit, vnN1, vnN2, vnN3, vnN4, vnM), + lib); + + assertFalse(program.getCode().isEmpty()); + } + + static class TestMemoryFaultHandler implements MemoryFaultHandler { + @Override + public boolean uninitializedRead(Address address, int size, byte[] buf, int bufOffset) { + return false; + } + + @Override + public boolean unknownAddress(Address address, boolean write) { + return false; + } + } + + interface DoAsm { + void accept(AssemblyBuffer buf) throws Exception; + } + + protected void doTestTBL_Equiv_old(Map init, AssemblyBuffer buf, + Map expected) throws Exception { + Address entry = buf.getEntry(); + + MemoryState state = new DefaultMemoryState(aarch64); + Emulate emu = new Emulate(aarch64, state, new BreakTableCallBack(aarch64)); + state.setMemoryBank(new MemoryPageBank(aarch64.getAddressFactory().getRegisterSpace(), + false, 0x1000, new TestMemoryFaultHandler())); + state.setMemoryBank(new MemoryPageBank(aarch64.getDefaultSpace(), + false, 0x1000, new TestMemoryFaultHandler())); + byte[] bytes = buf.getBytes(); + state.setChunk(bytes, aarch64.getDefaultSpace(), entry.getOffset(), bytes.length); + + for (Map.Entry ent : init.entrySet()) { + state.setValue(ent.getKey(), new BigInteger(ent.getValue(), 16)); + } + + emu.setExecuteAddress(entry); + emu.executeInstruction(false, monitor); + + for (Map.Entry ent : expected.entrySet()) { + assertEquals(ent.getValue(), state.getBigInteger(ent.getKey()).toString(16)); + } + } + + protected void doTestTBL_Equiv_new(Map init, AssemblyBuffer buf, + Map expected) throws Exception { + Address entry = buf.getEntry(); + + PcodeEmulator emu = new PcodeEmulator(aarch64) { + @Override + protected BytesPcodeThread createThread(String name) { + /** + * TODO: There's a branch somewhere that will make this not work. + */ + return new BytesPcodeThread(name, this) { + @Override + protected boolean onMissingUseropDef(PcodeOp op, String opName) { + return false; + } + }; + } + }; + byte[] bytes = buf.getBytes(); + emu.getSharedState().setVar(entry, bytes.length, false, bytes); + + PcodeThread thread = emu.newThread(); + PcodeExecutorState state = thread.getState(); + PcodeArithmetic arithmetic = thread.getArithmetic(); + + for (Map.Entry ent : init.entrySet()) { + Register reg = aarch64.getRegister(ent.getKey()); + state.setVar(reg, + arithmetic.fromConst(new BigInteger(ent.getValue(), 16), reg.getNumBytes())); + } + + thread.setCounter(entry); + thread.overrideContextWithDefault(); + thread.stepInstruction(); + + for (Map.Entry ent : expected.entrySet()) { + Register reg = aarch64.getRegister(ent.getKey()); + assertEquals(ent.getValue(), + arithmetic.toBigInteger(state.getVar(reg, Reason.INSPECT), Purpose.INSPECT) + .toString(16)); + } + } + + protected void doTestTBL_Equiv(Map init, DoAsm doAsm, + Map expected) throws Exception { + Assembler asm = Assemblers.getAssembler(aarch64); + Address entry = aarch64.getDefaultSpace().getAddress(0x00400000); + AssemblyBuffer buf = new AssemblyBuffer(asm, entry); + + doAsm.accept(buf); + + AssertionError oldFailure = null; + AssertionError newFailure = null; + try { + doTestTBL_Equiv_old(init, buf, expected); + } + catch (AssertionError e) { + oldFailure = e; + } + try { + doTestTBL_Equiv_new(init, buf, expected); + } + catch (AssertionError e) { + newFailure = e; + } + + if (newFailure != null && oldFailure != null) { + throw new AssertionError("Both old and new failed", + new MultipleCauses(List.of(oldFailure, newFailure))); + } + if (newFailure != null) { + throw new AssertionError("New failed", newFailure); + } + if (oldFailure != null) { + throw new AssumptionViolatedException( + "Old failed, but new passed: " + oldFailure.getMessage(), oldFailure); + } + } + + @Test + public void testTBL_1_8B_Instruction() throws Exception { + doTestTBL_Equiv( + Map.ofEntries( + Map.entry("d1", "040308070c0b100f"), + Map.entry("q2", "0123456789abcdeffedcba9876543210")), + buf -> buf.assemble("tbl v0.8B, {v2.16B}, v1.8B"), + Map.ofEntries( + Map.entry("d0", "9876effe67890001"))); + } + + @Test + public void testTBL_2_8B_Instruction() throws Exception { + doTestTBL_Equiv( + Map.ofEntries( + Map.entry("d1", "041408180c1c1020"), + Map.entry("q2", "0123456789abcdeffedcba9876543210"), + Map.entry("q3", "00112233445566778899aabbccddeeff")), + buf -> buf.assemble("tbl v0.8B, {v2.16B, v3.16B}, v1.8B"), + Map.ofEntries( + Map.entry("d0", "98bbef776733ff00"))); + } + + @Test + public void testTBL_1_16B_Instruction() throws Exception { + doTestTBL_Equiv( + Map.ofEntries( + Map.entry("q1", "040308070c0b100f020106050a090e0d"), + Map.entry("q2", "0123456789abcdeffedcba9876543210")), + buf -> buf.assemble("tbl v0.16B, {v2.16B}, v1.16B"), + Map.ofEntries( + Map.entry("q0", "9876effe678900015432dcbaabcd2345"))); + } + + @Test + public void testTBL_2_16B_Instruction() throws Exception { + doTestTBL_Equiv( + Map.ofEntries( + Map.entry("q1", "041408180c1c1020021206160a1a0e1e"), + Map.entry("q2", "0123456789abcdeffedcba9876543210"), + Map.entry("q3", "00112233445566778899aabbccddeeff")), + buf -> buf.assemble("tbl v0.16B, {v2.16B, v3.16B}, v1.16B"), + Map.ofEntries( + Map.entry("q0", "98bbef776733ff0054dddc99ab552311"))); + } +}