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..7815f11406a --- /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/WildcardAssemblerModule/Wildcard_Assembler.html||GHIDRA||||END| diff --git a/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java b/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java new file mode 100644 index 00000000000..c69734de66e --- /dev/null +++ b/Ghidra/Features/WildcardAssembler/ghidra_scripts/FindInstructionWithWildcard.java @@ -0,0 +1,338 @@ +/* ### + * 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 java.util.Map; +import java.util.Set; + +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.address.*; + +public class FindInstructionWithWildcard extends GhidraScript { + + public void run() throws Exception { + + 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); + + searchMemoryForEncodings(encodings, allValidResults); + } + + /** + * Use a {@link WildSleighAssembler} to assemble the given {@code wildcardedInstruction} + * + * @param wildcardedInstruction + * @return All {@link WildAssemblyResolvedPatterns} produced from the given input (e.g. All + * VALID results of assembling the given input) + */ + private List getAllResolvedPatterns( + String wildcardedInstruction) { + var allValidResults = new ArrayList(); + + SleighLanguage currentLanguage = (SleighLanguage) currentProgram.getLanguage(); + + // 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 = assembler.parseLine(wildcardedInstruction); + + // Remove all the AssemblyParseResults that represent parse errors + List allResults = parses.stream() + .filter(p -> !p.isError()) + .toList(); + + // 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 = assembler.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 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.isEmpty()) { + println("No assembly results for given assembly with wildcard!"); + 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 + Map> 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}. + */ + static 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 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.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()) { + // 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; + } + } + 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( + Map> encodings, + List allValidResolvedPatterns) + throws MemoryAccessException { + + Memory memory = currentProgram.getMemory(); + + for (var encoding : encodings.keySet()) { + 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, + List allValidResolvedPatterns) { + + println("Hit at address: " + matchAddress.toString()); + + // 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 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 + 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 + * {@link WildAssemblyResolvedPatterns} + * + * @param results + * 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(); + 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..6a7ad8b4353 --- /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.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 {@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( + 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 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 + 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 {@link 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 {@link 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 new file mode 100644 index 00000000000..48a46a3f67b --- /dev/null +++ b/Ghidra/Features/WildcardAssembler/src/main/help/help/TOC_Source.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + diff --git a/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerModule/Wildcard_Assembler.html b/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerModule/Wildcard_Assembler.html new file mode 100644 index 00000000000..d908f7a8c7e --- /dev/null +++ b/Ghidra/Features/WildcardAssembler/src/main/help/help/topics/WildcardAssemblerModule/Wildcard_Assembler.html @@ -0,0 +1,96 @@ + + + + + + + + Wildcard Assembler Module + + + + +

Wildcard Assembler Module

+ +
+ +

This feature is currently only available as an API for Ghidra + 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.

+ +

This assembler will return metadata for each wildcard in an assembled + instruction. This metadata includes details of which specific bits of an + assembled instruction are used to derive the value of the wildcarded token + and the expression used to derive the value.

+ +

Wildcard Syntax

+ +

Wildcards in instructions are specified by replacing the + to-be-wildcarded token with a wildcard name surrounded by backticks (e.g. + `Q1` where Q1 is an arbitrary wildcard name) and passing the + entire instruction to the Wildcard Assembler.

+ +

By default, the Wildcard Assembler will return metadata about all + possible values that a wildcarded token could take and all the encodings + of all these values. This behavior can be limited by filtering the + wildcard by appending specific syntax after the wildcard name:

+ +
    +
  • Numeric Filter: +
      +
    • Appending [..] will constrain the wildcarded token + to only numeric values (and not registers or other strings).
    • +
    • Appending [0x0..0x100] (where 0x0 and 0x100 are + arbitrary hexadecimal values with the smaller number first) will + constrain the wildcarded token to only numeric values between the + two given values. This can be used to ensure that the returned + encodings can hold values of a desired size. Multiple non-contiguous + ranges can be specified by separating them with commas (e.g. + [0x0..0x5,0x1000-0x4000])
    • +
    +
  • +
  • Regex Filter: +
      +
    • Appending /ABCD where ABCD is an arbitrary + regular expression will constrain the wildcarded token to only be + string tokens matching the given regular expression. This is most + likely used for filtering register names; for example appending + /(sp)|(lr) to a wildcard in a register position in + ARM assembly will limit the wildcard results to only encodings + using the sp or lr registers in that + position.
    • +
    +
  • +
+ +

Normally a wildcard will only match a single token. To allow a single + wildcard to match multiple related tokens: precede the wildcard name with a + ! character. For example, in a x86:LE:32:default binary:

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

Provided by: Wildcard Assembler Module

+ +
+ + 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)); + } +}