GP-4241: Discoverable per-arch userop-lib framework and AARCH64 port.

This commit is contained in:
Dan
2026-02-03 16:08:01 +00:00
parent 6bff7b6646
commit 6ced49d1d7
26 changed files with 1237 additions and 441 deletions
@@ -141,7 +141,8 @@ public class DebuggerEmuExampleScript extends GhidraScript implements FlatDebugg
PcodeEmulator emulator = new PcodeEmulator(access.getLanguage(), writer.callbacks()) {
@Override
protected PcodeUseropLibrary<byte[]> 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.
@@ -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");
}
}
@@ -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();
}
@@ -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<Object> 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());
}
}
@@ -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|
@@ -0,0 +1 @@
PcodeUseropLibraryFactory
@@ -89,7 +89,7 @@ public class AdaptedEmulator implements Emulator {
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return new AdaptedPcodeUseropLibrary();
return new AdaptedPcodeUseropLibrary().compose(super.createUseropLibrary());
}
}
@@ -115,7 +115,9 @@ public abstract class AbstractPcodeMachine<T> implements PcodeMachine<T> {
*
* @return the library
*/
protected abstract PcodeUseropLibrary<T> createUseropLibrary();
protected PcodeUseropLibrary<T> createUseropLibrary() {
return PcodeUseropLibraryFactory.createUseropLibraryForLanguage(language, arithmetic);
}
@Override
public SleighLanguage getLanguage() {
@@ -254,7 +254,7 @@ public class ModifiedPcodeThread<T> extends DefaultPcodeThread<T> {
@Override
protected PcodeUseropLibrary<T> createUseropLibrary() {
return super.createUseropLibrary().compose(new ModifierUseropLibrary());
return new ModifierUseropLibrary().compose(super.createUseropLibrary(), true);
}
@Override
@@ -166,9 +166,4 @@ public class PcodeEmulator extends AbstractPcodeMachine<byte[]> {
PcodeStateCallbacks scb = cb.wrapFor(thread);
return new BytesPcodeExecutorState(language, scb);
}
@Override
protected PcodeUseropLibrary<byte[]> createUseropLibrary() {
return PcodeUseropLibrary.nil();
}
}
@@ -62,7 +62,8 @@ public abstract class AuxPcodeEmulator<U> extends AbstractPcodeMachine<Pair<byte
@Override
protected PcodeUseropLibrary<Pair<byte[], U>> createUseropLibrary() {
return getPartsFactory().createSharedUseropLibrary(this);
return super.createUseropLibrary()
.compose(getPartsFactory().createSharedUseropLibrary(this));
}
@Override
@@ -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 <T> no type in particular, except to match any executor
*/
public abstract class AbstractSleighPcodeUseropDefinition<T>
implements SleighPcodeUseropDefinition<T> {
/**
* 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<Integer, SignatureDef> definitions = new HashMap<>();
private final List<String> params = new ArrayList<>();
private final List<BodyFunc> body = new ArrayList<>();
protected Builder(Factory factory, String name) {
this.factory = factory;
this.name = name;
params(OUT_SYMBOL_NAME);
}
@Override
public Builder params(Collection<String> 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
*
* <p>
* 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 <T> no particular type, except to match the executor
* @return the definition
*/
@Override
public <T> SleighPcodeUseropDefinition<T> build() {
overload();
if (definitions.size() == 1) {
return new FixedSleighPcodeUseropDefinition<T>(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<List<Varnode>, 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<T> getDefiningLibrary() {
return null;
}
@Override
public void execute(PcodeExecutor<T> executor, PcodeUseropLibrary<T> library, PcodeOp op,
Varnode outArg, List<Varnode> inArgs) {
List<Varnode> args = new ArrayList<>(inArgs.size() + 1);
args.add(outArg);
args.addAll(inArgs);
PcodeProgram program = programFor(args, library);
executor.execute(program, library);
}
}
@@ -41,7 +41,7 @@ import utilities.util.AnnotationUtilities;
*
* @param <T> the type of data processed by the library
*/
public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibrary<T> {
public abstract class AnnotatedPcodeUseropLibrary<T> extends DefaultPcodeUseropLibrary<T> {
private static final Map<Class<?>, Set<Method>> CACHE_BY_CLASS = new HashMap<>();
private static Set<Method> collectDefinitions(
@@ -825,10 +825,6 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
public @interface OpOp {
}
protected Map<String, PcodeUseropDefinition<T>> ops = new HashMap<>();
private Map<String, PcodeUseropDefinition<T>> unmodifiableOps =
Collections.unmodifiableMap(ops);
/**
* Default constructor, usually invoked implicitly
*/
@@ -864,9 +860,4 @@ public abstract class AnnotatedPcodeUseropLibrary<T> implements PcodeUseropLibra
protected Lookup getMethodLookup() {
return MethodHandles.lookup();
}
@Override
public Map<String, PcodeUseropDefinition<T>> getUserops() {
return unmodifiableOps;
}
}
@@ -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<T> implements PcodeUseropLibrary<T> {
*
* @param <T> 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 <T> Map<String, PcodeUseropDefinition<T>> composeUserops(
Collection<PcodeUseropLibrary<T>> libraries) {
Collection<PcodeUseropLibrary<T>> libraries, boolean override) {
Map<String, PcodeUseropDefinition<T>> userops = new HashMap<>();
for (PcodeUseropLibrary<T> lib : libraries) {
for (PcodeUseropDefinition<T> 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<String, PcodeUseropDefinition<T>> userops;
@@ -55,12 +56,14 @@ public class ComposedPcodeUseropLibrary<T> implements PcodeUseropLibrary<T> {
* Construct a composed userop library from the given libraries
*
* <p>
* 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<PcodeUseropLibrary<T>> libraries) {
this.userops = composeUserops(libraries);
public ComposedPcodeUseropLibrary(Collection<PcodeUseropLibrary<T>> libraries,
boolean override) {
this.userops = composeUserops(libraries, override);
}
@Override
@@ -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
*
* <p>
* Userops are added by calling {@link #putOp(PcodeUseropDefinition)}, usually in the constructor.
*
* @param <T> the type of data processed by the library
*/
public class DefaultPcodeUseropLibrary<T> implements PcodeUseropLibrary<T> {
protected Map<String, PcodeUseropDefinition<T>> ops = new HashMap<>();
private Map<String, PcodeUseropDefinition<T>> unmodifiableOps =
Collections.unmodifiableMap(ops);
/**
* Add the given userop to this library
*
* @param userop the userop
*/
protected void putOp(PcodeUseropDefinition<T> userop) {
ops.put(userop.getName(), userop);
}
@Override
public Map<String, PcodeUseropDefinition<T>> getUserops() {
return unmodifiableOps;
}
}
@@ -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 <T> no type in particular, except to match any executor
*/
public class FixedSleighPcodeUseropDefinition<T> extends AbstractSleighPcodeUseropDefinition<T> {
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<Varnode> args) {
return definition.generateBody(args);
}
@Override
public PcodeProgram programFor(List<Varnode> 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;
}
}
@@ -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 <T> no type in particular, except to match any executor
*/
public class OverloadedSleighPcodeUseropDefinition<T>
extends AbstractSleighPcodeUseropDefinition<T> {
protected final Map<Integer, SignatureDef> definitions;
protected OverloadedSleighPcodeUseropDefinition(SleighLanguage language, String name,
Map<Integer, SignatureDef> 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<SignatureDef> getAllSignatures() {
return definitions.values();
}
private SignatureDef requireSignatureDef(List<Varnode> 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<Varnode> args) {
SignatureDef definition = requireSignatureDef(args);
return definition.generateBody(args);
}
@Override
public PcodeProgram programFor(List<Varnode> 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;
}
}
@@ -187,7 +187,6 @@ public class PairedPcodeArithmetic<L, R> implements PcodeArithmetic<Pair<L, R>>
@Override
public long sizeOf(Pair<L, R> value) {
return leftArith.sizeOf(value.getLeft());
// TODO: Assert that the right agrees? Nah. Some aux types have no size.
}
/**
@@ -49,6 +49,11 @@ public interface PcodeUseropLibrary<T> {
public Map<String, PcodeUseropDefinition<Object>> getUserops() {
return Map.of();
}
@Override
public PcodeUseropLibrary<Object> compose(PcodeUseropLibrary<Object> lib) {
return lib;
}
}
/**
@@ -269,13 +274,24 @@ public interface PcodeUseropLibrary<T> {
* 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<T> compose(PcodeUseropLibrary<T> 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<T> compose(PcodeUseropLibrary<T> lib) {
if (lib == null) {
return this;
}
return new ComposedPcodeUseropLibrary<>(List.of(this, lib));
return compose(lib, false);
}
/**
@@ -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.
*
* <p>
* 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)
*
* <p>
* 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
*
* <p>
* 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.
*
* <p>
* 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 <em>not</em> 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.
*
* <p>
* 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 <T> 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 <T> PcodeUseropLibrary<T> createUseropLibraryFromId(String id, SleighLanguage language,
PcodeArithmetic<T> arithmetic) {
List<PcodeUseropLibraryFactory> 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
*
* <p>
* 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.
*
* <p>
* See the caveats in
* {@link #createUseropLibraryFromId(String, SleighLanguage, PcodeArithmetic)} regarding
* agreement between language and arithmetic.
*
* @param <T> 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 <T> PcodeUseropLibrary<T> createUseropLibraryForLanguage(SleighLanguage language,
PcodeArithmetic<T> arithmetic) {
List<String> libIds = List.of(language.getProperty(KEY_USEROP_LIBS, "").split(","));
Map<String, PcodeUseropLibraryFactory> matches =
ClassSearcher.getInstances((PcodeUseropLibraryFactory.class))
.stream()
.filter(f -> libIds.contains(f.getId()))
.collect(Collectors.toMap(f -> f.getId(), f -> f));
PcodeUseropLibrary<T> 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
*
* <p>
* 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 <T> 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
*/
<T> PcodeUseropLibrary<T> create(SleighLanguage language, PcodeArithmetic<T> arithmetic);
}
@@ -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 <T> no type in particular, except to match any executor
*/
public class SleighPcodeUseropDefinition<T> implements PcodeUseropDefinition<T> {
public static final String OUT_SYMBOL_NAME = "__op_output";
public interface SleighPcodeUseropDefinition<T> extends PcodeUseropDefinition<T> {
/** 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<T> implements PcodeUseropDefinition<T>
* @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<String> 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
* <p>
* 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<String> 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<Varnode> 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<T> implements PcodeUseropDefinition<T>
* @param <T> no particular type, except to match the executor
* @return the definition
*/
public <T> SleighPcodeUseropDefinition<T> build() {
return new SleighPcodeUseropDefinition<>(factory.language, name, List.copyOf(params),
body.toString());
<T> SleighPcodeUseropDefinition<T> 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<String> 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<String> 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<String> signature, List<BodyFunc> body) {
/**
* Generate the body's source code for the given arguments
*
* @param args the argument varnodes
* @return the body
*/
public String generateBody(List<Varnode> args) {
return body.stream().map(b -> b.generate(args)).collect(Collectors.joining());
}
private final Map<List<Varnode>, 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<String> params,
String body) {
this.language = language;
this.name = name;
this.params = params;
this.body = body;
/**
* Get the Sleigh source that defines this userop
*
* <p>
* The body may or may not actually depend on the arguments. Ideally, it does not, but sometimes
* the body may vary depending on the <em>sizes</em> 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<Varnode> 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<T> implements PcodeUseropDefinition<T>
* <p>
* 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<Varnode> inArgs,
PcodeUseropLibrary<?> library) {
List<Varnode> 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<T> executor, PcodeUseropLibrary<T> library,
PcodeOp op, Varnode outArg, List<Varnode> 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<String> 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<T> getDefiningLibrary() {
return null;
}
PcodeProgram programFor(List<Varnode> args, PcodeUseropLibrary<?> library);
}
@@ -7,6 +7,7 @@
<property key="useNewFunctionStackAnalysis" value="true"/>
<property key="emulateInstructionStateModifierClass"
value="ghidra.program.emulation.AARCH64EmulateInstructionStateModifier"/>
<property key="useropLibs" value="aarch64"/>
<property key="assemblyRating:AARCH64:BE:64:v8A" value="PLATINUM"/>
<property key="assemblyRating:AARCH64:LE:64:v8A" value="PLATINUM"/>
</properties>
@@ -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();
}
}
}
}
@@ -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];
}
@@ -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 <T> PcodeUseropLibrary<T> create(SleighLanguage language,
PcodeArithmetic<T> arithmetic) {
return new AARCH64PcodeUseropLibrary<>(language);
}
public static class AARCH64PcodeUseropLibrary<T> extends DefaultPcodeUseropLibrary<T> {
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 <pos>;
__op_output = -n;
goto <done>;
<pos>:
__op_output = n;
<done>:
""").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 <skip%d>;\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("<skip%d>\n".formatted(i));
}
buf.append("__op_output = result;");
return buf.toString();
}
}
}
@@ -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<byte[]> lib = PcodeUseropLibraryFactory.createUseropLibraryFromId(
"aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64));
SleighPcodeUseropDefinition<byte[]> tbl =
(SleighPcodeUseropDefinition<byte[]>) 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<byte[]> lib = PcodeUseropLibraryFactory.createUseropLibraryFromId(
"aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64));
SleighPcodeUseropDefinition<byte[]> tbl =
(SleighPcodeUseropDefinition<byte[]>) 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<byte[]> lib = PcodeUseropLibraryFactory.createUseropLibraryFromId(
"aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64));
SleighPcodeUseropDefinition<byte[]> tbl =
(SleighPcodeUseropDefinition<byte[]>) 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<byte[]> lib = PcodeUseropLibraryFactory.createUseropLibraryFromId(
"aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64));
SleighPcodeUseropDefinition<byte[]> tbl =
(SleighPcodeUseropDefinition<byte[]>) 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<byte[]> lib = PcodeUseropLibraryFactory.createUseropLibraryFromId(
"aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64));
SleighPcodeUseropDefinition<byte[]> tbl =
(SleighPcodeUseropDefinition<byte[]>) 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<byte[]> lib = PcodeUseropLibraryFactory.createUseropLibraryFromId(
"aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64));
SleighPcodeUseropDefinition<byte[]> tbl =
(SleighPcodeUseropDefinition<byte[]>) 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<byte[]> lib = PcodeUseropLibraryFactory.createUseropLibraryFromId(
"aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64));
SleighPcodeUseropDefinition<byte[]> tbl =
(SleighPcodeUseropDefinition<byte[]>) 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<byte[]> lib = PcodeUseropLibraryFactory.createUseropLibraryFromId(
"aarch64", aarch64, BytesPcodeArithmetic.forLanguage(aarch64));
SleighPcodeUseropDefinition<byte[]> tbl =
(SleighPcodeUseropDefinition<byte[]>) 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<String, String> init, AssemblyBuffer buf,
Map<String, String> 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<String, String> ent : init.entrySet()) {
state.setValue(ent.getKey(), new BigInteger(ent.getValue(), 16));
}
emu.setExecuteAddress(entry);
emu.executeInstruction(false, monitor);
for (Map.Entry<String, String> ent : expected.entrySet()) {
assertEquals(ent.getValue(), state.getBigInteger(ent.getKey()).toString(16));
}
}
protected void doTestTBL_Equiv_new(Map<String, String> init, AssemblyBuffer buf,
Map<String, String> 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<byte[]> thread = emu.newThread();
PcodeExecutorState<byte[]> state = thread.getState();
PcodeArithmetic<byte[]> arithmetic = thread.getArithmetic();
for (Map.Entry<String, String> 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<String, String> 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<String, String> init, DoAsm doAsm,
Map<String, String> 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")));
}
}