From a0d6a5eabb8bf843ebd8f3b09a02a9daa63caa3e Mon Sep 17 00:00:00 2001
From: plucia
Date: Sat, 13 Jan 2024 17:50:54 -0500
Subject: [PATCH 1/3] Add a WildcardAssembler Plugin.
Allows assembling instructions containing wildcards. Includes support for filtering assembly results to only include some completions for each wildcard.
---
.../WildcardAssembler/Module.manifest | 0
.../Features/WildcardAssembler/build.gradle | 29 +
.../WildcardAssembler/certification.manifest | 6 +
.../src/main/help/help/TOC_Source.xml | 54 ++
.../Wildcard_Assembler.html | 90 ++
.../java/ghidra/asm/wild/WildOperandInfo.java | 30 +
.../ghidra/asm/wild/WildSleighAssembler.java | 50 ++
.../asm/wild/WildSleighAssemblerBuilder.java | 128 +++
.../wild/grammars/WildAssemblyProduction.java | 33 +
.../DefaultWildAssemblyResolvedPatterns.java | 243 ++++++
.../ghidra/asm/wild/sem/PatternUtils.java | 57 ++
.../WildAssemblyConstructStateGenerator.java | 36 +
...ildAssemblyFixedNumericStateGenerator.java | 48 ++
.../asm/wild/sem/WildAssemblyNopState.java | 86 ++
.../sem/WildAssemblyNopStateGenerator.java | 46 +
.../WildAssemblyNumericMapStateGenerator.java | 56 ++
.../WildAssemblyNumericStateGenerator.java | 50 ++
.../wild/sem/WildAssemblyOperandState.java | 88 ++
.../sem/WildAssemblyResolutionFactory.java | 47 ++
.../sem/WildAssemblyResolvedPatterns.java | 32 +
.../WildAssemblyStringMapStateGenerator.java | 57 ++
.../sem/WildAssemblyStringStateGenerator.java | 46 +
.../wild/sem/WildAssemblyTreeResolver.java | 85 ++
.../WildAssemblyFixedNumericTerminal.java | 30 +
.../wild/symbol/WildAssemblyNonTerminal.java | 33 +
.../WildAssemblyNumericMapTerminal.java | 32 +
.../symbol/WildAssemblyNumericTerminal.java | 37 +
.../symbol/WildAssemblyStringMapTerminal.java | 32 +
.../symbol/WildAssemblyStringTerminal.java | 35 +
.../symbol/WildAssemblySubtableTerminal.java | 46 +
.../asm/wild/symbol/WildAssemblyTerminal.java | 55 ++
.../tree/WildAssemblyParseHiddenNode.java | 46 +
.../asm/wild/tree/WildAssemblyParseToken.java | 191 +++++
.../asm/wild/WildSleighAssemblerTest.java | 788 ++++++++++++++++++
34 files changed, 2722 insertions(+)
create mode 100644 Ghidra/Features/WildcardAssembler/Module.manifest
create mode 100644 Ghidra/Features/WildcardAssembler/build.gradle
create mode 100644 Ghidra/Features/WildcardAssembler/certification.manifest
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildOperandInfo.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildSleighAssembler.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildSleighAssemblerBuilder.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/grammars/WildAssemblyProduction.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/DefaultWildAssemblyResolvedPatterns.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/PatternUtils.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyConstructStateGenerator.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyFixedNumericStateGenerator.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNopState.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNopStateGenerator.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNumericMapStateGenerator.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNumericStateGenerator.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyOperandState.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyResolutionFactory.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyResolvedPatterns.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyStringMapStateGenerator.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyStringStateGenerator.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyTreeResolver.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyFixedNumericTerminal.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNonTerminal.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNumericMapTerminal.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNumericTerminal.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyStringMapTerminal.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyStringTerminal.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblySubtableTerminal.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyTerminal.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/tree/WildAssemblyParseHiddenNode.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/tree/WildAssemblyParseToken.java
create mode 100644 Ghidra/Features/WildcardAssembler/src/test/java/ghidra/asm/wild/WildSleighAssemblerTest.java
diff --git a/Ghidra/Features/WildcardAssembler/Module.manifest b/Ghidra/Features/WildcardAssembler/Module.manifest
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/Ghidra/Features/WildcardAssembler/build.gradle b/Ghidra/Features/WildcardAssembler/build.gradle
new file mode 100644
index 00000000000..af0f8f30b5f
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/build.gradle
@@ -0,0 +1,29 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
+apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
+apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
+apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
+apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
+apply plugin: 'eclipse'
+eclipse.project.name = 'Features WildcardAssembler'
+
+dependencies {
+
+ api project(':Base')
+
+ testImplementation project(path: ':SoftwareModeling', configuration: 'testArtifacts')
+}
diff --git a/Ghidra/Features/WildcardAssembler/certification.manifest b/Ghidra/Features/WildcardAssembler/certification.manifest
new file mode 100644
index 00000000000..f08f615729b
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/certification.manifest
@@ -0,0 +1,6 @@
+##VERSION: 2.0
+##MODULE IP: FAMFAMFAM Icons - CC 2.5
+##MODULE IP: Oxygen Icons - LGPL 3.0
+Module.manifest||GHIDRA||||END|
+src/main/help/help/TOC_Source.xml||GHIDRA||reviewed||END|
+src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html||GHIDRA||||END|
diff --git a/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml b/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
new file mode 100644
index 00000000000..c0cdde2a7cd
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
diff --git a/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html b/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html
new file mode 100644
index 00000000000..c8647893acc
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+ Wildcard Assembler Plugin
+
+
+
+
+
Wildcard Assembler Plugin
+
+
+
The Wildcard Assembler extends Ghidra's assembler to enable
+ assembling instructions with specific tokens replaced with wildcards.
+
+
This assembler will return metadata for each wildcard in an assembled
+ instruction. This metadata includes details of which specific bits of an
+ assembled instruction are used to derive the value of the wildcarded token
+ and the expression used to derive the value.
+
+
Wildcard Syntax
+
+
Wildcards in instructions are specified by replacing the
+ to-be-wildcarded token with a wildcard name surrounded by backticks (e.g.
+ `Q1` where Q1 is an arbitrary wildcard name) and passing the
+ entire instruction to the Wildcard Assembler.
+
+
By default the Wildcard Assembler will return metadata about all
+ possible values that a wildcarded token could take and all the encodings
+ of all these values. This behavior can be limited by filtering the
+ wildcard by appending specific syntax after the wildcard name:
+
+
+
Numeric Filter:
+
+
Appending [..] will constrain the wildcarded token
+ to only numeric values (and not registers or other strings).
+
Appending [0x0..0x100] (where 0x0 and 0x100 are
+ arbitrary hexadecimal values with the smaller number first) will
+ constrain the wildcarded token to only numeric values between the
+ two given values. This can be used to ensure that the returned
+ encodings can hold values of a desired size. Multiple non-contiguous
+ ranges can be specified by separating them with commas (e.g.
+ [0x0..0x5,0x1000-0x4000])
+
+
+
Regex Filter:
+
+
Appending /ABCD where ABCD is an arbitrary
+ regular expression will constrain the wildcarded token to only be
+ string tokens matching the given regular expression. This is most
+ likely used for filtering register names; for example appending
+ /(sp)|(lr) to a wildcard in a register position in
+ ARM assembly will limit the wildcard results to only encodings
+ using the sp or lr registers in that
+ position.
+
+
+
+
+
Normally, a wildcard will only match a single token. To allow a single
+ wildcard to match multiple related tokens: precede the wildcard name with a
+ ! character. For example, in a x86:LE:32:default binary:
+
+
+
Assembles (no wildcard):
+
MOVSD.REP ES:EDI,ESI
+
+
Assembles (single token):
+
MOVSD.REP `Q1`:EDI,ESI
+
+
Assembles (single token):
+
MOVSD.REP ES:`Q2`,ESI
+
+
Does NOT assemble (single token):
+
MOVSD.REP `Q3`,ESI
+
+
Assembles (multi-token):
+
MOVSD.REP `!Q4`,ESI
+
+
+
+
Provided by: Wildcard Assembler Plugin
+
+
+
+
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildOperandInfo.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildOperandInfo.java
new file mode 100644
index 00000000000..bacec42be04
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildOperandInfo.java
@@ -0,0 +1,30 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild;
+
+import java.util.List;
+
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyConstructorSemantic;
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
+import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
+
+public record WildOperandInfo(String wildcard, List path,
+ AssemblyPatternBlock location, PatternExpression expression, Object choice) {
+
+ public WildOperandInfo shift(int amt) {
+ return new WildOperandInfo(wildcard, path, location.shift(amt), expression, choice);
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildSleighAssembler.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildSleighAssembler.java
new file mode 100644
index 00000000000..6117cdf71be
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildSleighAssembler.java
@@ -0,0 +1,50 @@
+/* ###
+ * 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.asm.wild;
+
+import ghidra.app.plugin.assembler.AssemblySelector;
+import ghidra.app.plugin.assembler.sleigh.AbstractSleighAssembler;
+import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParser;
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch;
+import ghidra.app.plugin.processors.sleigh.SleighLanguage;
+import ghidra.asm.wild.sem.WildAssemblyResolvedPatterns;
+import ghidra.asm.wild.sem.WildAssemblyTreeResolver;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.listing.Program;
+
+public class WildSleighAssembler extends AbstractSleighAssembler {
+
+ protected WildSleighAssembler(
+ AbstractAssemblyResolutionFactory factory,
+ AssemblySelector selector, SleighLanguage lang, AssemblyParser parser,
+ AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) {
+ super(factory, selector, lang, parser, defaultContext, ctxGraph);
+ }
+
+ protected WildSleighAssembler(
+ AbstractAssemblyResolutionFactory factory,
+ AssemblySelector selector, Program program, AssemblyParser parser,
+ AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) {
+ super(factory, selector, program, parser, defaultContext, ctxGraph);
+ }
+
+ @Override
+ protected WildAssemblyTreeResolver newResolver(Address at, AssemblyParseBranch tree,
+ AssemblyPatternBlock ctx) {
+ return new WildAssemblyTreeResolver(factory, lang, at, tree, ctx, ctxGraph);
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildSleighAssemblerBuilder.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildSleighAssemblerBuilder.java
new file mode 100644
index 00000000000..ed99170351a
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/WildSleighAssemblerBuilder.java
@@ -0,0 +1,128 @@
+/* ###
+ * 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.asm.wild;
+
+import java.util.*;
+
+import ghidra.app.plugin.assembler.AssemblySelector;
+import ghidra.app.plugin.assembler.sleigh.AbstractSleighAssemblerBuilder;
+import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
+import ghidra.app.plugin.assembler.sleigh.grammars.AssemblySentential;
+import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory;
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedBackfill;
+import ghidra.app.plugin.assembler.sleigh.symbol.*;
+import ghidra.app.plugin.processors.sleigh.Constructor;
+import ghidra.app.plugin.processors.sleigh.SleighLanguage;
+import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern;
+import ghidra.asm.wild.grammars.WildAssemblyProduction;
+import ghidra.asm.wild.sem.WildAssemblyResolutionFactory;
+import ghidra.asm.wild.sem.WildAssemblyResolvedPatterns;
+import ghidra.asm.wild.symbol.*;
+import ghidra.program.model.listing.Program;
+
+public class WildSleighAssemblerBuilder
+ extends AbstractSleighAssemblerBuilder {
+
+ protected final Map wildNTs = new HashMap<>();
+
+ public WildSleighAssemblerBuilder(SleighLanguage lang) {
+ super(lang);
+ }
+
+ @Override
+ protected AbstractAssemblyResolutionFactory< //
+ WildAssemblyResolvedPatterns, AssemblyResolvedBackfill> newResolutionFactory() {
+ return new WildAssemblyResolutionFactory();
+ }
+
+ protected WildAssemblyTerminal generateWildTerminal(AssemblySymbol t) {
+ if (t instanceof AssemblyNonTerminal nt) {
+ if ("instruction".equals(nt.getName())) {
+ // Never allow full instruction to be wildcarded
+ return null;
+ }
+ return new WildAssemblySubtableTerminal(nt.getName());
+ }
+ if (t instanceof AssemblyFixedNumericTerminal term) {
+ return new WildAssemblyFixedNumericTerminal(term.getVal());
+ }
+ if (t instanceof AssemblyNumericMapTerminal term) {
+ return new WildAssemblyNumericMapTerminal(term.getName(), term.getMap());
+ }
+ if (t instanceof AssemblyNumericTerminal term) {
+ return new WildAssemblyNumericTerminal(term.getName(), term.getBitSize(),
+ term.getSpace());
+ }
+ if (t instanceof AssemblyStringMapTerminal term) {
+ return new WildAssemblyStringMapTerminal(term.getName(), term.getMap());
+ }
+ if (t instanceof AssemblyStringTerminal term && term.getDefiningSymbol() != null) {
+ return new WildAssemblyStringTerminal(term.getString());
+ }
+ /**
+ * Exclude string terminals. These should be purely syntactic elements. Use of them as fixed
+ * literals, e.g., 1 or RAX, is an error on the spec's part.
+ */
+ return null;
+ }
+
+ protected AssemblyNonTerminal createWildNonTerminal(AssemblySymbol s) {
+ WildAssemblyTerminal wt = generateWildTerminal(s);
+ if (wt == null) {
+ return null;
+ }
+ WildAssemblyNonTerminal nt =
+ new WildAssemblyNonTerminal("w`" + s.getName(), s.takesOperandIndex());
+ grammar.addProduction(new WildAssemblyProduction(nt, new AssemblySentential<>(s)));
+ grammar.addProduction(new WildAssemblyProduction(nt, new AssemblySentential<>(wt)));
+ return nt;
+ }
+
+ protected AssemblyNonTerminal getOrCreateWildNonTerminal(AssemblySymbol s) {
+ return wildNTs.computeIfAbsent(s, this::createWildNonTerminal);
+ }
+
+ protected AssemblySymbol maybeReplaceSymbol(AssemblySymbol s) {
+ AssemblyNonTerminal nt = getOrCreateWildNonTerminal(s);
+ if (nt == null) {
+ return s;
+ }
+ return nt;
+ }
+
+ @Override
+ protected void addProduction(AssemblyGrammar subgrammar, AssemblyNonTerminal lhs,
+ AssemblySentential rhs, DisjointPattern pattern, Constructor cons,
+ List indices) {
+ // Don't call super. We want to replace the original production
+ AssemblySentential wildRhs = new AssemblySentential<>();
+ for (AssemblySymbol sym : rhs.getSymbols()) {
+ wildRhs.addSymbol(maybeReplaceSymbol(sym));
+ }
+ subgrammar.addProduction(lhs, wildRhs, pattern, cons, indices);
+ }
+
+ @Override
+ protected WildSleighAssembler newAssembler(AssemblySelector selector) {
+ return new WildSleighAssembler(factory, selector, lang, parser, defaultContext, ctxGraph);
+ }
+
+ @Override
+ protected WildSleighAssembler newAssembler(AssemblySelector selector, Program program) {
+ return new WildSleighAssembler(factory, selector, program, parser, defaultContext,
+ ctxGraph);
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/grammars/WildAssemblyProduction.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/grammars/WildAssemblyProduction.java
new file mode 100644
index 00000000000..93ea8d1ae5f
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/grammars/WildAssemblyProduction.java
@@ -0,0 +1,33 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.grammars;
+
+import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyProduction;
+import ghidra.app.plugin.assembler.sleigh.grammars.AssemblySentential;
+import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal;
+
+public class WildAssemblyProduction extends AssemblyProduction {
+
+ public WildAssemblyProduction(AssemblyNonTerminal lhs,
+ AssemblySentential rhs) {
+ super(lhs, rhs);
+ }
+
+ @Override
+ public boolean isConstructor() {
+ return false;
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/DefaultWildAssemblyResolvedPatterns.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/DefaultWildAssemblyResolvedPatterns.java
new file mode 100644
index 00000000000..701a969d382
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/DefaultWildAssemblyResolvedPatterns.java
@@ -0,0 +1,243 @@
+/* ###
+ * 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.asm.wild.sem;
+
+import java.util.*;
+
+import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong;
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolutionBuilder;
+import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolvedPatternsBuilder;
+import ghidra.app.plugin.processors.sleigh.Constructor;
+import ghidra.app.plugin.processors.sleigh.ContextOp;
+import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
+import ghidra.asm.wild.WildOperandInfo;
+import ghidra.asm.wild.sem.WildAssemblyResolutionFactory.WildAssemblyResolvedPatternsBuilder;
+
+public class DefaultWildAssemblyResolvedPatterns extends DefaultAssemblyResolvedPatterns
+ implements WildAssemblyResolvedPatterns {
+
+ protected final WildAssemblyResolutionFactory factory;
+ protected final Set opInfo;
+
+ protected DefaultWildAssemblyResolvedPatterns(WildAssemblyResolutionFactory factory,
+ String description, Constructor cons, List extends AssemblyResolution> children,
+ AssemblyResolution right, AssemblyPatternBlock ins, AssemblyPatternBlock ctx,
+ Set backfills, Set forbids,
+ Set opInfo) {
+ super(factory, description, cons, children, right, ins, ctx, backfills, forbids);
+ this.factory = factory;
+ this.opInfo = opInfo == null ? Set.of() : Collections.unmodifiableSet(opInfo);
+ }
+
+ @Override
+ public Set getOperandInfo() {
+ return opInfo;
+ }
+
+ @Override
+ protected int computeHash() {
+ int result = super.computeHash();
+ result *= 31;
+ result += Objects.hashCode(opInfo);
+ return result;
+ }
+
+ protected boolean wildPartsEqual(DefaultWildAssemblyResolvedPatterns that) {
+ if (!partsEqual(that)) {
+ return false;
+ }
+ if (!Objects.equals(this.opInfo, that.opInfo)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+ DefaultWildAssemblyResolvedPatterns that = (DefaultWildAssemblyResolvedPatterns) obj;
+ return wildPartsEqual(that);
+ }
+
+ @Override
+ public String lineToString() {
+ return "WILD:" + super.lineToString();
+ }
+
+ @Override
+ protected String childrenToString(String indent) {
+ if (opInfo.isEmpty()) {
+ return super.childrenToString(indent);
+ }
+ StringBuilder sb = new StringBuilder();
+ sb.append(indent + "opInfo\n:");
+ for (WildOperandInfo i : opInfo) {
+ sb.append(indent + " " + i + "\n");
+ }
+ sb.append(super.childrenToString(indent));
+ return sb.toString();
+ }
+
+ protected WildAssemblyResolvedPatternsBuilder withWildInfoBuilder(String wildcard,
+ List path, AssemblyPatternBlock location,
+ PatternExpression expression, Object choice) {
+ var builder = factory.newPatternsBuilder();
+ builder.copyFromDefault(this);
+ var newOpInfo = new WildOperandInfo(wildcard, path, location, expression, choice);
+ if (opInfo.isEmpty()) {
+ builder.opInfo = Set.of(newOpInfo);
+ } else {
+ builder.opInfo = new HashSet<>(opInfo);
+ builder.opInfo.add(newOpInfo);
+ }
+ return builder;
+ }
+
+ @Override
+ public WildAssemblyResolvedPatterns withWildInfo(String wildcard,
+ List path, AssemblyPatternBlock location,
+ PatternExpression expression, Object choice) {
+ return withWildInfoBuilder(wildcard, path, location, expression, choice).build();
+ }
+
+ protected WildAssemblyResolvedPatterns cast(AssemblyResolvedPatterns pat) {
+ return (WildAssemblyResolvedPatterns) pat;
+ }
+
+ protected WildAssemblyResolvedPatternsBuilder cast(
+ AbstractAssemblyResolvedPatternsBuilder> builder) {
+ return (WildAssemblyResolvedPatternsBuilder) builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder shiftBuilder(int amt) {
+ var builder = cast(super.shiftBuilder(amt));
+ builder.opInfo = new HashSet<>();
+ for (WildOperandInfo info : opInfo) {
+ builder.opInfo.add(info.shift(amt));
+ }
+ return builder;
+ }
+
+ // NOTE: Do not override truncateBuilder. The docs say only used for reading a context op.
+
+ @Override
+ protected AbstractAssemblyResolutionBuilder, ?> checkNotForbiddenBuilder() {
+ var builder = super.checkNotForbiddenBuilder();
+ if (builder instanceof WildAssemblyResolvedPatternsBuilder pb) {
+ pb.opInfo = opInfo;
+ }
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder combineBuilder(AssemblyResolvedPatterns pat) {
+ var builder = cast(super.combineBuilder(pat));
+ if (builder != null) {
+ builder.opInfo = new HashSet<>(this.opInfo);
+ builder.opInfo.addAll(cast(pat).getOperandInfo());
+ }
+ return builder;
+ }
+
+ // NOTE: Do not override combineLessBackfill. Taken care of by combineBuilder.
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder combineBuilder(AssemblyResolvedBackfill bf) {
+ var builder = cast(super.combineBuilder(bf));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder withForbidsBuilder(
+ Set more) {
+ var builder = cast(super.withForbidsBuilder(more));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder withDescriptionBuilder(String description) {
+ var builder = cast(super.withDescriptionBuilder(description));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder withConstructorBuilder(Constructor cons) {
+ var builder = cast(super.withConstructorBuilder(cons));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder writeContextOpBuilder(ContextOp cop,
+ MaskedLong val) {
+ var builder = cast(super.writeContextOpBuilder(cop, val));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder copyAppendDescriptionBuilder(String append) {
+ var builder = cast(super.copyAppendDescriptionBuilder(append));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder withRightBuilder(AssemblyResolution right) {
+ var builder = cast(super.withRightBuilder(right));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder nopLeftSiblingBuilder() {
+ var builder = cast(super.nopLeftSiblingBuilder());
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder parentBuilder(String description, int opCount) {
+ var builder = cast(super.parentBuilder(description, opCount));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder maskOutBuilder(ContextOp cop) {
+ var builder = cast(super.maskOutBuilder(cop));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+
+ @Override
+ protected WildAssemblyResolvedPatternsBuilder solveContextChangesForForbidsBuilder(
+ AssemblyConstructorSemantic sem, Map vals) {
+ var builder = cast(super.solveContextChangesForForbidsBuilder(sem, vals));
+ builder.opInfo = opInfo;
+ return builder;
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/PatternUtils.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/PatternUtils.java
new file mode 100644
index 00000000000..a3194f5eb27
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/PatternUtils.java
@@ -0,0 +1,57 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.sem;
+
+import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong;
+import ghidra.app.plugin.assembler.sleigh.expr.OperandValueSolver;
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.Constructor;
+import ghidra.app.plugin.processors.sleigh.expression.*;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+
+public class PatternUtils {
+ private PatternUtils() {
+ }
+
+ public static WildAssemblyResolvedPatterns castWild(AssemblyResolvedPatterns rp) {
+ return (WildAssemblyResolvedPatterns) rp;
+ }
+
+ public static AssemblyPatternBlock collectLocation(PatternExpression exp) {
+ if (exp instanceof BinaryExpression bin) {
+ return collectLocation(bin.getLeft()).combine(collectLocation(bin.getRight()));
+ }
+ if (exp instanceof UnaryExpression un) {
+ return collectLocation(un.getUnary());
+ }
+ if (exp instanceof ContextField cf) {
+ // TODO: I'm not sure how to capture info for operands that go temporarily into context
+ return AssemblyPatternBlock.nop();
+ }
+ if (exp instanceof TokenField tf) {
+ return AssemblyPatternBlock.fromTokenField(tf, MaskedLong.ONES);
+ }
+ if (exp instanceof OperandValue ov) {
+ // I still have a lot of uncertainty as to what an OperandValue is
+ Constructor cons = ov.getConstructor();
+ OperandSymbol sym = cons.getOperand(ov.getIndex());
+ PatternExpression patexp = OperandValueSolver.getDefiningExpression(sym);
+ return collectLocation(patexp).shift(AssemblyTreeResolver.computeOffset(sym, cons));
+ }
+ // constant, start, end, next2
+ return AssemblyPatternBlock.nop();
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyConstructStateGenerator.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyConstructStateGenerator.java
new file mode 100644
index 00000000000..6e5e83751ce
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyConstructStateGenerator.java
@@ -0,0 +1,36 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.sem;
+
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseTreeNode;
+import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol;
+import ghidra.asm.wild.tree.WildAssemblyParseHiddenNode;
+
+public class WildAssemblyConstructStateGenerator extends AssemblyHiddenConstructStateGenerator {
+ protected final String wildcard;
+
+ public WildAssemblyConstructStateGenerator(AbstractAssemblyTreeResolver> resolver,
+ SubtableSymbol subtableSym, String wildcard, AssemblyResolvedPatterns fromLeft) {
+ super(resolver, subtableSym, fromLeft);
+ this.wildcard = wildcard;
+ }
+
+ @Override
+ protected AssemblyParseTreeNode getFiller() {
+ return new WildAssemblyParseHiddenNode(resolver.getGrammar(), wildcard);
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyFixedNumericStateGenerator.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyFixedNumericStateGenerator.java
new file mode 100644
index 00000000000..5433aa359aa
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyFixedNumericStateGenerator.java
@@ -0,0 +1,48 @@
+/* ###
+ * 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.asm.wild.sem;
+
+import java.util.stream.Stream;
+
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+import ghidra.asm.wild.tree.WildAssemblyParseToken;
+import ghidra.asm.wild.tree.WildAssemblyParseToken.RegexWildcard;
+
+public class WildAssemblyFixedNumericStateGenerator
+ extends AbstractAssemblyStateGenerator {
+
+ protected final OperandSymbol opSym;
+ protected final long val;
+
+ public WildAssemblyFixedNumericStateGenerator(
+ AbstractAssemblyTreeResolver> resolver, WildAssemblyParseToken node,
+ OperandSymbol opSym, long val, AssemblyResolvedPatterns fromLeft) {
+ super(resolver, node, fromLeft);
+ this.opSym = opSym;
+ this.val = val;
+ }
+
+ @Override
+ public Stream generate(GeneratorContext gc) {
+ if (!node.wild.test(val)) {
+ return Stream.of();
+ }
+ return Stream.of(
+ new AssemblyGeneratedPrototype(new WildAssemblyOperandState(resolver, gc.path, gc.shift,
+ node.getSym(), val, opSym, node.wildcardName(), val), fromLeft));
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNopState.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNopState.java
new file mode 100644
index 00000000000..1ca7caaacbb
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNopState.java
@@ -0,0 +1,86 @@
+/* ###
+ * 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.asm.wild.sem;
+
+import java.util.*;
+import java.util.stream.Stream;
+
+import ghidra.app.plugin.assembler.sleigh.expr.OperandValueSolver;
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+
+public class WildAssemblyNopState extends AssemblyNopState {
+ protected final OperandSymbol opSym; // super just uses it for length
+ protected final String wildcard;
+
+ public WildAssemblyNopState(AbstractAssemblyTreeResolver> resolver,
+ List path, int shift, OperandSymbol opSym,
+ String wildcard) {
+ super(resolver, path, shift, opSym);
+ this.opSym = opSym;
+ this.wildcard = Objects.requireNonNull(wildcard);
+ }
+
+ @Override
+ public int computeHash() {
+ int result = super.computeHash();
+ result *= 31;
+ result += wildcard.hashCode();
+ return result;
+ }
+
+ protected boolean wildPartsEqual(WildAssemblyNopState that) {
+ if (!partsEqual(that)) {
+ return false;
+ }
+ if (!this.wildcard.equals(that.wildcard)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+ WildAssemblyNopState that = (WildAssemblyNopState) obj;
+ return wildPartsEqual(that);
+ }
+
+ @Override
+ public String toString() {
+ return "WILD:" + super.toString();
+ }
+
+ protected WildAssemblyResolvedPatterns castWildPatterns(AssemblyResolvedPatterns rp) {
+ return (WildAssemblyResolvedPatterns) rp;
+ }
+
+ @Override
+ protected Stream resolve(AssemblyResolvedPatterns fromRight,
+ Collection errors) {
+ PatternExpression symExp = OperandValueSolver.getDefiningExpression(opSym);
+ AssemblyPatternBlock location = PatternUtils.collectLocation(symExp).shift(shift);
+ return super.resolve(fromRight, errors)
+ .map(PatternUtils::castWild)
+ .map(r -> r.withWildInfo(wildcard, path, location, symExp, null));
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNopStateGenerator.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNopStateGenerator.java
new file mode 100644
index 00000000000..e5101e39a87
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNopStateGenerator.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.asm.wild.sem;
+
+import java.util.stream.Stream;
+
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+import ghidra.asm.wild.tree.WildAssemblyParseToken;
+
+public class WildAssemblyNopStateGenerator
+ extends AbstractAssemblyStateGenerator {
+
+ protected final OperandSymbol opSym;
+ protected final String wildcard;
+
+ public WildAssemblyNopStateGenerator(WildAssemblyTreeResolver resolver,
+ WildAssemblyParseToken node, OperandSymbol opSym, String wildcard,
+ AssemblyResolvedPatterns fromLeft) {
+ super(resolver, node, fromLeft);
+ this.opSym = opSym;
+ this.wildcard = wildcard;
+ }
+
+ @Override
+ public Stream generate(GeneratorContext gc) {
+ // TODO: Do we want to restrict the values?
+ // TODO: Is this the right place to generate "interesting values"?
+ gc.dbg("Generating WILD NOP for " + opSym);
+ return Stream.of(new AssemblyGeneratedPrototype(
+ new WildAssemblyNopState(resolver, gc.path, gc.shift, opSym, wildcard), fromLeft));
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNumericMapStateGenerator.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNumericMapStateGenerator.java
new file mode 100644
index 00000000000..061c2c0f227
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNumericMapStateGenerator.java
@@ -0,0 +1,56 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.sem;
+
+import java.util.Map;
+import java.util.stream.Stream;
+
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+import ghidra.asm.wild.tree.WildAssemblyParseToken;
+import ghidra.asm.wild.tree.WildAssemblyParseToken.RegexWildcard;
+
+public class WildAssemblyNumericMapStateGenerator
+ extends AbstractAssemblyStateGenerator {
+
+ protected final OperandSymbol opSym;
+ protected final Map map;
+
+ public WildAssemblyNumericMapStateGenerator(WildAssemblyTreeResolver resolver,
+ WildAssemblyParseToken node, OperandSymbol opSym, Map map,
+ AssemblyResolvedPatterns fromLeft) {
+ super(resolver, node, fromLeft);
+ this.opSym = opSym;
+ this.map = map;
+ }
+
+ @Override
+ public Stream generate(GeneratorContext gc) {
+ // TODO: If all values are represented, perhaps just leave the bits unspecified.
+ // I'll lose the choice information, though....
+ if (node.wild instanceof RegexWildcard) {
+ // Faster to recognize this up front, instead of streaming and filtering all out
+ return Stream.of();
+ }
+ return map.entrySet()
+ .stream()
+ .filter(e -> node.wild.test(e.getKey()))
+ .map(e -> new AssemblyGeneratedPrototype(
+ new WildAssemblyOperandState(resolver, gc.path, gc.shift, node.getSym(),
+ e.getValue(), opSym, node.wildcardName(), e.getKey()),
+ fromLeft));
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNumericStateGenerator.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNumericStateGenerator.java
new file mode 100644
index 00000000000..9c2ae5aed0a
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyNumericStateGenerator.java
@@ -0,0 +1,50 @@
+/* ###
+ * 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.asm.wild.sem;
+
+import java.util.stream.Stream;
+
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyGeneratedPrototype;
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+import ghidra.asm.wild.tree.WildAssemblyParseToken;
+import ghidra.asm.wild.tree.WildAssemblyParseToken.*;
+
+public class WildAssemblyNumericStateGenerator extends WildAssemblyNopStateGenerator {
+
+ public WildAssemblyNumericStateGenerator(WildAssemblyTreeResolver resolver,
+ WildAssemblyParseToken node, OperandSymbol opSym, String wildcard,
+ AssemblyResolvedPatterns fromLeft) {
+ super(resolver, node, opSym, wildcard, fromLeft);
+ }
+
+ @Override
+ public Stream generate(GeneratorContext gc) {
+ if (node.wild instanceof RegexWildcard) {
+ return Stream.of();
+ }
+ if (node.wild instanceof FreeWildcard || node.wild instanceof NumericWildcard) {
+ return super.generate(gc);
+ }
+ if (node.wild instanceof RangesWildcard wild) {
+ return wild.stream()
+ .mapToObj(v -> new AssemblyGeneratedPrototype(new WildAssemblyOperandState(
+ resolver, gc.path, gc.shift, node.getSym(), v, opSym, node.wildcardName(),
+ v), fromLeft));
+ }
+ throw new AssertionError();
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyOperandState.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyOperandState.java
new file mode 100644
index 00000000000..a6e8c6e7e00
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyOperandState.java
@@ -0,0 +1,88 @@
+/* ###
+ * 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.asm.wild.sem;
+
+import java.util.*;
+import java.util.stream.Stream;
+
+import ghidra.app.plugin.assembler.sleigh.expr.OperandValueSolver;
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyTerminal;
+import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+
+public class WildAssemblyOperandState extends AssemblyOperandState {
+ protected final String wildcard;
+ protected final Object choice;
+
+ protected WildAssemblyOperandState(AbstractAssemblyTreeResolver> resolver,
+ List path, int shift, AssemblyTerminal terminal,
+ long value, OperandSymbol opSym, String wildcard, Object choice) {
+ super(resolver, path, shift, terminal, value, opSym);
+ this.wildcard = Objects.requireNonNull(wildcard);
+ this.choice = choice;
+ }
+
+ @Override
+ public int computeHash() {
+ int result = super.computeHash();
+ result *= 31;
+ result += wildcard.hashCode();
+ result *= 31;
+ result += choice.hashCode();
+ return result;
+ }
+
+ protected boolean wildPartsEqual(WildAssemblyOperandState that) {
+ if (!partsEqual(that)) {
+ return false;
+ }
+ if (!this.wildcard.equals(that.wildcard)) {
+ return false;
+ }
+ if (!this.choice.equals(that.choice)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (this.getClass() != obj.getClass()) {
+ return false;
+ }
+ WildAssemblyOperandState that = (WildAssemblyOperandState) obj;
+ return wildPartsEqual(that);
+ }
+
+ @Override
+ public String toString() {
+ return "WILD:" + super.toString();
+ }
+
+ @Override
+ protected Stream resolve(AssemblyResolvedPatterns fromRight,
+ Collection errors) {
+ PatternExpression symExp = OperandValueSolver.getDefiningExpression(opSym);
+ AssemblyPatternBlock location = PatternUtils.collectLocation(symExp).shift(shift);
+ return super.resolve(fromRight, errors)
+ .map(PatternUtils::castWild)
+ .map(r -> r.withWildInfo(wildcard, path, location, symExp, choice));
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyResolutionFactory.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyResolutionFactory.java
new file mode 100644
index 00000000000..a418d253c6f
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyResolutionFactory.java
@@ -0,0 +1,47 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.sem;
+
+import java.util.Set;
+
+import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory;
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedBackfill;
+import ghidra.asm.wild.WildOperandInfo;
+
+public class WildAssemblyResolutionFactory extends
+ AbstractAssemblyResolutionFactory {
+
+ protected class WildAssemblyResolvedPatternsBuilder
+ extends AbstractAssemblyResolvedPatternsBuilder {
+ protected Set opInfo;
+
+ @Override
+ protected WildAssemblyResolvedPatterns build() {
+ return new DefaultWildAssemblyResolvedPatterns(WildAssemblyResolutionFactory.this,
+ description, cons, children, right, ins, ctx, backfills, forbids, opInfo);
+ }
+ }
+
+ @Override
+ public WildAssemblyResolvedPatternsBuilder newPatternsBuilder() {
+ return new WildAssemblyResolvedPatternsBuilder();
+ }
+
+ @Override
+ public DefaultAssemblyResolvedBackfillBuilder newBackfillBuilder() {
+ return new DefaultAssemblyResolvedBackfillBuilder();
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyResolvedPatterns.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyResolvedPatterns.java
new file mode 100644
index 00000000000..3ab0fd5dc0a
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyResolvedPatterns.java
@@ -0,0 +1,32 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.sem;
+
+import java.util.List;
+import java.util.Set;
+
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
+import ghidra.asm.wild.WildOperandInfo;
+
+public interface WildAssemblyResolvedPatterns extends AssemblyResolvedPatterns {
+
+ Set getOperandInfo();
+
+ WildAssemblyResolvedPatterns withWildInfo(String wildcard,
+ List path, AssemblyPatternBlock location,
+ PatternExpression expression, Object choice);
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyStringMapStateGenerator.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyStringMapStateGenerator.java
new file mode 100644
index 00000000000..6bd7608ec7c
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyStringMapStateGenerator.java
@@ -0,0 +1,57 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.sem;
+
+import java.util.stream.Stream;
+
+import org.apache.commons.collections4.MultiValuedMap;
+
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+import ghidra.asm.wild.tree.WildAssemblyParseToken;
+import ghidra.asm.wild.tree.WildAssemblyParseToken.NumericWildcard;
+import ghidra.asm.wild.tree.WildAssemblyParseToken.RangesWildcard;
+
+public class WildAssemblyStringMapStateGenerator
+ extends AbstractAssemblyStateGenerator {
+
+ protected final OperandSymbol opSym;
+ protected final MultiValuedMap map;
+
+ public WildAssemblyStringMapStateGenerator(WildAssemblyTreeResolver resolver,
+ WildAssemblyParseToken node, OperandSymbol opSym, MultiValuedMap map,
+ AssemblyResolvedPatterns fromLeft) {
+ super(resolver, node, fromLeft);
+ this.opSym = opSym;
+ this.map = map;
+ }
+
+ @Override
+ public Stream generate(GeneratorContext gc) {
+ // TODO: If all values are represented, perhaps just leave the bits unspecified.
+ // I'll lose the choice information, though....
+ if (node.wild instanceof RangesWildcard || node.wild instanceof NumericWildcard) {
+ return Stream.of();
+ }
+ return map.entries()
+ .stream()
+ .filter(e -> node.wild.test(e.getKey()))
+ .map(e -> new AssemblyGeneratedPrototype(
+ new WildAssemblyOperandState(resolver, gc.path, gc.shift, node.getSym(),
+ e.getValue(), opSym, node.wildcardName(), e.getKey()),
+ fromLeft));
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyStringStateGenerator.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyStringStateGenerator.java
new file mode 100644
index 00000000000..729a9a38ed2
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyStringStateGenerator.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.asm.wild.sem;
+
+import java.util.stream.Stream;
+
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
+import ghidra.asm.wild.tree.WildAssemblyParseToken;
+
+public class WildAssemblyStringStateGenerator
+ extends AbstractAssemblyStateGenerator {
+
+ protected final OperandSymbol opSym;
+ protected final String val;
+
+ public WildAssemblyStringStateGenerator(AbstractAssemblyTreeResolver> resolver,
+ WildAssemblyParseToken node, OperandSymbol opSym, String val,
+ AssemblyResolvedPatterns fromLeft) {
+ super(resolver, node, fromLeft);
+ this.opSym = opSym;
+ this.val = val;
+ }
+
+ @Override
+ public Stream generate(GeneratorContext gc) {
+ if (!node.wild.test(val)) {
+ return Stream.of();
+ }
+ return Stream.of(new AssemblyGeneratedPrototype(new WildAssemblyOperandState(resolver,
+ gc.path, gc.shift, node.getSym(), 0, opSym, node.wildcardName(), val), fromLeft));
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyTreeResolver.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyTreeResolver.java
new file mode 100644
index 00000000000..8447193fc56
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/sem/WildAssemblyTreeResolver.java
@@ -0,0 +1,85 @@
+/* ###
+ * 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.asm.wild.sem;
+
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch;
+import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseTreeNode;
+import ghidra.app.plugin.processors.sleigh.SleighLanguage;
+import ghidra.app.plugin.processors.sleigh.symbol.*;
+import ghidra.asm.wild.grammars.WildAssemblyProduction;
+import ghidra.asm.wild.symbol.*;
+import ghidra.asm.wild.tree.WildAssemblyParseHiddenNode;
+import ghidra.asm.wild.tree.WildAssemblyParseToken;
+import ghidra.program.model.address.Address;
+
+public class WildAssemblyTreeResolver
+ extends AbstractAssemblyTreeResolver {
+
+ public WildAssemblyTreeResolver(
+ AbstractAssemblyResolutionFactory factory,
+ SleighLanguage lang, Address at, AssemblyParseBranch tree, AssemblyPatternBlock context,
+ AssemblyContextGraph ctxGraph) {
+ super(factory, lang, at, tree, context, ctxGraph);
+ }
+
+ protected AbstractAssemblyStateGenerator> getWildHiddenStateGenerator(OperandSymbol opSym,
+ String wildcard, AssemblyResolvedPatterns fromLeft) {
+ TripleSymbol defSym = opSym.getDefiningSymbol();
+ if (defSym instanceof SubtableSymbol subtable) {
+ return new WildAssemblyConstructStateGenerator(this, subtable, wildcard, fromLeft);
+ }
+ return new WildAssemblyNopStateGenerator(this, null, opSym, wildcard, fromLeft);
+ }
+
+ @Override
+ protected AbstractAssemblyStateGenerator> getStateGenerator(OperandSymbol opSym,
+ AssemblyParseTreeNode node, AssemblyResolvedPatterns fromLeft) {
+ if (node instanceof WildAssemblyParseHiddenNode hidden) {
+ return getWildHiddenStateGenerator(opSym, hidden.wildcard, fromLeft);
+ }
+ if (node instanceof AssemblyParseBranch branch && !branch.isConstructor()) {
+ if (branch.getProduction() instanceof WildAssemblyProduction) {
+ assert branch.getSubstitutions().size() == 1;
+ return getStateGenerator(opSym, branch.getSubstitution(0), fromLeft);
+ }
+ }
+ if (!(node instanceof WildAssemblyParseToken token)) {
+ return super.getStateGenerator(opSym, node, fromLeft);
+ }
+ if (node.getSym() instanceof WildAssemblySubtableTerminal term) {
+ return getWildHiddenStateGenerator(opSym, token.wildcardName(), fromLeft);
+ }
+ if (node.getSym() instanceof WildAssemblyNumericMapTerminal term) {
+ return new WildAssemblyNumericMapStateGenerator(this, token, opSym, term.map, fromLeft);
+ }
+ if (node.getSym() instanceof WildAssemblyStringMapTerminal term) {
+ return new WildAssemblyStringMapStateGenerator(this, token, opSym, term.map, fromLeft);
+ }
+ if (node.getSym() instanceof WildAssemblyStringTerminal term) {
+ return new WildAssemblyStringStateGenerator(this, token, opSym, term.str, fromLeft);
+ }
+ if (node.getSym() instanceof WildAssemblyFixedNumericTerminal term) {
+ return new WildAssemblyFixedNumericStateGenerator(this, token, opSym, term.val,
+ fromLeft);
+ }
+ if (node.getSym() instanceof WildAssemblyNumericTerminal term) {
+ return new WildAssemblyNumericStateGenerator(this, token, opSym, token.wildcardName(),
+ fromLeft);
+ }
+ return super.getStateGenerator(opSym, node, fromLeft);
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyFixedNumericTerminal.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyFixedNumericTerminal.java
new file mode 100644
index 00000000000..bfe33a52162
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyFixedNumericTerminal.java
@@ -0,0 +1,30 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.symbol;
+
+public class WildAssemblyFixedNumericTerminal extends WildAssemblyTerminal {
+ public final long val;
+
+ public WildAssemblyFixedNumericTerminal(long val) {
+ super("WILD:" + val);
+ this.val = val;
+ }
+
+ @Override
+ public String toString() {
+ return "[wild:" + val + "]";
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNonTerminal.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNonTerminal.java
new file mode 100644
index 00000000000..ca57f011ced
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNonTerminal.java
@@ -0,0 +1,33 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.symbol;
+
+import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal;
+
+public class WildAssemblyNonTerminal extends AssemblyNonTerminal {
+
+ protected final boolean takesOperandIndex;
+
+ public WildAssemblyNonTerminal(String name, boolean takesOperandIndex) {
+ super(name);
+ this.takesOperandIndex = takesOperandIndex;
+ }
+
+ @Override
+ public boolean takesOperandIndex() {
+ return takesOperandIndex;
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNumericMapTerminal.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNumericMapTerminal.java
new file mode 100644
index 00000000000..f7445b6fb6e
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNumericMapTerminal.java
@@ -0,0 +1,32 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.symbol;
+
+import java.util.Map;
+
+public class WildAssemblyNumericMapTerminal extends WildAssemblyTerminal {
+ public final Map map;
+
+ public WildAssemblyNumericMapTerminal(String name, Map map) {
+ super(name);
+ this.map = map;
+ }
+
+ @Override
+ public String toString() {
+ return "[wildnum:" + name + "]";
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNumericTerminal.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNumericTerminal.java
new file mode 100644
index 00000000000..a2abd509474
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyNumericTerminal.java
@@ -0,0 +1,37 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.symbol;
+
+import ghidra.program.model.address.AddressSpace;
+
+public class WildAssemblyNumericTerminal extends WildAssemblyTerminal {
+ protected final int bitsize;
+ protected final AddressSpace space;
+
+ public WildAssemblyNumericTerminal(String name, int bitsize, AddressSpace space) {
+ super(name);
+ this.bitsize = bitsize;
+ this.space = space;
+ }
+
+ @Override
+ public String toString() {
+ if (bitsize == 0) {
+ return "[wildnum:" + name + "]";
+ }
+ return "[wildnum" + bitsize + ":" + name + "]";
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyStringMapTerminal.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyStringMapTerminal.java
new file mode 100644
index 00000000000..210d32eb5c4
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyStringMapTerminal.java
@@ -0,0 +1,32 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.symbol;
+
+import org.apache.commons.collections4.MultiValuedMap;
+
+public class WildAssemblyStringMapTerminal extends WildAssemblyTerminal {
+ public final MultiValuedMap map;
+
+ public WildAssemblyStringMapTerminal(String name, MultiValuedMap map) {
+ super(name);
+ this.map = map;
+ }
+
+ @Override
+ public String toString() {
+ return "[wildlist:" + name + "]";
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyStringTerminal.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyStringTerminal.java
new file mode 100644
index 00000000000..1512e8c76ed
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyStringTerminal.java
@@ -0,0 +1,35 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.symbol;
+
+public class WildAssemblyStringTerminal extends WildAssemblyTerminal {
+ public final String str;
+
+ public WildAssemblyStringTerminal(String str) {
+ super("\"" + str + "\"");
+ this.str = str;
+ }
+
+ @Override
+ public String toString() {
+ return "[wildstr:" + name + "]";
+ }
+
+ @Override
+ public boolean takesOperandIndex() {
+ return false;
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblySubtableTerminal.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblySubtableTerminal.java
new file mode 100644
index 00000000000..69efc79f988
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblySubtableTerminal.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.asm.wild.symbol;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNumericSymbols;
+
+public class WildAssemblySubtableTerminal extends WildAssemblyTerminal {
+ // Yes, leave the ! part of the spec/name
+ public static final Pattern PAT_WILD_TREE = Pattern.compile("`(?![^`]*)`");
+
+ public WildAssemblySubtableTerminal(String name) {
+ super("`WILD`-" + name);
+ }
+
+ @Override
+ public String toString() {
+ return "[" + name + "]";
+ }
+
+ @Override
+ protected Pattern getPattern() {
+ return PAT_WILD_TREE;
+ }
+
+ @Override
+ public Collection getSuggestions(String got, AssemblyNumericSymbols symbols) {
+ return List.of("`!Q1`");
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyTerminal.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyTerminal.java
new file mode 100644
index 00000000000..a42a6f027d2
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/symbol/WildAssemblyTerminal.java
@@ -0,0 +1,55 @@
+/* ###
+ * IP: GHIDRA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package ghidra.asm.wild.symbol;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
+import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNumericSymbols;
+import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyTerminal;
+import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseToken;
+import ghidra.asm.wild.tree.WildAssemblyParseToken;
+
+public abstract class WildAssemblyTerminal extends AssemblyTerminal {
+ public static final Pattern PAT_WILD = Pattern.compile("`(?[^`]*)`");
+
+ public WildAssemblyTerminal(String name) {
+ super(name);
+ }
+
+ protected Pattern getPattern() {
+ return PAT_WILD;
+ }
+
+ @Override
+ public Collection extends AssemblyParseToken> match(String buffer, int pos,
+ AssemblyGrammar grammar, AssemblyNumericSymbols symbols) {
+ Matcher matcher = getPattern().matcher(buffer).region(pos, buffer.length());
+ if (!matcher.lookingAt()) {
+ return List.of();
+ }
+ return List.of(
+ new WildAssemblyParseToken(grammar, this, matcher.group(), matcher.group("spec")));
+ }
+
+ @Override
+ public Collection getSuggestions(String got, AssemblyNumericSymbols symbols) {
+ return List.of("`Q1`");
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/tree/WildAssemblyParseHiddenNode.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/tree/WildAssemblyParseHiddenNode.java
new file mode 100644
index 00000000000..545669a7726
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/tree/WildAssemblyParseHiddenNode.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.asm.wild.tree;
+
+import java.io.PrintStream;
+
+import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
+import ghidra.app.plugin.assembler.sleigh.symbol.AssemblySymbol;
+import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseTreeNode;
+
+public class WildAssemblyParseHiddenNode extends AssemblyParseTreeNode {
+ public final String wildcard;
+
+ public WildAssemblyParseHiddenNode(AssemblyGrammar grammar, String wildcard) {
+ super(grammar);
+ this.wildcard = wildcard;
+ }
+
+ @Override
+ public AssemblySymbol getSym() {
+ return null;
+ }
+
+ @Override
+ protected void print(PrintStream out, String indent) {
+ out.print("");
+ }
+
+ @Override
+ public String generateString() {
+ return "";
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/tree/WildAssemblyParseToken.java b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/tree/WildAssemblyParseToken.java
new file mode 100644
index 00000000000..c35ebb9231b
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/main/java/ghidra/asm/wild/tree/WildAssemblyParseToken.java
@@ -0,0 +1,191 @@
+/* ###
+ * 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.asm.wild.tree;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.LongStream;
+import java.util.stream.Stream;
+
+import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
+import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyTerminal;
+import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseToken;
+
+public class WildAssemblyParseToken extends AssemblyParseToken {
+
+ public interface Wildcard {
+ public static Wildcard parse(String spec) {
+ Matcher matRegex = RegexWildcard.PATTERN.matcher(spec);
+ if (matRegex.matches()) {
+ return RegexWildcard.get(matRegex);
+ }
+ Matcher matNumeric = NumericWildcard.PATTERN.matcher(spec);
+ if (matNumeric.matches()) {
+ return NumericWildcard.get(matNumeric);
+ }
+ Matcher matRanges = RangesWildcard.PATTERN.matcher(spec);
+ if (matRanges.matches()) {
+ return RangesWildcard.get(matRanges);
+ }
+ return new FreeWildcard(spec);
+ }
+
+ public String name();
+
+ public boolean test(Object object);
+ }
+
+ public record FreeWildcard(String name) implements Wildcard {
+ @Override
+ public boolean test(Object object) {
+ return true;
+ }
+ }
+
+ public record RegexWildcard(String name, Pattern pat) implements Wildcard {
+ static final Pattern PATTERN = Pattern.compile("(?[^/]*)/(?.*)");
+
+ public static RegexWildcard get(Matcher matcher) {
+ return new RegexWildcard(matcher.group("name"),
+ Pattern.compile(matcher.group("regex")));
+ }
+
+ @Override
+ public boolean test(Object object) {
+ if (!(object instanceof CharSequence cs)) {
+ return false;
+ }
+ return pat.matcher(cs).matches();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ // Because Pattern does not override equals
+ return o instanceof RegexWildcard that &&
+ Objects.equals(this.name, that.name) &&
+ Objects.equals(this.pat.toString(), that.pat.toString());
+ }
+ }
+
+ public record NumericWildcard(String name) implements Wildcard {
+ static final Pattern PATTERN = Pattern.compile("(?.*)\\[\\.\\.\\]");
+
+ public static NumericWildcard get(Matcher matcher) {
+ return new NumericWildcard(matcher.group("name"));
+ }
+
+ @Override
+ public boolean test(Object object) {
+ return object instanceof Number;
+ }
+ }
+
+ // TODO: It's possible we'll eventually want BigInteger's here.
+ public record WildRange(long min, long max) implements Comparable {
+ public WildRange(long min, long max) {
+ if (min > max) {
+ throw new AssertionError("max > max");
+ }
+ this.min = min;
+ this.max = max;
+ }
+
+ public static WildRange parse(String str) {
+ String[] parts = str.split("\\.\\.");
+ if (parts.length == 1) {
+ long val = Long.decode(parts[0]);
+ return new WildRange(val, val);
+ }
+ if (parts.length == 2) {
+ long min = Long.decode(parts[0]);
+ long max = Long.decode(parts[1]);
+ return new WildRange(min, max);
+ }
+ throw new IllegalArgumentException("Invalid range specification in wildcard: " + str);
+ }
+
+ public LongStream stream() {
+ return LongStream.rangeClosed(min, max);
+ }
+
+ @Override
+ public int compareTo(WildRange that) {
+ return Long.compare(this.min, that.min);
+ }
+ }
+
+ public record RangesWildcard(String name, List ranges) implements Wildcard {
+ public static final Pattern PATTERN =
+ Pattern.compile("(?[^\\[]*)\\[(?[^\\]]*)\\]");
+
+ public static RangesWildcard get(Matcher matcher) {
+ return new RangesWildcard(matcher.group("name"), parseRanges(matcher.group("ranges")));
+ }
+
+ public static List parseRanges(String str) {
+ return Stream.of(str.split(",")).map(WildRange::parse).sorted().toList();
+ }
+
+ static long getLong(Object a) {
+ if (a instanceof Number n) {
+ return n.longValue();
+ }
+ if (a instanceof WildRange range) {
+ return range.min;
+ }
+ throw new AssertionError();
+ }
+
+ static int searchComp(Object a, Object b) {
+ return Long.compare(getLong(a), getLong(b));
+ }
+
+ public LongStream stream() {
+ return ranges.stream().flatMapToLong(i -> i.stream());
+ }
+
+ @Override
+ public boolean test(Object object) {
+ if (!(object instanceof Number n)) {
+ return false;
+ }
+ long lv = n.longValue();
+ int i = Collections.binarySearch(ranges, lv, RangesWildcard::searchComp);
+ if (i >= 0) {
+ return true; // We're exactly at one of the mins
+ }
+ // -i-1 is first index greater (ceiling). I want last index lesser (floor).
+ i = -i - 2;
+ if (i < 0) {
+ return false;
+ }
+ return lv <= ranges.get(i).max; // I already know lv >= min
+ }
+ }
+
+ public final Wildcard wild;
+
+ public WildAssemblyParseToken(AssemblyGrammar grammar, AssemblyTerminal term, String str,
+ String spec) {
+ super(grammar, term, str);
+ this.wild = Wildcard.parse(spec);
+ }
+
+ public String wildcardName() {
+ return wild.name();
+ }
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/test/java/ghidra/asm/wild/WildSleighAssemblerTest.java b/Ghidra/Features/WildcardAssembler/src/test/java/ghidra/asm/wild/WildSleighAssemblerTest.java
new file mode 100644
index 00000000000..c314941d663
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/src/test/java/ghidra/asm/wild/WildSleighAssemblerTest.java
@@ -0,0 +1,788 @@
+/* ###
+ * 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.asm.wild;
+
+import static org.hamcrest.Matchers.hasItem;
+import static org.junit.Assert.*;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.junit.Test;
+
+import generic.Unique;
+import ghidra.app.plugin.assembler.AssemblySelector;
+import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult;
+import ghidra.app.plugin.assembler.sleigh.sem.*;
+import ghidra.app.plugin.processors.sleigh.SleighLanguage;
+import ghidra.asm.wild.sem.WildAssemblyResolvedPatterns;
+import ghidra.asm.wild.symbol.WildAssemblyTerminal;
+import ghidra.asm.wild.tree.WildAssemblyParseToken.*;
+import ghidra.program.database.ProgramBuilder;
+import ghidra.program.model.address.Address;
+import ghidra.program.model.lang.LanguageID;
+import ghidra.program.model.listing.Program;
+import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
+import ghidra.test.ClassicSampleX86ProgramBuilder;
+import ghidra.util.NumericUtilities;
+
+public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTest {
+
+ static SleighLanguage toy;
+ static WildSleighAssembler asmToy;
+ static SleighLanguage arm;
+ static WildSleighAssembler asmArm;
+ static SleighLanguage mips;
+ static WildSleighAssembler asmMips;
+ static SleighLanguage x86;
+ static WildSleighAssembler asmX86;
+ static Program x86Program;
+ static SleighLanguage x8664;
+ static WildSleighAssembler asmX8664;
+ static Program x8664Program;
+
+ protected static void toy() throws Exception {
+ if (toy != null) {
+ return;
+ }
+ toy =
+ (SleighLanguage) getLanguageService().getLanguage(new LanguageID("Toy:BE:64:default"));
+ WildSleighAssemblerBuilder builder = new WildSleighAssemblerBuilder(toy);
+ asmToy = builder.getAssembler(new AssemblySelector());
+ }
+
+ protected static void arm() throws Exception {
+ if (arm != null) {
+ return;
+ }
+ ProgramBuilder armProgramBuilder = new ProgramBuilder("arm_le_test", "ARM:LE:32:v8");
+ armProgramBuilder.setBytes(String.format("0x%08X", 0x0),
+ "00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 fb ff ff eb 00 00 a0 e1");
+ Program armProgram = armProgramBuilder.getProgram();
+ arm = (SleighLanguage) armProgram.getLanguage();
+ WildSleighAssemblerBuilder builderArm = new WildSleighAssemblerBuilder(arm);
+ asmArm = builderArm.getAssembler(new AssemblySelector(), armProgram);
+ }
+
+ protected static void mips() throws Exception {
+ if (mips != null) {
+ return;
+ }
+ ProgramBuilder mipsProgramBuilder = new ProgramBuilder("mips_test", "MIPS:BE:32:default");
+ // The following is:
+ // 0x00000000: jalx 0x8
+ // 0x00000004: nop
+ // 0x00000008: restore 0x1b8,ra,s0-s1
+ // 0x0000000c: nop
+ mipsProgramBuilder.setBytes("0x00000000",
+ "0c 00 00 08 00 00 00 00 f0 30 64 77 00 00 00 00");
+ // This line sets the binary at addresses 0x8-0xc to be MIPS 16 (e.g. the
+ // restore instruction above)
+ mipsProgramBuilder.setRegisterValue("ISA_MODE", "0x8", "0xc", 1);
+ mipsProgramBuilder.disassemble("0x00000000", 0x10);
+ var mipsProgram = mipsProgramBuilder.getProgram();
+ mips = (SleighLanguage) mipsProgram.getLanguage();
+ WildSleighAssemblerBuilder mipsBuilder = new WildSleighAssemblerBuilder(mips);
+ asmMips = mipsBuilder.getAssembler(new AssemblySelector(), mipsProgram);
+ }
+
+ protected static void x86() throws Exception {
+ if (x86 != null) {
+ return;
+ }
+ ProgramBuilder x86ProgramBuilder = new ClassicSampleX86ProgramBuilder();
+ x86Program = x86ProgramBuilder.getProgram();
+ x86 = (SleighLanguage) x86Program.getLanguage();
+ WildSleighAssemblerBuilder builderX86 = new WildSleighAssemblerBuilder(x86);
+ asmX86 = builderX86.getAssembler(new AssemblySelector(), x86Program);
+ }
+
+ protected static void x8664() throws Exception {
+ if (x8664 != null) {
+ return;
+ }
+ ProgramBuilder x8664ProgramBuilder = new ProgramBuilder("x86_64_test", "x86:LE:64:default");
+ x8664Program = x8664ProgramBuilder.getProgram();
+ x8664 = (SleighLanguage) x8664Program.getLanguage();
+ WildSleighAssemblerBuilder builderX8664 = new WildSleighAssemblerBuilder(x8664);
+ asmX8664 = builderX8664.getAssembler(new AssemblySelector(), x8664Program);
+ }
+
+ protected void dumpResults(AssemblyResolutionResults results) {
+ System.err.println("results:" + results);
+ for (AssemblyResolution res : results) {
+ if (res instanceof WildAssemblyResolvedPatterns pats) {
+ System.err.println(pats.getInstruction());
+ for (WildOperandInfo info : pats.getOperandInfo()) {
+ var choice_str = "?";
+ var choice = info.choice();
+ if (choice != null) {
+ choice_str = choice.toString();
+ }
+
+ System.err.println(info.location() + ": " + info.wildcard() + " = " +
+ info.expression() + "(" + info.path() + ") == " + choice_str.toString());
+ }
+ }
+ }
+ }
+
+ /**
+ * Return all items from {@code results} which are instances of
+ * {@code WildAssemblyResolvedPatterns}
+ *
+ * @param results
+ * @return
+ */
+ protected List getValidResults(
+ AssemblyResolutionResults results) {
+ var out = new ArrayList();
+ for (AssemblyResolution res : results) {
+ if (res instanceof WildAssemblyResolvedPatterns pats) {
+ out.add(pats);
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Return all Choice values for the given {@code wildcardIdentifier} found in the given
+ * {@code results}
+ *
+ * @param wildcardIdentifier
+ * @param results
+ * @return
+ */
+ protected List getChoiceValues(String wildcardIdentifier,
+ AssemblyResolutionResults results) {
+ return getValidResults(results).stream()
+ .flatMap(x -> x.getOperandInfo()
+ .stream()
+ .filter(oi -> oi.wildcard().equals(wildcardIdentifier))
+ .filter(oi -> oi.choice() != null)
+ .map(oi -> oi.choice().toString()))
+ .toList();
+ }
+
+ protected Set getInstructionPatterns(AssemblyResolutionResults results) {
+ return getValidResults(results).stream()
+ .map(res -> res.getInstruction())
+ .collect(Collectors.toSet());
+ }
+
+ protected Set makeInstructionPatterns(String... patterns) {
+ return Stream.of(patterns)
+ .map(AssemblyPatternBlock::fromString)
+ .collect(Collectors.toSet());
+ }
+
+ /**
+ * Return all possible instruction encodings from the given {@code results}
+ *
+ * @param results the assembly results whose instruction bytes to collect
+ * @return the full list of results
+ */
+ protected List getInstructionValues(AssemblyResolutionResults results) {
+ var out = new ArrayList();
+ for (WildAssemblyResolvedPatterns res : getValidResults(results)) {
+ for (byte[] instructionVal : res.getInstruction().possibleVals()) {
+ // Read the docs on possibleVals(). You must create a copy.
+ out.add(Arrays.copyOf(instructionVal, instructionVal.length));
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Return all possible instruction encodings as hex strings from the given {@code results}
+ *
+ * @param results the results to encode and collect
+ * @return the list
+ */
+ protected List getInstructionValuesHex(AssemblyResolutionResults results) {
+ return getInstructionValues(results).stream()
+ .map(x -> NumericUtilities.convertBytesToString(x, ":"))
+ .toList();
+ }
+
+ @Test
+ public void testWildRegex() {
+ Pattern patWild = WildAssemblyTerminal.PAT_WILD;
+
+ Matcher mat = patWild.matcher("`test` more` and `more`");
+
+ assertTrue(mat.find(0));
+ assertEquals("`test`", mat.group());
+ }
+
+ @Test
+ public void testSpecRegexWildcard() {
+ assertEquals(new RegexWildcard("Q1", Pattern.compile("r.")), Wildcard.parse("Q1/r."));
+ }
+
+ @Test
+ public void testSpecNumericWildcard() {
+ assertEquals(new NumericWildcard("Q1"), Wildcard.parse("Q1[..]"));
+ }
+
+ @Test
+ public void testSpecRangesWildcard() {
+ assertEquals(new RangesWildcard("Q1", List.of(new WildRange(1, 1))),
+ Wildcard.parse("Q1[1]"));
+ assertEquals(new RangesWildcard("Q1", List.of(new WildRange(1, 2))),
+ Wildcard.parse("Q1[1..2]"));
+ assertEquals(new RangesWildcard("Q1", List.of(
+ new WildRange(-16, -4),
+ new WildRange(1, 2))),
+ Wildcard.parse("Q1[1..2,-0x10..-4]"));
+ }
+
+ @Test
+ public void testRangesTest() {
+ Wildcard wild = Wildcard.parse("Q1[1..2,-0x10..-4]");
+
+ assertFalse(wild.test("r1"));
+
+ assertTrue(wild.test(1));
+ assertTrue(wild.test(2));
+ assertFalse(wild.test(0));
+ assertFalse(wild.test(3));
+
+ assertTrue(wild.test(-16));
+ assertTrue(wild.test(-10));
+ assertTrue(wild.test(-4));
+ assertFalse(wild.test(-17));
+ assertFalse(wild.test(-3));
+
+ assertTrue(wild.test(-10L));
+ }
+
+ @Test
+ public void testParseWildRegOp_Toy() throws Exception {
+ toy();
+ Collection parses = asmToy.parseLine("add `Q1`, #6");
+ assertTrue(parses.stream().anyMatch(p -> !p.isError()));
+ }
+
+ @Test
+ public void testParseWildImm_Toy() throws Exception {
+ toy();
+ Collection parses = asmToy.parseLine("add r0, #`Q2`");
+ assertTrue(parses.stream().anyMatch(p -> !p.isError()));
+ }
+
+ @Test
+ public void testParseWildStr_Err_Toy() throws Exception {
+ toy();
+ Collection parses = asmToy.parseLine("add r0, `Q3`6");
+ assertFalse(parses.stream().anyMatch(p -> !p.isError()));
+ }
+
+ @Test
+ public void testParseWildRegAndImmOp_Toy() throws Exception {
+ toy();
+ Collection parses = asmToy.parseLine("add `Q1`, #`Q2`");
+ assertTrue(parses.stream().anyMatch(p -> !p.isError()));
+ }
+
+ @Test
+ public void testParseAndResolveWildRegOp_Toy() throws Exception {
+ toy();
+ Collection parses = asmToy.parseLine("add `Q1/r.`, #6");
+ AssemblyParseResult one = Unique.assertOne(parses.stream().filter(p -> !p.isError()));
+ System.err.println("parse: " + one);
+
+ Address addr0 = toy.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ AssemblyResolutionResults results = asmToy.resolveTree(one, addr0);
+ dumpResults(results);
+
+ assertEquals(
+ makeInstructionPatterns("C8:06", "C8:16", "C8:26", "C8:36", "C8:46", "C8:56", "C8:66",
+ "C8:76", "C8:86", "C8:96"),
+ getInstructionPatterns(results));
+ }
+
+ @Test
+ public void testParseAndResolveWildImmOp_Toy() throws Exception {
+ toy();
+ Collection parses = asmToy.parseLine("add r0, #`Q2[0,2..4]`");
+ AssemblyParseResult one = Unique.assertOne(parses.stream().filter(p -> !p.isError()));
+ System.err.println("parse: " + one);
+
+ Address addr0 = toy.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ AssemblyResolutionResults results = asmToy.resolveTree(one, addr0);
+ dumpResults(results);
+
+ assertEquals(makeInstructionPatterns("C8:00", "C8:02", "C8:03", "C8:04"),
+ getInstructionPatterns(results));
+ }
+
+ @Test
+ public void testParseAndResolveWildSubTree_Toy() throws Exception {
+ toy();
+ Collection parses =
+ asmToy.parseLine("add r0, `!Q2`").stream().filter(p -> !p.isError()).toList();
+
+ Address addr0 = toy.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ var allValidPatterns = new HashSet();
+ for (AssemblyParseResult p : parses) {
+ System.err.println("parse: " + p);
+ AssemblyResolutionResults results = asmToy.resolveTree(p, addr0);
+ dumpResults(results);
+ allValidPatterns.addAll(getInstructionPatterns(results));
+ }
+ assertEquals(makeInstructionPatterns(
+ "C8:0X", // Unspecified immediate op
+ // enumerated register ops
+ "C0:00", "C0:01", "C0:02", "C0:03", "C0:04", "C0:05", "C0:06", "C0:07",
+ "C0:08", "C0:09", "C0:0A", "C0:0B", "C0:0C", "C0:0D", "C0:0E", "C0:0F"),
+ allValidPatterns);
+ }
+
+ @Test
+ public void testParseAndResolveWildNotSubTree_Toy() throws Exception {
+ toy();
+ Collection parses =
+ asmToy.parseLine("add r0, `Q2`").stream().filter(p -> !p.isError()).toList();
+
+ Address addr0 = toy.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ var allValidPatterns = new HashSet();
+ for (AssemblyParseResult p : parses) {
+ System.err.println("parse: " + p);
+ AssemblyResolutionResults results = asmToy.resolveTree(p, addr0);
+ dumpResults(results);
+ allValidPatterns.addAll(getInstructionPatterns(results));
+ }
+ assertEquals(makeInstructionPatterns(
+ // Immediate op is excluded, because Toy's Sleigh spec wants a # on the literal
+ // enumerated register ops
+ "C0:00", "C0:01", "C0:02", "C0:03", "C0:04", "C0:05", "C0:06", "C0:07",
+ "C0:08", "C0:09", "C0:0A", "C0:0B", "C0:0C", "C0:0D", "C0:0E", "C0:0F"),
+ allValidPatterns);
+ }
+
+ @Test
+ public void testMov_arm() throws Exception {
+ arm();
+ Collection parses = asmArm.parseLine("mov `Q1`, #0x0");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidChoices = new ArrayList();
+ var allValidEncodings = new ArrayList();
+ Address addr0 = arm.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmArm.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidChoices.addAll(getChoiceValues("Q1", results));
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ // This is the encoding of "mov pc,#0x0"
+ assertThat("Expected to have a Q1=='pc' encoding",
+ allValidEncodings, hasItem("00:f0:a0:e3"));
+ // This is the encoding of "mov r5,#0x0"
+ assertThat("Expected to have a Q1=='r5' encoding",
+ allValidEncodings, hasItem("00:50:a0:e3"));
+
+ assertEquals(Set.of(
+ "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
+ "r8", "r9", "r10", "r11", "r12", "lr", "sp", "pc"),
+ Set.copyOf(allValidChoices));
+ }
+
+ @Test
+ public void testSub_arm() throws Exception {
+ arm();
+ Collection parses = asmArm.parseLine("sub r1,r1,#0x200");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ Address addr0 = arm.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmArm.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ assertTrue("Expect to have one valid encoding", allValidEncodings.size() == 1);
+ assertTrue("Expect to have 02:1c:41:e2 as an encoding",
+ allValidEncodings.contains("02:1c:41:e2"));
+ }
+
+ @Test
+ public void testSubWildcard_arm() throws Exception {
+ arm();
+ Collection parses = asmArm.parseLine("sub `Q3/r.`,`Q4`,#0x200");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ Address addr0 = arm.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmArm.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ assertTrue("Expect to have multiple valid encodings", allValidEncodings.size() > 0);
+ }
+
+ @Test
+ public void testRestore_mips() throws Exception {
+ mips();
+ Collection parses = asmMips.parseLine("restore 0x1b8,ra,s0-s1");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ Address addr8 = mips.getAddressFactory().getDefaultAddressSpace().getAddress(8);
+
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmMips.resolveTree(r, addr8);
+ dumpResults(results);
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ assertThat(allValidEncodings, hasItem("f0:30:64:77"));
+ }
+
+ @Test
+ public void testRestoreWild_mips() throws Exception {
+ mips();
+ Collection parses = asmMips.parseLine("restore `Q1`,ra,s0-s1");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidResults = new ArrayList();
+ Address addr8 = mips.getAddressFactory().getDefaultAddressSpace().getAddress(8);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmMips.resolveTree(r, addr8);
+ dumpResults(results);
+ allValidResults.addAll(getValidResults(results));
+ }
+
+ // I expect at least an encoding like 0xf0306477 (see "testRestore_mips" test)
+ assertFalse(allValidResults.isEmpty());
+ }
+
+ @Test
+ public void testLw_mips() throws Exception {
+ mips();
+ Collection parses = asmMips.parseLine("lw `Q1`,0x0(a0)");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ // Note here, be sure to go past the mips16 code at the start of our fake
+ // program
+ Address addr0 = mips.getAddressFactory().getDefaultAddressSpace().getAddress(0x100);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmMips.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ // Build all 32 encodings (one per target register, Q1) and verify they're in
+ // the results
+ byte[] expected = NumericUtilities.convertStringToBytes("8c800000");
+ for (var i = 1; i < 32; i++) {
+ expected[1] = (byte) (0x80 + i);
+ String expectedHex = NumericUtilities.convertBytesToString(expected, ":");
+ assertTrue("Expected to have " + expectedHex + " as an encoding",
+ allValidEncodings.contains(expectedHex));
+ }
+
+ assertEquals(32, allValidEncodings.size());
+ }
+
+ @Test
+ public void testCall_x86() throws Exception {
+ x86();
+ Collection parses = asmX86.parseLine("CALL 0x004058f3");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ Address addr0 = x86.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX86.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ // These are the two encodings Ghidra suggests when using the "Patch
+ // Instruction" right-click menu option
+ assertEquals(Set.of(
+ "e8:ee:58:40:00",
+ "67:e8:ed:58:40:00"),
+ Set.copyOf(allValidEncodings));
+ }
+
+ @Test
+ public void testCallWildcard_x86() throws Exception {
+ x86();
+ Collection parses =
+ asmX86.parseLine("CALL `Q1[0x00400000..0x00400003]`");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidResults = new ArrayList();
+ var allValidEncodings = new ArrayList();
+ Address addr0 = x86.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX86.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidResults.addAll(getValidResults(results));
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ assertEquals(Set.of(
+ "e8:fb:ff:3f:00", "e8:fc:ff:3f:00", "e8:fd:ff:3f:00", "e8:fe:ff:3f:00",
+ "67:e8:fa:ff:3f:00", "67:e8:fb:ff:3f:00", "67:e8:fc:ff:3f:00", "67:e8:fd:ff:3f:00"),
+ Set.copyOf(allValidEncodings));
+
+ WildAssemblyResolvedPatterns call0x00400000 = Unique.assertOne(allValidResults.stream()
+ .filter(r -> r.getInstruction()
+ .equals(AssemblyPatternBlock.fromString("e8:fb:ff:3f:00"))));
+ WildOperandInfo targetInfo = Unique.assertOne(call0x00400000.getOperandInfo());
+ assertEquals("Q1", targetInfo.wildcard());
+ assertEquals(AssemblyPatternBlock.fromString("SS:FF:FF:FF:FF"), targetInfo.location());
+ assertEquals(0x00400000L, targetInfo.choice());
+ }
+
+ @Test
+ public void testMov_x86() throws Exception {
+ x86();
+ Collection parses = asmX86.parseLine("MOV EBP,`Q1/E.P`");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ Address addr0 = x86Program.getMinAddress();
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX86.resolveTree(r, addr0);
+ dumpResults(results);
+ // This will blow up if the numeric values for Q1 are still in the results
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ // These are the four encodings Ghidra suggests when using the "Patch
+ // Instruction" right-click menu option
+ assertEquals(Set.of(
+ // These two are when Q1 == "EBP"
+ "89:ed", "8b:ed",
+ // These two are when Q1 == "ESP"
+ "89:e5", "8b:ec"),
+ Set.copyOf(allValidEncodings));
+ }
+
+ @Test
+ public void testShrd_x86() throws Exception {
+ x86();
+ Collection parses = asmX86.parseLine("SHRD EAX,EBX,0x7");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ Address addr0 = x86Program.getMinAddress();
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX86.resolveTree(r, addr0);
+ dumpResults(results);
+ // This will blow up if the numeric values for Q1 are still in the results
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ assertEquals(1, allValidEncodings.size());
+
+ var expectedEncodings = List.of("0f:ac:d8:07");
+ for (String expected : expectedEncodings) {
+ assertTrue("Expected to have " + expected + " as an encoding",
+ allValidEncodings.contains(expected));
+ }
+ }
+
+ @Test
+ public void testShrdWildcard_x86() throws Exception {
+ x86();
+ Collection parses = asmX86.parseLine("SHRD EAX,EBX,`Q1[..]`");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidResults = new ArrayList();
+ Address addr0 = x86Program.getMinAddress();
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX86.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidResults.addAll(getValidResults(results));
+ }
+
+ WildAssemblyResolvedPatterns res = Unique.assertOne(allValidResults);
+
+ assertEquals(AssemblyPatternBlock.fromString("0F:AC:D8"), res.getInstruction());
+ assertEquals(1, res.getOperandInfo().size());
+ WildOperandInfo wild = res.getOperandInfo().iterator().next();
+ assertEquals("Q1", wild.wildcard());
+ assertEquals(
+ "The Q1 operand should be the final byte of this instruction... (e.g. after the 0xD8)",
+ AssemblyPatternBlock.fromString("SS:SS:SS:FF"), wild.location());
+ }
+
+ @Test
+ public void testCallDx_x86() throws Exception {
+ x86();
+ Collection parses = asmX86.parseLine("CALL DX");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ Address addr0 = x86.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX86.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ // These are the three encodings Ghidra suggests when using the "Patch
+ // Instruction" right-click menu option
+ assertEquals(Set.of("67:66:ff:d2", "66:67:ff:d2", "66:ff:d2"),
+ Set.copyOf(allValidEncodings));
+ }
+
+ @Test
+ public void testCallDx_x8664() throws Exception {
+ x8664();
+ Collection parses = asmX8664.parseLine("CALL DX");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidEncodings = new ArrayList();
+ Address addr0 = x8664.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX8664.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ // This is the encoding Ghidra suggests when using the "Patch
+ // Instruction" right-click menu option
+ assertEquals(List.of("66:ff:d2"), allValidEncodings);
+ }
+
+ @Test
+ public void testCallDxWild_x8664() throws Exception {
+ x8664();
+ Collection parses = asmX8664.parseLine("CALL `Q1/(C|D)X`");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidChoices = new ArrayList();
+ var allValidResults = new ArrayList();
+ Address addr0 = x8664.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX8664.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidChoices.addAll(getChoiceValues("Q1", results));
+ allValidResults.addAll(
+ getValidResults(results).stream().map(x -> x.getInstruction()).toList());
+ }
+
+ assertEquals(Set.of("CX", "DX"), Set.copyOf(allValidChoices));
+ assertEquals(
+ makeInstructionPatterns("66:ff:d2", "66:ff:d1"),
+ Set.copyOf(allValidResults));
+ }
+
+ @Test
+ public void testLea_x8664() throws Exception {
+ x8664();
+ Collection parses = asmX8664.parseLine("LEA EAX, [ EDX + -0x6c ]");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidResults = new ArrayList();
+ var allValidEncodings = new ArrayList();
+ Address addr0 = x8664.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX8664.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidResults.addAll(
+ getValidResults(results).stream().map(x -> x.getInstruction()).toList());
+ allValidEncodings.addAll(getInstructionValuesHex(results));
+ }
+
+ assertTrue(allValidResults.size() > 0);
+ assertEquals(Set.of(
+ "67:8d:42:94",
+ "67:8d:44:22:94",
+ "67:8d:44:62:94",
+ "67:8d:44:a2:94",
+ "67:8d:44:e2:94",
+ "67:8d:82:94:ff:ff:ff",
+ "67:8d:84:22:94:ff:ff:ff",
+ "67:8d:84:62:94:ff:ff:ff",
+ "67:8d:84:a2:94:ff:ff:ff",
+ "67:8d:84:e2:94:ff:ff:ff"),
+ Set.copyOf(allValidEncodings));
+ }
+
+ @Test
+ public void testLeaWild_x8664() throws Exception {
+ x8664();
+ Collection parses =
+ asmX8664.parseLine("LEA EAX, [ `Q1/EDX` + -0x6c ]");
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ var allValidChoices = new ArrayList();
+ var allValidResults = new ArrayList();
+ Address addr0 = x8664.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX8664.resolveTree(r, addr0);
+ dumpResults(results);
+ allValidChoices.addAll(getChoiceValues("Q1", results));
+ allValidResults.addAll(getValidResults(results));
+ }
+
+ WildAssemblyResolvedPatterns shortest =
+ Unique.assertOne(allValidResults.stream().filter(r -> r.getInstructionLength() == 4));
+ assertEquals(AssemblyPatternBlock.fromString("67:8d:42:94"), shortest.getInstruction());
+ WildOperandInfo opInfoEDX = Unique.assertOne(
+ shortest.getOperandInfo().stream().filter(x -> x.choice().toString().equals("EDX")));
+ assertEquals(AssemblyPatternBlock.fromString("SS:SS:X[x111]"), opInfoEDX.location());
+ assertEquals(Set.of("EDX"), Set.copyOf(allValidChoices));
+ }
+}
From 8470e3588b59f5982833eb502ebc3af490469a05 Mon Sep 17 00:00:00 2001
From: plucia
Date: Wed, 24 Jan 2024 15:28:53 -0500
Subject: [PATCH 2/3] Update documentation and add example scripts for
WildSleighAssembler.
---
.../FindInstructionWithWildcard.java | 346 ++++++++++++++++++
.../WildSleighAssemblerInfo.java | 157 ++++++++
.../src/main/help/help/TOC_Source.xml | 9 +-
.../Wildcard_Assembler.html | 8 +-
4 files changed, 516 insertions(+), 4 deletions(-)
create mode 100644 Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java
create mode 100644 Ghidra/Features/WildcardAssembler/ghidra_scripts/WildSleighAssemblerInfo.java
diff --git a/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java b/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java
new file mode 100644
index 00000000000..dcad3ddef83
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java
@@ -0,0 +1,346 @@
+/* ###
+ * 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.
+ */
+// This script illustrates one way to work with the WildSleighAssembler by searching for the first
+// 10 instances of each encoding of a single predefined x86_64 instruction with a wildcard.
+//
+// This script assembles the instruction "XOR R13D,`Q1/R1(2|3)D`" where the second operand is a
+// wildcard which we have constrained to be either R12D or R13D. Using the metadata from assembly
+// we find all the unique encodings after discounting wildcard specific bits and search for each of
+// these unique encodings in the binary. For performance / example reasons we only find the first
+// 10 search results for each starting from currentAddress. For each result, we print the address
+// of the hit and the value of the wildcard at that location.
+//
+// See documentation within the script for more detail on APIs. See "Help > Contents > Ghidra
+// Functionality > Wildcard Assembler" for assembly wildcard syntax.
+//
+// See the "WildSleighAssemblerInfo" script for a simpler use of the WildSleighAssembler.
+// @category Examples
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+import ghidra.app.plugin.assembler.AssemblySelector;
+import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult;
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolutionResults;
+import ghidra.app.plugin.processors.sleigh.SleighLanguage;
+import ghidra.app.script.GhidraScript;
+import ghidra.asm.wild.WildOperandInfo;
+import ghidra.asm.wild.WildSleighAssembler;
+import ghidra.asm.wild.WildSleighAssemblerBuilder;
+import ghidra.asm.wild.sem.WildAssemblyResolvedPatterns;
+import ghidra.program.model.mem.*;
+import ghidra.program.model.listing.*;
+import ghidra.program.database.ProgramBuilder;
+import ghidra.program.model.address.*;
+
+public class FindInstructionWithWildcard extends GhidraScript {
+
+ public void run() throws Exception {
+
+ // The wildcard here specifies that the second operand of this instruction is
+ // either "R12D" or "R13D". The instruction and/or wildcard here can be modified
+ // to an instruction found in your x86_64 binary, or patch your binary to have
+ // the bytes "0x45 0x31 0xed" somewhere.
+ var allValidResults = getAllResolvedPatterns("XOR R13D,`Q1/R1(2|3)D`");
+
+ var encodings = getMapOfUniqueInstructionEncodings(allValidResults);
+
+ searchMemoryForEncodings(encodings, allValidResults);
+ }
+
+ /**
+ * Use an x86_64 WildSleighAssembler to assemble the given {@code wildcardedInstruction}
+ *
+ * @param wildcardedInstruction
+ * @return All WildAssemblyResolvedPatterns produced from the given input (e.g. All VALID
+ * results of assembling the given input)
+ */
+ private ArrayList getAllResolvedPatterns(
+ String wildcardedInstruction) {
+ var allValidResults = new ArrayList();
+
+ // Get our current program or build a new program if our current binary isn't x86_64
+ Program baseProgram = currentProgram;
+ if (!baseProgram.getLanguageCompilerSpecPair().languageID.getIdAsString()
+ .equals("x86:LE:64:default")) {
+ println(
+ "WARNING: Current program is not 'x86:LE:64:default' so using a builder instead!");
+
+ ProgramBuilder baseProgramBuilder;
+ try {
+ baseProgramBuilder = new ProgramBuilder("x86_64_test", "x86:LE:64:default");
+ }
+ catch (Exception e) {
+ println(
+ "Couldn't create ProgramBuilder with hardcoded languageName! Something is very wrong!");
+ e.printStackTrace();
+ return allValidResults;
+ }
+
+ baseProgram = baseProgramBuilder.getProgram();
+ }
+
+ SleighLanguage x8664Language = (SleighLanguage) baseProgram.getLanguage();
+
+ // Create a WildSleighAssembler that we'll use to assemble our wildcard-included instruction
+ WildSleighAssemblerBuilder builderX8664 = new WildSleighAssemblerBuilder(x8664Language);
+ WildSleighAssembler asmX8664 =
+ builderX8664.getAssembler(new AssemblySelector(), baseProgram);
+
+ // Parse a single line of assembly which includes a wildcard.
+ Collection parses = asmX8664.parseLine(wildcardedInstruction);
+
+ // Remove all the AssemblyParseResults that represent parse errors
+ AssemblyParseResult[] allResults = parses.stream()
+ .filter(p -> !p.isError())
+ .toArray(AssemblyParseResult[]::new);
+
+ // Try to resolve each AssemblyParseResult at address 0 and collect all the
+ // results which are valid
+ Address addr0 = x8664Language.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+
+ for (AssemblyParseResult r : allResults) {
+ AssemblyResolutionResults results = asmX8664.resolveTree(r, addr0);
+
+ allValidResults.addAll(getValidResults(results));
+ }
+ return allValidResults;
+ }
+
+ /**
+ * Reduce the given {@code WildAssemblyResolvedPatterns} to a map keyed by unique instruction
+ * encodings WITHOUT specific wildcard values. Each key in this map corresponds to a set of
+ * {@code WildOperandInfo} options for the corresponding encoding.
+ *
+ * @param allValidResolvedPatterns
+ * @return
+ */
+ private HashMap> getMapOfUniqueInstructionEncodings(
+ ArrayList allValidResolvedPatterns) {
+
+ // Bail out early if we were not able to find any results (should only happen if the hard
+ // coded instruction in this example script is changed)
+ if (allValidResolvedPatterns.size() < 1) {
+ println("No assembly results for given assembly with wildcard!");
+ return new HashMap>();
+ }
+
+ // 'allValidResolvedPatterns' has one entry for each encoding/wildcard value pair. We're
+ // going to reduce that down to a map where each:
+ // * Key is a single encoding of an instruction WITHOUT the wildcard operand
+ // bits specified
+ // * Value is a set of WildOperandInfo instances containing each valid wildcard
+ // completion
+ var encodings = new HashMap>();
+ for (WildAssemblyResolvedPatterns x : allValidResolvedPatterns) {
+ var y = new ReducedWildcardAssemblyResolvedPattern(x);
+ var existing = encodings.get(y.maskedInstruction);
+ if (existing == null) {
+ existing = new HashSet();
+ }
+ existing.addAll(y.parent.getOperandInfo());
+ encodings.put(y.maskedInstruction, existing);
+ }
+ return encodings;
+ }
+
+ /**
+ * This is a helper class which creates and holds an {@code AssemblyPatternBlock} for a given
+ * {@code WildAssemblyResolvedPatterns}. This created {@code AssemblyPatternBlock} does NOT have
+ * any bits specified that are part of a wildcarded operand. This is in contrast to the original
+ * {@code WildAssemblyResolvedPatterns} where those bits are specified to have the values that
+ * correspond to the {@code WildOperandInfo} values found in the
+ * {@code WildAssemblyResolvedPatterns}.
+ */
+ class ReducedWildcardAssemblyResolvedPattern {
+ /**
+ * The original WildAssemblyResolvedPatterns that this is based on
+ */
+ WildAssemblyResolvedPatterns parent;
+ /**
+ * The portion of the instruction that is NOT any wildcarded operand
+ */
+ AssemblyPatternBlock maskedInstruction;
+
+ ReducedWildcardAssemblyResolvedPattern(WildAssemblyResolvedPatterns input) {
+ parent = input;
+
+ // Remove all the bits which correspond to wildcarded opcodes from the instruction and
+ // save the result as maskedInstruction
+ var reducedInstruction = input.getInstruction();
+ for (WildOperandInfo info : input.getOperandInfo()) {
+ reducedInstruction = reducedInstruction.maskOut(info.location());
+ }
+ maskedInstruction = reducedInstruction;
+ }
+
+ /**
+ * Returns true of the given value shares the same {@code maskedInstruction} and wildcard(s)
+ * as this instance.
+ *
+ * @param other
+ * Value to compare against
+ * @return True if both values share the same maskedInstruction and wildcard(s)
+ */
+ boolean sameBaseEncoding(ReducedWildcardAssemblyResolvedPattern other) {
+ if (this.maskedInstruction.compareTo(other.maskedInstruction) != 0) {
+ return false;
+ }
+
+ for (WildOperandInfo info : this.parent.getOperandInfo()) {
+ var foundMatch = false;
+ for (WildOperandInfo otherInfo : other.parent.getOperandInfo()) {
+ if (info.wildcard().equals(otherInfo.wildcard())) {
+ if (!info.equals(otherInfo)) {
+ return false;
+ }
+ foundMatch = true;
+ break;
+ }
+ }
+ if (!foundMatch) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Searches for at most 10 matches for each given encoding, starting at {@code currentAddress}
+ * and prints results to the console.
+ *
+ * This searches encoding by encoding, restarting back at the start of memory for each.
+ *
+ * Does not currently print wildcard information about the search results, but this could be
+ * added.
+ *
+ * @param encodings
+ * HashMap of encodings to that encoding's possible WildOperandInfo values.
+ * @throws MemoryAccessException
+ * If we find bytes but can't read them
+ */
+ private void searchMemoryForEncodings(
+ HashMap> encodings,
+ ArrayList allValidResolvedPatterns)
+ throws MemoryAccessException {
+
+ Memory memory = currentProgram.getMemory();
+
+ for (var entry : encodings.entrySet()) {
+ var encoding = entry.getKey();
+ println("Searching for encoding: " + encoding.toString());
+
+ // Start/restart back at currentAddress for each new encoding search
+ var searchFromAddress = currentAddress;
+ var matchCount = 0;
+
+ // Stop if we run out of addresses or don't have a currentAddress
+ while (searchFromAddress != null) {
+
+ var matchAddress =
+ memory.findBytes(searchFromAddress, encoding.getVals(), encoding.getMask(),
+ getReusePreviousChoices(), monitor);
+ if (matchAddress == null) {
+ // No match found, go to next encoding
+ break;
+ }
+
+ // Get the specific bytes found at this address and print match info
+ var foundBytes = new byte[encoding.length()];
+ memory.getBytes(matchAddress, foundBytes);
+ printSearchHitInfo(matchAddress, foundBytes, allValidResolvedPatterns);
+
+ // Continue to the next result (unless we've had 10 matches already)
+ searchFromAddress = matchAddress.next();
+ matchCount += 1;
+ if (matchCount > 10) {
+ println("Stopping after 10 matches!");
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Print information about a specific search hit to the console
+ *
+ * NOTE: This is certainly not the highest performance way to do this, but it is reasonably
+ * simple and shows what is possible.
+ *
+ * @param matchAddress
+ * The address where our search hit occurred
+ * @param matchData
+ * The bytes found at matchAddress. Must include the entire matching instruction!
+ * @param allValidResolvedPatterns
+ * All resolved patterns which were searched from (used to find wildcard information)
+ */
+ private void printSearchHitInfo(Address matchAddress, byte[] matchData,
+ ArrayList allValidResolvedPatterns) {
+
+ println("Hit at address: " + matchAddress.toString());
+
+ // Search over all the resolutions we were searching for and find the one which matches and
+ // use it determine what the wildcard values are for the given hit.
+ //
+ // It'd likely be much faster the deduplicate similar WildAssemblyResolvedPatterns based on
+ // their instruction with wildcards masked out (similar to what is done in
+ // ReducedWildcardAssemblyResolvedPattern) and create a lookup table for wildcard values but
+ // that's beyond this basic example script.
+ for (WildAssemblyResolvedPatterns resolved : allValidResolvedPatterns) {
+ var resolvedInstruction = resolved.getInstruction();
+ if (resolvedInstruction.length() > matchData.length) {
+ // It can't be this resolution because we were not given enough bytes of matchData
+ continue;
+ }
+
+ // Mask out the matchData with the mask of our candidate resolvedInstruction and see if
+ // the results match. If they do, then this is the WildAssemblyResolvedPatterns
+ var matchMasked = resolvedInstruction.getMaskedValue(matchData);
+ if (matchMasked.equals(resolvedInstruction)) {
+ for (WildOperandInfo info : resolved.getOperandInfo()) {
+ println("Wildcard `" + info.wildcard() + "` = " + info.choice().toString());
+ }
+ return;
+ }
+ println("Failed to find search hit info");
+ }
+
+ }
+
+ /**
+ * Return all items from {@code results} which are instances of
+ * {@code WildAssemblyResolvedPatterns}
+ *
+ * @param results
+ * @return
+ */
+ private List getValidResults(AssemblyResolutionResults results) {
+ var out = new ArrayList();
+ for (AssemblyResolution result : results) {
+ if (result instanceof WildAssemblyResolvedPatterns resolvedPatterns) {
+ out.add(resolvedPatterns);
+ }
+ }
+ return out;
+ }
+
+}
diff --git a/Ghidra/Features/WildcardAssembler/ghidra_scripts/WildSleighAssemblerInfo.java b/Ghidra/Features/WildcardAssembler/ghidra_scripts/WildSleighAssemblerInfo.java
new file mode 100644
index 00000000000..d7abff9ee83
--- /dev/null
+++ b/Ghidra/Features/WildcardAssembler/ghidra_scripts/WildSleighAssemblerInfo.java
@@ -0,0 +1,157 @@
+/* ###
+ * 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.
+ */
+// Prints information about the results of assembling an instruction using the WildSleighAssembler
+// when that instruction has one or more wildcards in it.
+//
+// This script uses currentProgram and currentAddress to determine architecture and location.
+//
+// Notice that this script doesn't only output the assembled bytes of an instruction, but also more
+// specific information about each wildcard in the input instruction.
+//
+// See the "FindInstructionWithWildcard" script for another example of using the WildSleighAssembler
+// @category Examples
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import ghidra.app.plugin.assembler.AssemblySelector;
+import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult;
+import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
+import ghidra.app.plugin.processors.sleigh.SleighLanguage;
+import ghidra.app.script.GhidraScript;
+import ghidra.asm.wild.WildOperandInfo;
+import ghidra.asm.wild.WildSleighAssembler;
+import ghidra.asm.wild.WildSleighAssemblerBuilder;
+import ghidra.asm.wild.sem.WildAssemblyResolvedPatterns;
+
+public class WildSleighAssemblerInfo extends GhidraScript {
+
+ List sampleInstructions =
+ Arrays.asList("MOV EAX,`Q1`", "MOV RDI,qword ptr [`Q1` + -0x30]", "Custom");
+
+ public void run() throws Exception {
+
+ String instruction = askChoice("Instruction to assemble", "Assemble this instruction:",
+ sampleInstructions, "Custom");
+
+ if (instruction.equals("Custom")) {
+ instruction = askString("Instruction",
+ "Instruction to assemble and print information about.",
+ "MOV RDI,qword ptr [`Q1` + -0x30]");
+ }
+ var assemblyResolutions = getAllAssemblyResolutions(instruction);
+ printAssemblyParseResults(assemblyResolutions);
+ }
+
+ /**
+ * Use a WildSleighAssembler to assemble the given {@code wildcardedInstruction}
+ *
+ * @param wildcardedInstruction
+ * @return All AssemblyParseResult produced from the given input
+ */
+ private List getAllAssemblyResolutions(
+ String wildcardedInstruction) {
+
+ SleighLanguage language = (SleighLanguage) currentProgram.getLanguage();
+
+ // Make sure that if one of the example instructions was chosen the current binary has the
+ // correct architecture.
+ if (sampleInstructions.contains(wildcardedInstruction) &&
+ !language.getLanguageID().toString().equals("x86:LE:64:default")) {
+ popup(
+ "The current program must be a \"x86:LE:64:default\" binary for the example " +
+ "instructions to work. Retry with a custom instruction in your architecture!");
+ return Collections.emptyList();
+ }
+
+ // Create a WildSleighAssembler that we'll use to assemble our wildcard-included instruction
+ WildSleighAssemblerBuilder assemblerBuilder = new WildSleighAssemblerBuilder(language);
+ WildSleighAssembler assembler =
+ assemblerBuilder.getAssembler(new AssemblySelector(), currentProgram);
+
+ // Parse a single line of assembly which includes a wildcard.
+ Collection parses = assembler.parseLine(wildcardedInstruction);
+
+ long errorCount = parses.stream().filter(p -> p.isError()).count();
+ println("Removing " + errorCount + " of " + parses.size() +
+ " AssemblyParseResults which are errored parses");
+
+ return parses
+ .stream()
+ // Remove all the AssemblyParseResults that represent parse errors
+ .filter(p -> !p.isError())
+ // Resolve each parseTree at the current address and collect all AssemblyResolutions
+ // into a single flat collection using flatMap
+ .flatMap(p -> assembler.resolveTree(p, currentAddress).stream())
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Print information about the WildAssemblyResolvedPatterns in the given list.
+ *
+ * @param resolutionResults
+ */
+ private void printAssemblyParseResults(List resolutionResults) {
+ var errorCount = 0;
+
+ for (AssemblyResolution r : resolutionResults) {
+ if (monitor.isCancelled()) {
+ break;
+ }
+ if (r instanceof WildAssemblyResolvedPatterns resolution) {
+ printWildAssemblyResolvedPatterns(resolution);
+ }
+ else {
+ errorCount += 1;
+ }
+ }
+
+ if (errorCount > 0) {
+ println(
+ "Additionally " + errorCount +
+ " non-WildAssemblyResolvedPatterns were not printed");
+
+ }
+ }
+
+ /**
+ * Print information about a single {@code WildAssemblyResolvedPatterns}, including information
+ * about each of its wildcards.
+ *
+ * @param x
+ * The value to print information about.
+ */
+ private void printWildAssemblyResolvedPatterns(WildAssemblyResolvedPatterns x) {
+ println("Instruction bits (including wildcard values): " + x.getInstruction());
+ for (WildOperandInfo info : x.getOperandInfo()) {
+ String out =
+ "\tThe wildcard " + info.wildcard() + " is found in bits " + info.location();
+ if (info.choice() == null) {
+ out += " with a value which can be computed with the expression: " +
+ info.expression();
+ }
+ else {
+ out += " with the value: " + info.choice() +
+ " which can be computed with the expression: " + info.expression();
+ }
+ println(out);
+ }
+ }
+
+}
diff --git a/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml b/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
index c0cdde2a7cd..d36810d689d 100644
--- a/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
+++ b/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
@@ -49,6 +49,11 @@
-
-
+
+
+
+
+
diff --git a/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html b/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html
index c8647893acc..24cbbc31d6f 100644
--- a/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html
+++ b/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html
@@ -13,6 +13,10 @@
Wildcard Assembler Plugin
+
+
This feature is currently only available as an API for Ghidra
+ scripts and plugins.
+
The Wildcard Assembler extends Ghidra's assembler to enable
assembling instructions with specific tokens replaced with wildcards.
@@ -28,7 +32,7 @@
Wildcard Syntax
`Q1` where Q1 is an arbitrary wildcard name) and passing the
entire instruction to the Wildcard Assembler.
-
By default the Wildcard Assembler will return metadata about all
+
By default, the Wildcard Assembler will return metadata about all
possible values that a wildcarded token could take and all the encodings
of all these values. This behavior can be limited by filtering the
wildcard by appending specific syntax after the wildcard name:
@@ -61,7 +65,7 @@
Wildcard Syntax
-
Normally, a wildcard will only match a single token. To allow a single
+
Normally a wildcard will only match a single token. To allow a single
wildcard to match multiple related tokens: precede the wildcard name with a
! character. For example, in a x86:LE:32:default binary:
From 67bb4d107a0a615864577049112dcc77ed590830 Mon Sep 17 00:00:00 2001
From: plucia
Date: Wed, 31 Jan 2024 13:19:29 -0500
Subject: [PATCH 3/3] Address pull request feedback.
---
.../WildcardAssembler/certification.manifest | 2 +-
.../FindInstructionWithWildcard.java | 144 +++++++++---------
.../WildSleighAssemblerInfo.java | 14 +-
.../src/main/help/help/TOC_Source.xml | 2 +-
.../Wildcard_Assembler.html | 20 +--
5 files changed, 88 insertions(+), 94 deletions(-)
rename Ghidra/Features/WildcardAssembler/src/main/help/help/topics/{WildcardAssemblerPlugin => WildcardAssemblerModule}/Wildcard_Assembler.html (87%)
diff --git a/Ghidra/Features/WildcardAssembler/certification.manifest b/Ghidra/Features/WildcardAssembler/certification.manifest
index f08f615729b..7815f11406a 100644
--- a/Ghidra/Features/WildcardAssembler/certification.manifest
+++ b/Ghidra/Features/WildcardAssembler/certification.manifest
@@ -3,4 +3,4 @@
##MODULE IP: Oxygen Icons - LGPL 3.0
Module.manifest||GHIDRA||||END|
src/main/help/help/TOC_Source.xml||GHIDRA||reviewed||END|
-src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html||GHIDRA||||END|
+src/main/help/help/topics/WildcardAssemblerModule/Wildcard_Assembler.html||GHIDRA||||END|
diff --git a/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java b/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java
index dcad3ddef83..c69734de66e 100644
--- a/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java
+++ b/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java
@@ -34,6 +34,8 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import ghidra.app.plugin.assembler.AssemblySelector;
import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult;
@@ -47,19 +49,16 @@
import ghidra.asm.wild.WildSleighAssemblerBuilder;
import ghidra.asm.wild.sem.WildAssemblyResolvedPatterns;
import ghidra.program.model.mem.*;
-import ghidra.program.model.listing.*;
-import ghidra.program.database.ProgramBuilder;
import ghidra.program.model.address.*;
public class FindInstructionWithWildcard extends GhidraScript {
public void run() throws Exception {
- // The wildcard here specifies that the second operand of this instruction is
- // either "R12D" or "R13D". The instruction and/or wildcard here can be modified
- // to an instruction found in your x86_64 binary, or patch your binary to have
- // the bytes "0x45 0x31 0xed" somewhere.
- var allValidResults = getAllResolvedPatterns("XOR R13D,`Q1/R1(2|3)D`");
+ var instruction = askString("Instruction to search",
+ "Instruction to search for with wildcard (example is for x86_64, adjust if you are using a different architecture):",
+ "XOR R13D,`Q1/R1(2|3)D`");
+ var allValidResults = getAllResolvedPatterns(instruction);
var encodings = getMapOfUniqueInstructionEncodings(allValidResults);
@@ -67,58 +66,39 @@ public void run() throws Exception {
}
/**
- * Use an x86_64 WildSleighAssembler to assemble the given {@code wildcardedInstruction}
+ * Use a {@link WildSleighAssembler} to assemble the given {@code wildcardedInstruction}
*
* @param wildcardedInstruction
- * @return All WildAssemblyResolvedPatterns produced from the given input (e.g. All VALID
- * results of assembling the given input)
+ * @return All {@link WildAssemblyResolvedPatterns} produced from the given input (e.g. All
+ * VALID results of assembling the given input)
*/
- private ArrayList getAllResolvedPatterns(
+ private List getAllResolvedPatterns(
String wildcardedInstruction) {
var allValidResults = new ArrayList();
- // Get our current program or build a new program if our current binary isn't x86_64
- Program baseProgram = currentProgram;
- if (!baseProgram.getLanguageCompilerSpecPair().languageID.getIdAsString()
- .equals("x86:LE:64:default")) {
- println(
- "WARNING: Current program is not 'x86:LE:64:default' so using a builder instead!");
+ SleighLanguage currentLanguage = (SleighLanguage) currentProgram.getLanguage();
- ProgramBuilder baseProgramBuilder;
- try {
- baseProgramBuilder = new ProgramBuilder("x86_64_test", "x86:LE:64:default");
- }
- catch (Exception e) {
- println(
- "Couldn't create ProgramBuilder with hardcoded languageName! Something is very wrong!");
- e.printStackTrace();
- return allValidResults;
- }
-
- baseProgram = baseProgramBuilder.getProgram();
- }
-
- SleighLanguage x8664Language = (SleighLanguage) baseProgram.getLanguage();
-
- // Create a WildSleighAssembler that we'll use to assemble our wildcard-included instruction
- WildSleighAssemblerBuilder builderX8664 = new WildSleighAssemblerBuilder(x8664Language);
- WildSleighAssembler asmX8664 =
- builderX8664.getAssembler(new AssemblySelector(), baseProgram);
+ // Create a WildSleighAssembler that we'll use to assemble our wildcard-included
+ // instruction
+ WildSleighAssemblerBuilder assemblerBuilder =
+ new WildSleighAssemblerBuilder(currentLanguage);
+ WildSleighAssembler assembler =
+ assemblerBuilder.getAssembler(new AssemblySelector(), currentProgram);
// Parse a single line of assembly which includes a wildcard.
- Collection parses = asmX8664.parseLine(wildcardedInstruction);
+ Collection parses = assembler.parseLine(wildcardedInstruction);
// Remove all the AssemblyParseResults that represent parse errors
- AssemblyParseResult[] allResults = parses.stream()
+ List allResults = parses.stream()
.filter(p -> !p.isError())
- .toArray(AssemblyParseResult[]::new);
+ .toList();
- // Try to resolve each AssemblyParseResult at address 0 and collect all the
- // results which are valid
- Address addr0 = x8664Language.getAddressFactory().getDefaultAddressSpace().getAddress(0);
+ // Try to resolve each AssemblyParseResult at address 0 and collect all the results which
+ // are valid
+ Address addr0 = currentLanguage.getAddressFactory().getDefaultAddressSpace().getAddress(0);
for (AssemblyParseResult r : allResults) {
- AssemblyResolutionResults results = asmX8664.resolveTree(r, addr0);
+ AssemblyResolutionResults results = assembler.resolveTree(r, addr0);
allValidResults.addAll(getValidResults(results));
}
@@ -133,23 +113,22 @@ private ArrayList getAllResolvedPatterns(
* @param allValidResolvedPatterns
* @return
*/
- private HashMap> getMapOfUniqueInstructionEncodings(
- ArrayList allValidResolvedPatterns) {
+ private Map> getMapOfUniqueInstructionEncodings(
+ List allValidResolvedPatterns) {
// Bail out early if we were not able to find any results (should only happen if the hard
// coded instruction in this example script is changed)
- if (allValidResolvedPatterns.size() < 1) {
+ if (allValidResolvedPatterns.isEmpty()) {
println("No assembly results for given assembly with wildcard!");
- return new HashMap>();
+ return Map.of();
}
// 'allValidResolvedPatterns' has one entry for each encoding/wildcard value pair. We're
// going to reduce that down to a map where each:
- // * Key is a single encoding of an instruction WITHOUT the wildcard operand
- // bits specified
- // * Value is a set of WildOperandInfo instances containing each valid wildcard
- // completion
- var encodings = new HashMap>();
+ // * Key is a single encoding of an instruction WITHOUT the wildcard operand bits specified
+ // * Value is a set of WildOperandInfo instances containing each valid wildcard completion
+ Map> encodings =
+ new HashMap>();
for (WildAssemblyResolvedPatterns x : allValidResolvedPatterns) {
var y = new ReducedWildcardAssemblyResolvedPattern(x);
var existing = encodings.get(y.maskedInstruction);
@@ -170,7 +149,7 @@ private HashMap> getMapOfUniqueIn
* correspond to the {@code WildOperandInfo} values found in the
* {@code WildAssemblyResolvedPatterns}.
*/
- class ReducedWildcardAssemblyResolvedPattern {
+ static class ReducedWildcardAssemblyResolvedPattern {
/**
* The original WildAssemblyResolvedPatterns that this is based on
*/
@@ -183,8 +162,8 @@ class ReducedWildcardAssemblyResolvedPattern {
ReducedWildcardAssemblyResolvedPattern(WildAssemblyResolvedPatterns input) {
parent = input;
- // Remove all the bits which correspond to wildcarded opcodes from the instruction and
- // save the result as maskedInstruction
+ // Remove all the bits which correspond to wildcarded opcodes from the instruction save
+ // the result as maskedInstruction
var reducedInstruction = input.getInstruction();
for (WildOperandInfo info : input.getOperandInfo()) {
reducedInstruction = reducedInstruction.maskOut(info.location());
@@ -201,22 +180,32 @@ class ReducedWildcardAssemblyResolvedPattern {
* @return True if both values share the same maskedInstruction and wildcard(s)
*/
boolean sameBaseEncoding(ReducedWildcardAssemblyResolvedPattern other) {
- if (this.maskedInstruction.compareTo(other.maskedInstruction) != 0) {
+ if (!this.maskedInstruction.equals(other.maskedInstruction)) {
return false;
}
+ // Loop over each WildOperandInfo in this to ensure that there is a matching one in
+ // other which shares the same wildcard (name) and location. Remember that there might
+ // be more than one wildcard in an instruction with the same name so we can't assume
+ // there's not a match if a matching name doesn't have the same location.
for (WildOperandInfo info : this.parent.getOperandInfo()) {
var foundMatch = false;
+
+ // Check all of other's WildOperandInfo
for (WildOperandInfo otherInfo : other.parent.getOperandInfo()) {
- if (info.wildcard().equals(otherInfo.wildcard())) {
- if (!info.equals(otherInfo)) {
- return false;
- }
+ // Check if we have matching wildcards (names), expressions, and locations.
+ // Notice that we're *NOT* checking choice here, as we expect those to be different.
+ if (info.wildcard().equals(otherInfo.wildcard()) &&
+ info.expression().equals(otherInfo.expression()) &&
+ info.location().equals(otherInfo.location())) {
foundMatch = true;
break;
}
}
+
if (!foundMatch) {
+ // We were unable to find a wildcard that matched so we declare that these
+ // encodings don't have the same base encoding
return false;
}
}
@@ -227,9 +216,9 @@ boolean sameBaseEncoding(ReducedWildcardAssemblyResolvedPattern other) {
/**
* Searches for at most 10 matches for each given encoding, starting at {@code currentAddress}
* and prints results to the console.
- *
+ *
* This searches encoding by encoding, restarting back at the start of memory for each.
- *
+ *
* Does not currently print wildcard information about the search results, but this could be
* added.
*
@@ -239,14 +228,13 @@ boolean sameBaseEncoding(ReducedWildcardAssemblyResolvedPattern other) {
* If we find bytes but can't read them
*/
private void searchMemoryForEncodings(
- HashMap> encodings,
- ArrayList allValidResolvedPatterns)
+ Map> encodings,
+ List allValidResolvedPatterns)
throws MemoryAccessException {
Memory memory = currentProgram.getMemory();
- for (var entry : encodings.entrySet()) {
- var encoding = entry.getKey();
+ for (var encoding : encodings.keySet()) {
println("Searching for encoding: " + encoding.toString());
// Start/restart back at currentAddress for each new encoding search
@@ -282,7 +270,7 @@ private void searchMemoryForEncodings(
/**
* Print information about a specific search hit to the console
- *
+ *
* NOTE: This is certainly not the highest performance way to do this, but it is reasonably
* simple and shows what is possible.
*
@@ -294,25 +282,28 @@ private void searchMemoryForEncodings(
* All resolved patterns which were searched from (used to find wildcard information)
*/
private void printSearchHitInfo(Address matchAddress, byte[] matchData,
- ArrayList allValidResolvedPatterns) {
+ List allValidResolvedPatterns) {
println("Hit at address: " + matchAddress.toString());
- // Search over all the resolutions we were searching for and find the one which matches and
- // use it determine what the wildcard values are for the given hit.
+ // Check all the resolutions we were searching for and find the one which matches the found
+ // bytes and use that resolution to determine what the wildcard values are for the given
+ // hit.
//
- // It'd likely be much faster the deduplicate similar WildAssemblyResolvedPatterns based on
+ // It'd likely be much faster to deduplicate similar WildAssemblyResolvedPatterns based on
// their instruction with wildcards masked out (similar to what is done in
// ReducedWildcardAssemblyResolvedPattern) and create a lookup table for wildcard values but
// that's beyond this basic example script.
for (WildAssemblyResolvedPatterns resolved : allValidResolvedPatterns) {
var resolvedInstruction = resolved.getInstruction();
if (resolvedInstruction.length() > matchData.length) {
- // It can't be this resolution because we were not given enough bytes of matchData
+ // It can't be this resolution because we were not given enough bytes of
+ // matchData
continue;
}
- // Mask out the matchData with the mask of our candidate resolvedInstruction and see if
+ // Mask out the matchData with the mask of our candidate resolvedInstruction and
+ // see if
// the results match. If they do, then this is the WildAssemblyResolvedPatterns
var matchMasked = resolvedInstruction.getMaskedValue(matchData);
if (matchMasked.equals(resolvedInstruction)) {
@@ -328,10 +319,11 @@ private void printSearchHitInfo(Address matchAddress, byte[] matchData,
/**
* Return all items from {@code results} which are instances of
- * {@code WildAssemblyResolvedPatterns}
+ * {@link WildAssemblyResolvedPatterns}
*
* @param results
- * @return
+ * The results to return {@link WildAssemblyResolvePatterns} from
+ * @return All {@link WildAssemblyResolvedPatterns} which were found in the input
*/
private List getValidResults(AssemblyResolutionResults results) {
var out = new ArrayList();
diff --git a/Ghidra/Features/WildcardAssembler/ghidra_scripts/WildSleighAssemblerInfo.java b/Ghidra/Features/WildcardAssembler/ghidra_scripts/WildSleighAssemblerInfo.java
index d7abff9ee83..6a7ad8b4353 100644
--- a/Ghidra/Features/WildcardAssembler/ghidra_scripts/WildSleighAssemblerInfo.java
+++ b/Ghidra/Features/WildcardAssembler/ghidra_scripts/WildSleighAssemblerInfo.java
@@ -26,7 +26,6 @@
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -60,9 +59,10 @@ public void run() throws Exception {
}
/**
- * Use a WildSleighAssembler to assemble the given {@code wildcardedInstruction}
+ * Use a {@link WildSleighAssembler} to assemble the given {@code wildcardedInstruction}
*
* @param wildcardedInstruction
+ * String of the instruction to assemble, possibly including a wildcard
* @return All AssemblyParseResult produced from the given input
*/
private List getAllAssemblyResolutions(
@@ -75,9 +75,9 @@ private List getAllAssemblyResolutions(
if (sampleInstructions.contains(wildcardedInstruction) &&
!language.getLanguageID().toString().equals("x86:LE:64:default")) {
popup(
- "The current program must be a \"x86:LE:64:default\" binary for the example " +
- "instructions to work. Retry with a custom instruction in your architecture!");
- return Collections.emptyList();
+ "The current program is not a \"x86:LE:64:default\" binary that the example was " +
+ "designed for. This script will continue and try anyway, but the results might " +
+ "not be as expected. Retry with a custom instruction in your architecture!");
}
// Create a WildSleighAssembler that we'll use to assemble our wildcard-included instruction
@@ -103,7 +103,7 @@ private List getAllAssemblyResolutions(
}
/**
- * Print information about the WildAssemblyResolvedPatterns in the given list.
+ * Print information about the {@link WildAssemblyResolvedPatterns} in the given list.
*
* @param resolutionResults
*/
@@ -131,7 +131,7 @@ private void printAssemblyParseResults(List resolutionResult
}
/**
- * Print information about a single {@code WildAssemblyResolvedPatterns}, including information
+ * Print information about a single {@link WildAssemblyResolvedPatterns}, including information
* about each of its wildcards.
*
* @param x
diff --git a/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml b/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
index d36810d689d..48a46a3f67b 100644
--- a/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
+++ b/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml
@@ -53,7 +53,7 @@
+ target="help/topics/WildcardAssemblerModule/Wildcard_Assembler.html" >
diff --git a/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html b/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerModule/Wildcard_Assembler.html
similarity index 87%
rename from Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html
rename to Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerModule/Wildcard_Assembler.html
index 24cbbc31d6f..d908f7a8c7e 100644
--- a/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerPlugin/Wildcard_Assembler.html
+++ b/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerModule/Wildcard_Assembler.html
@@ -5,17 +5,19 @@
- Wildcard Assembler Plugin
+ Wildcard Assembler Module
-
Wildcard Assembler Plugin
+
Wildcard Assembler Module
This feature is currently only available as an API for Ghidra
- scripts and plugins.
+ scripts and plugins. For an example of how to use the API, see the
+ FindInstructionWithWildcard and WildSleighAssemblerInfo scripts in the
+ Script Manager.
The Wildcard Assembler extends Ghidra's assembler to enable
assembling instructions with specific tokens replaced with wildcards.
@@ -70,24 +72,24 @@
Wildcard Syntax
! character. For example, in a x86:LE:32:default binary: