Add a WildcardAssembler Plugin.

This commit is contained in:
plucia 2024-01-13 17:50:54 -05:00 committed by Dan
parent 65dbaeaf57
commit 43131199cf
36 changed files with 3228 additions and 0 deletions

View File

@ -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')
}

View File

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

View File

@ -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<WildAssemblyResolvedPatterns> getAllResolvedPatterns(
String wildcardedInstruction) {
var allValidResults = new ArrayList<WildAssemblyResolvedPatterns>();
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<AssemblyParseResult> parses = assembler.parseLine(wildcardedInstruction);
// Remove all the AssemblyParseResults that represent parse errors
List<AssemblyParseResult> 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<AssemblyPatternBlock, Set<WildOperandInfo>> getMapOfUniqueInstructionEncodings(
List<WildAssemblyResolvedPatterns> 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<AssemblyPatternBlock, Set<WildOperandInfo>> encodings =
new HashMap<AssemblyPatternBlock, Set<WildOperandInfo>>();
for (WildAssemblyResolvedPatterns x : allValidResolvedPatterns) {
var y = new ReducedWildcardAssemblyResolvedPattern(x);
var existing = encodings.get(y.maskedInstruction);
if (existing == null) {
existing = new HashSet<WildOperandInfo>();
}
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.
* <p>
* This searches encoding by encoding, restarting back at the start of memory for each.
* <p>
* 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<AssemblyPatternBlock, Set<WildOperandInfo>> encodings,
List<WildAssemblyResolvedPatterns> 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
* <p>
* 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<WildAssemblyResolvedPatterns> 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<WildAssemblyResolvedPatterns> getValidResults(AssemblyResolutionResults results) {
var out = new ArrayList<WildAssemblyResolvedPatterns>();
for (AssemblyResolution result : results) {
if (result instanceof WildAssemblyResolvedPatterns resolvedPatterns) {
out.add(resolvedPatterns);
}
}
return out;
}
}

View File

@ -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<String> 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<AssemblyResolution> 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<AssemblyParseResult> 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<AssemblyResolution> 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);
}
}
}

View File

@ -0,0 +1,59 @@
<?xml version='1.0' encoding='ISO-8859-1' ?>
<!--
This is an XML file intended to be parsed by the Ghidra help system. It is loosely based
upon the JavaHelp table of contents document format. The Ghidra help system uses a
TOC_Source.xml file to allow a module with help to define how its contents appear in the
Ghidra help viewer's table of contents. The main document (in the Base module)
defines a basic structure for the
Ghidra table of contents system. Other TOC_Source.xml files may use this structure to insert
their files directly into this structure (and optionally define a substructure).
In this document, a tag can be either a <tocdef> or a <tocref>. The former is a definition
of an XML item that may have a link and may contain other <tocdef> and <tocref> children.
<tocdef> items may be referred to in other documents by using a <tocref> tag with the
appropriate id attribute value. Using these two tags allows any module to define a place
in the table of contents system (<tocdef>), which also provides a place for
other TOC_Source.xml files to insert content (<tocref>).
During the help build time, all TOC_Source.xml files will be parsed and validated to ensure
that all <tocref> tags point to valid <tocdef> tags. From these files will be generated
<module name>_TOC.xml files, which are table of contents files written in the format
desired by the JavaHelp system. Additionally, the genated files will be merged together
as they are loaded by the JavaHelp system. In the end, when displaying help in the Ghidra
help GUI, there will be on table of contents that has been created from the definitions in
all of the modules' TOC_Source.xml files.
Tags and Attributes
<tocdef>
-id - the name of the definition (this must be unique across all TOC_Source.xml files)
-text - the display text of the node, as seen in the help GUI
-target** - the file to display when the node is clicked in the GUI
-sortgroup - this is a string that defines where a given node should appear under a given
parent. The string values will be sorted by the JavaHelp system using
a javax.text.RulesBasedCollator. If this attribute is not specified, then
the text of attribute will be used.
<tocref>
-id - The id of the <tocdef> that this reference points to
**The URL for the target is relative and should start with 'help/topics'. This text is
used by the Ghidra help system to provide a universal starting point for all links so that
they can be resolved at runtime, across modules.
-->
<tocroot>
<tocref id="Ghidra Functionality">
<tocdef id="Wildcard Assembler"
text="Wildcard Assembler"
target="help/topics/WildcardAssemblerModule/Wildcard_Assembler.html" >
</tocdef>
</tocref>
</tocroot>

View File

@ -0,0 +1,96 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<HTML>
<HEAD>
<META http-equiv="Content-Language" content="en-us">
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
<TITLE>Wildcard Assembler Module</TITLE>
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
</HEAD>
<BODY>
<H1><A name="Wildcard_Assembler_Module"></A>Wildcard Assembler Module</H1>
<BLOCKQUOTE>
<P><B>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.</B></P>
<P>The <I>Wildcard Assembler</I> extends Ghidra's assembler to enable
assembling instructions with specific tokens replaced with wildcards.</P>
<p>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.</p>
<H2>Wildcard Syntax</H2>
<P>Wildcards in instructions are specified by replacing the
to-be-wildcarded token with a wildcard name surrounded by backticks (e.g.
<CODE>`Q1`</CODE> where Q1 is an arbitrary wildcard name) and passing the
entire instruction to the Wildcard Assembler.</P>
<P>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:</P>
<UL>
<LI><B>Numeric Filter:</B>
<UL>
<LI>Appending <CODE>[..]</CODE> will constrain the wildcarded token
to only numeric values (and not registers or other strings).</LI>
<LI>Appending <CODE>[0x0..0x100]</CODE> (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.
<CODE>[0x0..0x5,0x1000-0x4000]</CODE>)</LI>
</UL>
</LI>
<LI><B>Regex Filter:</B>
<UL>
<LI>Appending <CODE>/ABCD</CODE> 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
<CODE>/(sp)|(lr)</CODE> to a wildcard in a register position in
ARM assembly will limit the wildcard results to only encodings
using the <CODE>sp</CODE> or <CODE>lr</CODE> registers in that
position.</LI>
</UL>
</LI>
</UL>
<P>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
<CODE>!</CODE> character. For example, in a x86:LE:32:default binary:</P>
<BLOCKQUOTE>
<DL>
<DT>No wildcard:</DT>
<DD><CODE>MOVSD.REP ES:EDI,ESI</CODE></DD>
<DT>Single token:</DT>
<DD><CODE>MOVSD.REP `Q1`:EDI,ESI</CODE></DD>
<DT>Single token:</DT>
<DD><CODE>MOVSD.REP ES:`Q2`,ESI</CODE></DD>
<DT>Single token (Does <I>NOT</I> assemble):</DT>
<DD><CODE>MOVSD.REP `Q3`,ESI</CODE></DD>
<DT>Multi-token:</DT>
<DD><CODE>MOVSD.REP `!Q4`,ESI</CODE></DD>
</DL>
</BLOCKQUOTE>
<P class="providedbyplugin">Provided by: <I>Wildcard Assembler Module</I></P>
</BLOCKQUOTE>
</BODY>
</HTML>

View File

@ -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<AssemblyConstructorSemantic> path,
AssemblyPatternBlock location, PatternExpression expression, Object choice) {
public WildOperandInfo shift(int amt) {
return new WildOperandInfo(wildcard, path, location.shift(amt), expression, choice);
}
}

View File

@ -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<WildAssemblyResolvedPatterns> {
protected WildSleighAssembler(
AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> factory,
AssemblySelector selector, SleighLanguage lang, AssemblyParser parser,
AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) {
super(factory, selector, lang, parser, defaultContext, ctxGraph);
}
protected WildSleighAssembler(
AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> 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);
}
}

View File

@ -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<WildAssemblyResolvedPatterns, WildSleighAssembler> {
protected final Map<AssemblySymbol, AssemblyNonTerminal> 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<AssemblyNonTerminal> rhs, DisjointPattern pattern, Constructor cons,
List<Integer> indices) {
// Don't call super. We want to replace the original production
AssemblySentential<AssemblyNonTerminal> 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);
}
}

View File

@ -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<AssemblyNonTerminal> rhs) {
super(lhs, rhs);
}
@Override
public boolean isConstructor() {
return false;
}
}

View File

@ -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<WildOperandInfo> opInfo;
protected DefaultWildAssemblyResolvedPatterns(WildAssemblyResolutionFactory factory,
String description, Constructor cons, List<? extends AssemblyResolution> children,
AssemblyResolution right, AssemblyPatternBlock ins, AssemblyPatternBlock ctx,
Set<AssemblyResolvedBackfill> backfills, Set<AssemblyResolvedPatterns> forbids,
Set<WildOperandInfo> 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<WildOperandInfo> 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<AssemblyConstructorSemantic> 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<AssemblyConstructorSemantic> 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<AssemblyResolvedPatterns> 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<String, Long> vals) {
var builder = cast(super.solveContextChangesForForbidsBuilder(sem, vals));
builder.opInfo = opInfo;
return builder;
}
}

View File

@ -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();
}
}

View File

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

View File

@ -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<WildAssemblyParseToken> {
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<AssemblyGeneratedPrototype> 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));
}
}

View File

