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:

+ + + +

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 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 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:

-
Assembles (no wildcard):
+
No wildcard:
MOVSD.REP ES:EDI,ESI
-
Assembles (single token):
+
Single token:
MOVSD.REP `Q1`:EDI,ESI
-
Assembles (single token):
+
Single token:
MOVSD.REP ES:`Q2`,ESI
-
Does NOT assemble (single token):
+
Single token (Does NOT assemble):
MOVSD.REP `Q3`,ESI
-
Assembles (multi-token):
+
Multi-token:
MOVSD.REP `!Q4`,ESI
-

Provided by: Wildcard Assembler Plugin

+

Provided by: Wildcard Assembler Module