@ -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<AssemblyConstructorSemantic> 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<AssemblyResolvedPatterns> resolve(AssemblyResolvedPatterns fromRight,
Collection<AssemblyResolvedError> 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));
}
}

View File

@ -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<WildAssemblyParseToken> {
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<AssemblyGeneratedPrototype> 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));
}
}

View File

@ -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<WildAssemblyParseToken> {
protected final OperandSymbol opSym;
protected final Map<Long, Integer> map;
public WildAssemblyNumericMapStateGenerator(WildAssemblyTreeResolver resolver,
WildAssemblyParseToken node, OperandSymbol opSym, Map<Long, Integer> map,
AssemblyResolvedPatterns fromLeft) {
super(resolver, node, fromLeft);
this.opSym = opSym;
this.map = map;
}
@Override
public Stream<AssemblyGeneratedPrototype> 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));
}
}

View File

@ -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<AssemblyGeneratedPrototype> 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();
}
}

View File

@ -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<AssemblyConstructorSemantic> 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<AssemblyResolvedPatterns> resolve(AssemblyResolvedPatterns fromRight,
Collection<AssemblyResolvedError> 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));
}
}

View File

@ -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<WildAssemblyResolvedPatterns, AssemblyResolvedBackfill> {
protected class WildAssemblyResolvedPatternsBuilder
extends AbstractAssemblyResolvedPatternsBuilder<WildAssemblyResolvedPatterns> {
protected Set<WildOperandInfo> 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();
}
}

View File

@ -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<WildOperandInfo> getOperandInfo();
WildAssemblyResolvedPatterns withWildInfo(String wildcard,
List<AssemblyConstructorSemantic> path, AssemblyPatternBlock location,
PatternExpression expression, Object choice);
}

View File

@ -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<WildAssemblyParseToken> {
protected final OperandSymbol opSym;
protected final MultiValuedMap<String, Integer> map;
public WildAssemblyStringMapStateGenerator(WildAssemblyTreeResolver resolver,
WildAssemblyParseToken node, OperandSymbol opSym, MultiValuedMap<String, Integer> map,
AssemblyResolvedPatterns fromLeft) {
super(resolver, node, fromLeft);
this.opSym = opSym;
this.map = map;
}
@Override
public Stream<AssemblyGeneratedPrototype> 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));
}
}

View File

@ -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<WildAssemblyParseToken> {
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<AssemblyGeneratedPrototype> 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));
}
}

View File

@ -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<WildAssemblyResolvedPatterns> {
public WildAssemblyTreeResolver(
AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> 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);
}
}

View File

@ -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 + "]";
}
}

View File

@ -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;
}
}

View File

@ -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<Long, Integer> map;
public WildAssemblyNumericMapTerminal(String name, Map<Long, Integer> map) {
super(name);
this.map = map;
}
@Override
public String toString() {
return "[wildnum:" + name + "]";
}
}

View File

@ -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 + "]";
}
}

View File

@ -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<String, Integer> map;
public WildAssemblyStringMapTerminal(String name, MultiValuedMap<String, Integer> map) {
super(name);
this.map = map;
}
@Override
public String toString() {
return "[wildlist:" + name + "]";
}
}

View File

@ -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;
}
}

View File

@ -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("`(?<spec>![^`]*)`");
public WildAssemblySubtableTerminal(String name) {
super("`WILD`-" + name);
}
@Override
public String toString() {
return "[" + name + "]";
}
@Override
protected Pattern getPattern() {
return PAT_WILD_TREE;
}
@Override
public Collection<String> getSuggestions(String got, AssemblyNumericSymbols symbols) {
return List.of("`!Q1`");
}
}

View File

@ -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("`(?<spec>[^`]*)`");
public WildAssemblyTerminal(String name) {
super(name);
}
protected Pattern getPattern() {
return PAT_WILD;
}
@Override
public Collection<? extends AssemblyParseToken> match(String buffer, int pos,
AssemblyGrammar grammar, AssemblyNumericSymbols symbols) {
Matcher matcher = getPattern().matcher(buffer).region(pos, buffer.length());
if (!matcher.lookingAt()) {
return List.of();
}
return List.of(
new WildAssemblyParseToken(grammar, this, matcher.group(), matcher.group("spec")));
}
@Override
public Collection<String> getSuggestions(String got, AssemblyNumericSymbols symbols) {
return List.of("`Q1`");
}
}

View File

@ -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("<wild-hidden>");
}
@Override
public String generateString() {
return "";
}
}

View File

@ -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("(?<name>[^/]*)/(?<regex>.*)");
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("(?<name>.*)\\[\\.\\.\\]");
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<WildRange> {
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<WildRange> ranges) implements Wildcard {
public static final Pattern PATTERN =
Pattern.compile("(?<name>[^\\[]*)\\[(?<ranges>[^\\]]*)\\]");
public static RangesWildcard get(Matcher matcher) {
return new RangesWildcard(matcher.group("name"), parseRanges(matcher.group("ranges")));
}
public static List<WildRange> 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();
}
}

View File

@ -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<WildAssemblyResolvedPatterns> getValidResults(
AssemblyResolutionResults results) {
var out = new ArrayList<WildAssemblyResolvedPatterns>();
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<String> 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<AssemblyPatternBlock> getInstructionPatterns(AssemblyResolutionResults results) {
return getValidResults(results).stream()
.map(res -> res.getInstruction())
.collect(Collectors.toSet());
}
protected Set<AssemblyPatternBlock> 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<byte[]> getInstructionValues(AssemblyResolutionResults results) {
var out = new ArrayList<byte[]>();
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<String> 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<AssemblyParseResult> parses = asmToy.parseLine("add `Q1`, #6");
assertTrue(parses.stream().anyMatch(p -> !p.isError()));
}
@Test
public void testParseWildImm_Toy() throws Exception {
toy();
Collection<AssemblyParseResult> parses = asmToy.parseLine("add r0, #`Q2`");
assertTrue(parses.stream().anyMatch(p -> !p.isError()));
}
@Test
public void testParseWildStr_Err_Toy() throws Exception {
toy();
Collection<AssemblyParseResult> parses = asmToy.parseLine("add r0, `Q3`6");
assertFalse(parses.stream().anyMatch(p -> !p.isError()));
}
@Test
public void testParseWildRegAndImmOp_Toy() throws Exception {
toy();
Collection<AssemblyParseResult> parses = asmToy.parseLine("add `Q1`, #`Q2`");
assertTrue(parses.stream().anyMatch(p -> !p.isError()));
}
@Test
public void testParseAndResolveWildRegOp_Toy() throws Exception {
toy();
Collection<AssemblyParseResult> 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<AssemblyParseResult> 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<AssemblyParseResult> parses =
asmToy.parseLine("add r0, `!Q2`").stream().filter(p -> !p.isError()).toList();
Address addr0 = toy.getAddressFactory().getDefaultAddressSpace().getAddress(0);
var allValidPatterns = new HashSet<AssemblyPatternBlock>();
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<AssemblyParseResult> parses =
asmToy.parseLine("add r0, `Q2`").stream().filter(p -> !p.isError()).toList();
Address addr0 = toy.getAddressFactory().getDefaultAddressSpace().getAddress(0);
var allValidPatterns = new HashSet<AssemblyPatternBlock>();
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<AssemblyParseResult> parses = asmArm.parseLine("mov `Q1`, #0x0");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidChoices = new ArrayList<String>();
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmArm.parseLine("sub r1,r1,#0x200");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmArm.parseLine("sub `Q3/r.`,`Q4`,#0x200");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmMips.parseLine("restore 0x1b8,ra,s0-s1");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmMips.parseLine("restore `Q1`,ra,s0-s1");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidResults = new ArrayList<WildAssemblyResolvedPatterns>();
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<AssemblyParseResult> parses = asmMips.parseLine("lw `Q1`,0x0(a0)");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
// 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<AssemblyParseResult> parses = asmX86.parseLine("CALL 0x004058f3");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses =
asmX86.parseLine("CALL `Q1[0x00400000..0x00400003]`");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidResults = new ArrayList<WildAssemblyResolvedPatterns>();
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmX86.parseLine("MOV EBP,`Q1/E.P`");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmX86.parseLine("SHRD EAX,EBX,0x7");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmX86.parseLine("SHRD EAX,EBX,`Q1[..]`");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidResults = new ArrayList<WildAssemblyResolvedPatterns>();
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<AssemblyParseResult> parses = asmX86.parseLine("CALL DX");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmX8664.parseLine("CALL DX");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses = asmX8664.parseLine("CALL `Q1/(C|D)X`");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidChoices = new ArrayList<String>();
var allValidResults = new ArrayList<AssemblyPatternBlock>();
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<AssemblyParseResult> parses = asmX8664.parseLine("LEA EAX, [ EDX + -0x6c ]");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidResults = new ArrayList<AssemblyPatternBlock>();
var allValidEncodings = new ArrayList<String>();
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<AssemblyParseResult> parses =
asmX8664.parseLine("LEA EAX, [ `Q1/EDX` + -0x6c ]");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidChoices = new ArrayList<String>();
var allValidResults = new ArrayList<WildAssemblyResolvedPatterns>();
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));
}
}