Merge remote-tracking branch 'origin/GP-1426_Dan_asmWoW64--SQUASHED'

This commit is contained in:
Ryan Kurtz
2022-03-29 01:27:33 -04:00
114 changed files with 4968 additions and 1820 deletions
@@ -38,6 +38,7 @@ import ghidra.program.model.lang.*;
import ghidra.program.model.listing.Instruction;
import ghidra.test.AbstractGhidraHeadlessIntegrationTest;
import ghidra.trace.database.ToyDBTraceBuilder;
import ghidra.trace.database.context.DBTraceRegisterContextManager;
import ghidra.trace.model.memory.TraceMemoryFlag;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.thread.TraceThread;
@@ -864,17 +865,13 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
try (ToyDBTraceBuilder tb = new ToyDBTraceBuilder("Test", "x86:LE:64:default")) {
Language lang = tb.trace.getBaseLanguage();
Register ctxReg = lang.getContextBaseRegister();
Register opsizeReg = lang.getRegister("opsize");
Register addrsizeReg = lang.getRegister("addrsize");
Register longModeReg = lang.getRegister("longMode");
RegisterValue ctxVal = new RegisterValue(ctxReg)
.assign(opsizeReg, BigInteger.ONE)
.assign(addrsizeReg, BigInteger.ONE)
.assign(longModeReg, BigInteger.ZERO);
DBTraceRegisterContextManager ctxManager = tb.trace.getRegisterContextManager();
try (UndoableTransaction tid = tb.startTransaction()) {
tb.trace.getRegisterContextManager()
.setValue(lang, ctxVal, Range.atLeast(0L),
tb.range(0x00400000, 0x00400002));
ctxManager.setValue(lang, ctxVal, Range.atLeast(0L),
tb.range(0x00400000, 0x00400002));
}
TraceThread thread = initTrace(tb,
List.of(
@@ -891,6 +888,8 @@ public class TracePcodeEmulatorTest extends AbstractGhidraHeadlessIntegrationTes
TracePcodeEmulator emu = new TracePcodeEmulator(tb.trace, 0);
PcodeThread<byte[]> emuThread = emu.newThread(thread.getPath());
// TODO: Seems the Trace-bound thread ought to know to do this in reInitialize()
ctxVal = ctxManager.getValueWithDefault(lang, ctxReg, 0, tb.addr(0x00400000));
emuThread.overrideContext(ctxVal);
emuThread.stepInstruction();
emuThread.stepInstruction();
@@ -63,7 +63,7 @@ public class AssemblyThrasherDevScript extends GhidraScript {
}
@Override
public AssemblyResolvedConstructor select(AssemblyResolutionResults rr,
public AssemblyResolvedPatterns select(AssemblyResolutionResults rr,
AssemblyPatternBlock ctx) throws AssemblySemanticException {
StringBuilder sb = new StringBuilder();
boolean gotOne = false;
@@ -72,7 +72,7 @@ public class AssemblyThrasherDevScript extends GhidraScript {
if (ar.isError()) {
continue;
}
AssemblyResolvedConstructor can = (AssemblyResolvedConstructor) ar;
AssemblyResolvedPatterns can = (AssemblyResolvedPatterns) ar;
if (can.getContext().combine(ctx) == null) {
continue;
}
@@ -704,7 +704,7 @@ public class AssemblyDualTextField {
* @param existing the instruction, if any, currently under the user's cursor
* @return a preference
*/
protected int computePreference(AssemblyResolvedConstructor rc, Instruction existing) {
protected int computePreference(AssemblyResolvedPatterns rc, Instruction existing) {
if (existing == null) {
return 0;
}
@@ -763,7 +763,7 @@ public class AssemblyDualTextField {
//result.add(new AssemblyError("", ar.toString()));
continue;
}
AssemblyResolvedConstructor rc = (AssemblyResolvedConstructor) ar;
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar;
for (byte[] ins : rc.possibleInsVals(ctx)) {
result.add(new AssemblyInstruction(text, Arrays.copyOf(ins, ins.length),
computePreference(rc, existing)));
@@ -42,7 +42,8 @@ public interface Assembler {
* refer to pseudo instructions.
*
* <p>
* NOTE: There must be an active transaction on the bound program for this method to succeed.
* <b>NOTE:</b> There must be an active transaction on the bound program for this method to
* succeed.
*
* @param at the location where the resulting instructions should be placed
* @param listing a new-line separated or array sequence of instructions
@@ -119,8 +120,8 @@ public interface Assembler {
* results.
*
* <p>
* NOTE: The resolved instructions are given as masks and values. Where the mask does not cover,
* you can choose any value.
* <b>NOTE:</b> The resolved instructions are given as masks and values. Where the mask does not
* cover, you can choose any value.
*
* @param parse a parse result giving a valid tree
* @param at the location of the start of the instruction
@@ -139,8 +140,8 @@ public interface Assembler {
* results.
*
* <p>
* NOTE: The resolved instructions are given as masks and values. Where the mask does not cover,
* you can choose any value.
* <b>NOTE:</b> The resolved instructions are given as masks and values. Where the mask does not
* cover, you can choose any value.
*
* @param parse a parse result giving a valid tree
* @param at the location of the start of the instruction
@@ -192,7 +193,7 @@ public interface Assembler {
* @return the new {@link Instruction} code unit
* @throws MemoryAccessException there is an issue writing the result to program memory
*/
public Instruction patchProgram(AssemblyResolvedConstructor res, Address at)
public Instruction patchProgram(AssemblyResolvedPatterns res, Address at)
throws MemoryAccessException;
/**
@@ -25,18 +25,21 @@ import ghidra.program.model.listing.Program;
public interface AssemblerBuilder {
/**
* Get the ID of the language for which this instance builds an assembler
*
* @return the language ID
*/
public LanguageID getLanguageID();
/**
* Get the language for which this instance builds an assembler
*
* @return the language
*/
public Language getLanguage();
/**
* Build an assembler with the given selector callback
*
* @param selector the selector callback
* @return the built assembler
*/
@@ -44,6 +47,7 @@ public interface AssemblerBuilder {
/**
* Build an assembler with the given selector callback and program binding
*
* @param selector the selector callback
* @param program the bound program
* @return the built assembler
@@ -19,19 +19,20 @@ import java.util.*;
import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult;
import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.util.SleighUtil;
import ghidra.app.plugin.assembler.sleigh.util.AsmUtil;
/**
* Provides a mechanism for pruning and selecting binary assembled instructions from the results
* of parsing textual assembly instructions. There are two opportunities: After parsing, but before
* semantic resolution, and after resolution. In the first opportunity, filtering is optional ---
* the user may discard any or all parse trees. The second is required, since only one instruction
* may be placed at the desired address --- the user must select one instruction among the many
* results, and if a mask is present, decide on a value for the omitted bits.
* Provides a mechanism for pruning and selecting binary assembled instructions from the results of
* parsing textual assembly instructions. There are two opportunities: After parsing, but before
* prototype generation, and after machine code generation. In the first opportunity, filtering is
* optional --- the user may discard any or all parse trees. The second is required, since only one
* instruction may be placed at the desired address --- the user must select one instruction among
* the many results, and if a mask is present, decide on a value for the omitted bits.
*
* <p>
* Extensions of this class are also suitable for collecting diagnostic information about attempted
* assemblies. For example, an implementation may employ the syntax errors in order to produce
* code completion suggestions in a GUI.
* assemblies. For example, an implementation may employ the syntax errors in order to produce code
* completion suggestions in a GUI.
*/
public class AssemblySelector {
protected Set<AssemblyParseResult> syntaxErrors = new TreeSet<>();
@@ -40,7 +41,7 @@ public class AssemblySelector {
/**
* A comparator on instruction length (shortest first), then bits lexicographically
*/
protected Comparator<AssemblyResolvedConstructor> compareBySizeThenBits = (a, b) -> {
protected Comparator<AssemblyResolvedPatterns> compareBySizeThenBits = (a, b) -> {
int result;
result = a.getInstructionLength() - b.getInstructionLength();
if (result != 0) {
@@ -48,7 +49,7 @@ public class AssemblySelector {
}
result =
SleighUtil.compareArrays(a.getInstruction().getVals(), b.getInstruction().getVals());
AsmUtil.compareArrays(a.getInstruction().getVals(), b.getInstruction().getVals());
if (result != 0) {
return result;
}
@@ -58,16 +59,20 @@ public class AssemblySelector {
/**
* Filter a collection of parse trees.
*
* Generally, the assembly resolver considers every possible parsing of an assembly
* instruction. If, for some reason, the user wishes to ignore certain trees (perhaps for
* efficiency, or perhaps because a certain form of instruction is desired), entire parse
* trees may be pruned here.
* <p>
* Generally, the assembly resolver considers every possible parsing of an assembly instruction.
* If, for some reason, the user wishes to ignore certain trees (perhaps for efficiency, or
* perhaps because a certain form of instruction is desired), entire parse trees may be pruned
* here.
*
* It's possible that no trees pass the filter. In this case, this method ought to throw an
* {@link AssemblySyntaxException}. Another option is to pass the erroneous result on for semantic
* analysis, in which case, the error is simply copied into an erroneous semantic result.
* Depending on preferences, this may simplify the overall filtering and error-handling logic.
* <p>
* It is possible that no trees pass the filter. In this case, this method ought to throw an
* {@link AssemblySyntaxException}. Another option is to pass the erroneous result on for
* semantic analysis, in which case, the error is simply copied into an erroneous semantic
* result. Depending on preferences, this may simplify the overall filtering and error-handling
* logic.
*
* <p>
* By default, no filtering is applied. If all the trees produce syntax errors, an exception is
* thrown.
*
@@ -95,10 +100,12 @@ public class AssemblySelector {
/**
* Select an instruction from the possible results.
*
* Must select precisely one resolved constructor from the results given back by the assembly
* resolver. Precisely one. That means the mask of the returned result must consist of all 1s.
* Also, if no selection is suitable, an exception must be thrown.
* <p>
* This must select precisely one resolved constructor from the results given back by the
* assembly resolver. This further implies the mask of the returned result must consist of all
* 1s. If no selection is suitable, this must throw an exception.
*
* <p>
* By default, this method selects the shortest instruction that is compatible with the given
* context and takes 0 for bits that fall outside the mask. If all possible resolutions produce
* errors, an exception is thrown.
@@ -106,18 +113,18 @@ public class AssemblySelector {
* @param rr the collection of resolved constructors
* @param ctx the applicable context.
* @return a single resolved constructor with a full instruction mask.
* @throws AssemblySemanticException
* @throws AssemblySemanticException
*/
public AssemblyResolvedConstructor select(AssemblyResolutionResults rr,
public AssemblyResolvedPatterns select(AssemblyResolutionResults rr,
AssemblyPatternBlock ctx) throws AssemblySemanticException {
List<AssemblyResolvedConstructor> sorted = new ArrayList<>();
List<AssemblyResolvedPatterns> sorted = new ArrayList<>();
// Select only non-erroneous results whose contexts are compatible.
for (AssemblyResolution ar : rr) {
if (ar.isError()) {
semanticErrors.add((AssemblyResolvedError) ar);
continue;
}
AssemblyResolvedConstructor rc = (AssemblyResolvedConstructor) ar;
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar;
sorted.add(rc);
}
if (sorted.isEmpty()) {
@@ -127,9 +134,9 @@ public class AssemblySelector {
sorted.sort(compareBySizeThenBits);
// Pick just the first
AssemblyResolvedConstructor res = sorted.get(0);
AssemblyResolvedPatterns res = sorted.get(0);
// Just set the mask to ffs (effectively choosing 0 for the omitted bits)
return AssemblyResolution.resolved(res.getInstruction().fillMask(), res.getContext(),
"Selected", null);
"Selected", null, null, null);
}
}
@@ -26,6 +26,7 @@ import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedError;
/**
* Thrown when all resolutions of an assembly instruction result in semantic errors.
*
* <p>
* For SLEIGH, semantic errors amount to incompatible contexts
*/
public class AssemblySemanticException extends AssemblyException {
@@ -37,6 +38,7 @@ public class AssemblySemanticException extends AssemblyException {
/**
* Construct a semantic exception with the associated semantic errors
*
* @param errors the associated semantic errors
*/
public AssemblySemanticException(Set<AssemblyResolvedError> errors) {
@@ -46,6 +48,7 @@ public class AssemblySemanticException extends AssemblyException {
/**
* Get the collection of associated semantic errors
*
* @return the collection
*/
public Collection<AssemblyResolvedError> getErrors() {
@@ -35,6 +35,7 @@ public class AssemblySyntaxException extends AssemblyException {
/**
* Construct a syntax exception with the associated syntax errors
*
* @param errors the associated syntax errors
*/
public AssemblySyntaxException(Set<AssemblyParseResult> errors) {
@@ -44,6 +45,7 @@ public class AssemblySyntaxException extends AssemblyException {
/**
* Get the collection of associated syntax errors
*
* @return the collection
*/
public Collection<AssemblyParseResult> getErrors() {
@@ -17,11 +17,12 @@ package ghidra.app.plugin.assembler.sleigh;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.*;
import java.util.Collection;
import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.assembler.sleigh.parse.*;
import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNumericSymbols;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.program.disassemble.Disassembler;
@@ -32,17 +33,16 @@ import ghidra.program.model.lang.RegisterValue;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.symbol.*;
import ghidra.util.task.TaskMonitor;
/**
* An {@link Assembler} for a {@link SleighLanguage}.
*
* To obtain one of these, please use {@link SleighAssemblerBuilder}, or better yet, the static
* methods of {@link Assemblers}.
* <p>
* For documentation on how the SLEIGH assembler works, see {@link SleighAssemblerBuilder}. To use
* the assembler, please use {@link Assemblers#getAssembler(Program)} or similar.
*/
public class SleighAssembler implements Assembler {
public static final int DEFAULT_MAX_RECURSION_DEPTH = 2; // TODO: Toss this
protected static final DbgTimer dbg = DbgTimer.INACTIVE;
protected AssemblySelector selector;
@@ -75,7 +75,8 @@ public class SleighAssembler implements Assembler {
/**
* Construct a SleighAssembler.
*
* NOTE: This variant does not permit {@link #assemble(Address, String...)}.
* <p>
* <b>NOTE:</b> This variant does not permit {@link #assemble(Address, String...)}.
*
* @param selector a method of selecting one result from many
* @param lang the SLEIGH language (must be same as to create the parser)
@@ -93,7 +94,7 @@ public class SleighAssembler implements Assembler {
}
@Override
public Instruction patchProgram(AssemblyResolvedConstructor res, Address at)
public Instruction patchProgram(AssemblyResolvedPatterns res, Address at)
throws MemoryAccessException {
if (!res.getInstruction().isFullMask()) {
throw new AssemblySelectionError("Selected instruction must have a full mask.");
@@ -157,7 +158,7 @@ public class SleighAssembler implements Assembler {
@Override
public Collection<AssemblyParseResult> parseLine(String line) {
return parser.parse(line, getProgramLabels());
return parser.parse(line, getNumericSymbols());
}
@Override
@@ -173,13 +174,13 @@ public class SleighAssembler implements Assembler {
if (parse.isError()) {
AssemblyResolutionResults results = new AssemblyResolutionResults();
AssemblyParseErrorResult err = (AssemblyParseErrorResult) parse;
results.add(AssemblyResolution.error(err.describeError(), "Parsing", null));
results.add(AssemblyResolution.error(err.describeError(), "Parsing"));
return results;
}
AssemblyParseAcceptResult acc = (AssemblyParseAcceptResult) parse;
AssemblyTreeResolver tr =
new AssemblyTreeResolver(lang, at.getOffset(), acc.getTree(), ctx, ctxGraph);
new AssemblyTreeResolver(lang, at, acc.getTree(), ctx, ctxGraph);
return tr.resolve();
}
@@ -219,7 +220,7 @@ public class SleighAssembler implements Assembler {
public byte[] assembleLine(Address at, String line, AssemblyPatternBlock ctx)
throws AssemblySemanticException, AssemblySyntaxException {
AssemblyResolutionResults results = resolveLine(at, line, ctx);
AssemblyResolvedConstructor res = selector.select(results, ctx);
AssemblyResolvedPatterns res = selector.select(results, ctx);
if (res == null) {
throw new AssemblySelectionError(
"Must select exactly one instruction. Report errors via AssemblySemanticError");
@@ -234,37 +235,15 @@ public class SleighAssembler implements Assembler {
}
/**
* A convenience to obtain a map of program labels strings to long values
* A convenience to obtain assembly symbols
*
* @return the map
*
* {@literal TODO Use a Map<String, Address> instead so that, if possible, symbol values can be checked}
* lest they be an invalid substitution for a given operand.
*/
protected Map<String, Long> getProgramLabels() {
Map<String, Long> labels = new HashMap<>();
for (Register reg : lang.getRegisters()) {
// TODO/HACK: There ought to be a better mechanism describing suitable symbolic
// substitutions for a given operand.
if (!"register".equals(reg.getAddressSpace().getName())) {
labels.put(reg.getName(), (long) reg.getOffset());
}
}
protected AssemblyNumericSymbols getNumericSymbols() {
if (program != null) {
final SymbolIterator it = program.getSymbolTable().getAllSymbols(false);
while (it.hasNext()) {
Symbol sym = it.next();
if (sym.isExternal()) {
continue; // skip externals - will generally be referenced indirectly not directly
}
SymbolType symbolType = sym.getSymbolType();
if (symbolType != SymbolType.LABEL && symbolType != SymbolType.FUNCTION) {
continue;
}
labels.put(sym.getName(), sym.getAddress().getOffset());
}
return AssemblyNumericSymbols.fromProgram(program);
}
return labels;
return AssemblyNumericSymbols.fromLanguage(lang);
}
@Override
@@ -24,8 +24,7 @@ import ghidra.app.plugin.assembler.*;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblySentential;
import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParser;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyContextGraph;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyDefaultContext;
import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.symbol.*;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx;
@@ -43,57 +42,267 @@ import ghidra.util.SystemUtilities;
/**
* An {@link AssemblerBuilder} capable of supporting almost any {@link SleighLanguage}
*
* <p>
* To build an assembler, please use a static method of the {@link Assemblers} class.
*
* SLEIGH-based assembly is a bit of an experimental feature at this time. Nevertheless, it seems to
* have come along quite nicely. It's not quite as fast as disassembly, since after all, that's what
* SLEIGH was designed to do.
* <p>
* SLEIGH-based assembly is a bit temperamental, since it essentially runs the disassembler
* backwards. The process is tenuous, but works well enough for interactive single-instruction
* assembly. It is not nearly as fast as disassembly, since after all, SLEIGH was not designed for
* assembly. The assembler is great for interactive patching and for building small samples in unit
* tests. For other cases, a real tool chain is likely more appropriate.
*
* Overall, the method is fairly simple, though its implementation is a bit more complex. First, we
* gather every pair of pattern and constructor by traversing the decision tree used by disassembly.
* We then use the "print pieces" to construct a context-free grammar. Each production is associated
* with the one-or-more constructors with the same sequence of print pieces. We then build a LALR(1)
* parser for the generated grammar. This now constitutes a generic parser for the given language.
* Note that this step takes some time, and may be better suited as a build-time step. Because
* SLEIGH specifications are not generally concerned with eliminating ambiguity of printed
* instructions (rather, it only does so for instruction bytes), we must consider that the grammar
* could be ambiguous. To handle this, the action/goto table is permitted multiple entries per cell,
* and we allow backtracking. There are also cases where tokens are not actually separated by
* spaces. For example, in the {@code ia.sinc} file, there is JMP ... and J^cc, meaning, the lexer
* must consider J as a token as well as JMP, introducing another source of possible backtracking.
* Despite that, parsing is completed fairly quickly.
* <h2>A Review of Disassembly</h2>
*
* To assemble, we first parse the textual instruction, yielding zero or more parse trees. No parse
* trees implies an error. For each parse tree, we attempt to resolve the instruction bytes,
* starting at the leaves and working upwards while tracking and solving context changes. The
* context changes must be considered in reverse. We <em>read</em> the context register of the
* children (a disassembler would write). We then assume there is at most one variable in the
* expression, solve for it, and <em>write</em> the solution to the appropriate field (a
* disassembler would read). If no solution exists, a semantic error is logged. Since it's possible
* a production in the parse tree is associated with multiple constructors, different combinations
* of constructors are explored as we move upward in the tree. If all possible combinations yield
* semantic errors, then the overall result is an error.
* <p>
* Before diving into assembly, it may be helpful to review SLEIGH and disassembly, at least as far
* as I understand. SLEIGH is really a specification of three distinct things, all related by trees
* of "constructors." 1) A mnemonic grammar, 2) A machine-code grammar, 3) Run-time semantics, i.e.,
* p-code. The third is consumed primarily by the decompiler, the emulator, and other analysis, and
* is of little concern to the (dis)assembler. All three are tightly bound. A single constructor
* specifies a production in both grammars, constraints for selecting the production, as well as the
* generated run-time semantics. Consider an example:
*
* Some productions are "purely recursive," e.g., {@code :^instruction} lines in the SLEIGH. These
* are ignored during parser construction. Let such a production be given as I =&gt; I. When resolving
* the parse tree to bytes, and we encounter a production with I on the left hand side, we then
* consider the possible application of the production I =&gt; I and its consequential constructors.
* Ideally, we could repeat this indefinitely, stopping when all further applications result in
* semantic errors; however, there is no guarantee in the SLEIGH specification that such an
* algorithm will actually halt, so a maximum number (default of 1) of applications are attempted.
* <pre>
* :ADD regD,imm8 is op=5 & regD & imm8 { regD = regD + imm8; }
* </pre>
*
* After all the context changes and operands are resolved, we apply the constructor patterns and
* proceed up the tree. Thus, each branch yields zero or more "resolved constructors," which each
* specify two masked blocks of data: one for the instruction, and one for the context. These are
* passed up to the parent production, which, having obtained results from all its children,
* attempts to apply the corresponding constructors.
* <p>
* The colon indicates this constructor applies to the root "instruction" table. The mnemonic
* production precedes the <code>is</code> keyword. The machine-code constraints and production
* follow. Finally, the semantics appear within braces.
*
* Once we've resolved the root node, any resolved constructors returned are taken as successfully
* assembled instruction bytes. If applicable, the corresponding context registers are compared to
* the context at the target address in the program and filtered for compatibility.
* <p>
* To support bitfield parsing, a list of token formats and fields within must be declared. The
* machine-code production may specify constraints in terms of those fields. Such constraints become
* patterns that the parser uses to choose a constructor. For example, we may have
* <code>op=(0,3);regD=(4,7);imm8=(8,15)</code>. In little endian, this would indicate a 2-byte
* token:
*
* <pre>
* +-4----+-4----+-8----------+
* | regD | op | imm8 |
* +------+------+------------+
* </pre>
*
* <p>
* Thus, this constructor is assigned the pattern <code>0101....</code>, which handles
* <code>op=5</code>. <code>regD</code> and <code>imm8</code> remain as operands. The operands of
* the machine-code production refer to fields and subtables. During disassembly, those operands are
* parsed in the order named: left to right, depth first. For the (root) instruction table and each
* subtable, the disassembler selects exactly one constructor. The parser may only examine one
* machine-code token at a time; however, the token can be large (32 bits is common), and it may
* make several sub-table decisions based on fields within a single token, essentially allowing it
* to look ahead and parse those fields out of order. In the example, the parser will technically
* examine the <code>op</code> field before parsing <code>regD</code>.
*
* <p>
* When parsing a table or subtable, if no constructor's constraints can be matched, parsing fails.
* Each token is some number of bytes in size. The parser advances to the next token when it
* encounters a semicolon in the machine-code production. Note that when the parser returns to a
* parent constructor, i.e., the PDA pops its stack, the parser may return to a previous token. If
* that behavior is not desired, a machine-code production may contain ellipses, causing the parser
* to advance to the next token, even considering those tokens already examined by operands to the
* ellipses' left. Once all operands of the selected instruction constructor have been parsed, the
* resulting constructor tree ("prototype") is recorded and returned.
*
* <p>
* To display the instruction's mnemonic, the prototype is walked, generating the tokens ("print
* pieces") from the mnemonic production of each constructor. The walk is ordered according to that
* mnemonic production. The mnemonic grammar consists of syntactic text and symbols. Any symbols it
* uses must also appear in the machine-code production. Where the symbol is a sub-table, it behaves
* like a non-terminal in the grammar: It generates the print pieces of the constructor selected for
* the sub-table. Where the symbol is a field, it behaves like a terminal. It displays the numeric
* value of the field, or in the case of attached names, e.g., register names, it displays the name.
*
* <p>
* To complicate matters, but greatly increase the capability of the disassembler, SLEIGH introduces
* temporary symbols and context to the disassembler. A temporary symbol allows the computation of
* displayed values from fields. (The value may also be used by the p-code generator.) For example,
* a language may permit the expression of immediates as a value and a shift. Temporary symbols
* permit the effective value to be computed and displayed. Thus, a temporary symbol is valid in the
* mnemonic production. Context serves at least two purposes: 1) To propagate auxiliary information
* to sub-tables during disassembly, and 2) To handle persistent state changes in a processor that
* modify its decoder, e.g., ARM in THUMB mode. The latter is accomplished by marking regions of
* memory with this contextual information. Context is implemented by introducing a context
* register. It behaves like a special mutable token, initialized from the disassembler's memory,
* the context marked at the instruction's start address, or the language's default context. Like
* token fields, context fields can be referred to by a constructor's machine-code production,
* either to form constraints or to parse as operands. Fields may be modified by including mutations
* in the constructor. Mutations and temporary symbols are defined by assigning an expression to the
* field or symbol. Those expressions may refer to other fields and temporary symbols in the scope
* of that constructor. Since mutations are meant to be propagated down, they must be applied in
* pre-order during parsing. Note that context is not saved on any sort of stack, thus it is
* possible for context mutations in a sub-table operand (and its sub-table operands) to affect
* parsing of sibling sub-table operands to the right.
*
* <p>
* When disassembling entire subroutines, the disassembler must propagate context changes from
* instruction to instruction. Some bits of the context register are marked "global." Those bits,
* when instruction parsing succeeds, are taken as the "output context" of the resulting
* instruction. Propagation follows from a recursive traversal disassembly strategy, i.e., it heeds
* the branch targets of the instruction. The generated p-code is used to determine whether the
* instruction has branches and/or fall-through. If the output context differs from the default
* context, the disassembler saves it as the initial context for the next instruction. If the
* instruction has a branch target, the output context is marked at the target address.
*
* <h2>Assembly</h2>
*
* <p>
* Conceptually, assembly is a straightforward reversal of the disassembly process; however, the
* actual implementation is far more complex. To assemble an instruction there are three distinct
* phases: 1) Parsing, 2) Prototype generation, 3) Machine code generation. Each phase may take
* advantage of pre-computed artifacts.
*
* <h3>Parsing Assembly Mnemonics</h3>
*
* <p>
* To parse, we pre-compute a LALR(1) parser based on mnemonic grammar. Because different
* constructors may specify the same mnemonic production as others in the same table, we have to
* associate all such constructors to the production. This step takes some time, and may be better
* suited as a build-time step. Because SLEIGH specifications are not generally concerned with
* eliminating ambiguity of printed instructions (rather, it only does so for instruction bytes), we
* must consider that the grammar could be ambiguous. To handle this, the action/goto table is
* permitted multiple entries per cell, and we allow backtracking. There are also cases where tokens
* are not actually separated by spaces. For example, in the {@code ia.sinc} file, there is JMP, and
* J^cc, meaning, the lexer must consider J as a token as well as JMP, introducing another source of
* possible backtracking. Despite that, parsing an instruction is fairly quick, since the sentences
* are rather short. The pre-compute part of this process is implemented in {@link #buildGrammar()}
* and {@link #buildParser()}. Parsing is then encapsulated in {@link AssemblyParser}.
*
* <h3>Prototype Generation</h3>
*
* <p>
* To generate prototypes, we examine each resulting parse tree. If there are no parse trees, then a
* syntax errors is reported. Otherwise, for each tree, starting at the root production, we consider
* all associated constructors, matching each print piece to its corresponding operand on the
* machine-code side. For sub-table operands, the production substituted for the associated
* non-terminal guides generation, recursively. For other operands, the associated terminal provides
* the value or name. To mimic the token advancement of the disassembler, a shift is computed and
* stored for each operand. Computing the shift requires computing each operand's length, and so
* once the root of each prototype is generated, the instruction length is also known. Patterns and
* mutations are applied to mimic the disassembly process: pre-ordered, depth first, left to right,
* heeding the computed shift. If a pattern or mutation for a constructor conflicts with what's been
* generated so far, the constructor is pruned. If all possible constructors for a sub-table operand
* are pruned, then the containing constructor is also pruned.
*
* <p>
* In some cases, an operand appears in the machine-code production, but not the mnemonic
* production: so-called "hidden operands." These pose a potential issue for the assembler, because
* nothing syntactic can guide prototype generation. For hidden sub-table operands, we must consider
* all constructors in the table. Furthermore, all operands of those constructors are considered
* "hidden," and so we exhaust recursively. For other hidden operands, the value is left
* unspecified. The prototype generation process is encapsulated in
* {@link AssemblyConstructStateGenerator}.
*
* <h3>Machine Code Generation</h3>
*
* <p>
* Machine code generation is a complex process, but it follows a straightforward reversal of the
* disassembler's parse phase. For each prototype, we start at the leaves (non-sub-table operands)
* and proceed upwards. This is still a depth-first traversal, but unlike disassembly, generation
* proceeds in post-order and right to left, as follows. Starting at the root:
*
* <ol>
* <li>Resolve operands from right to left, descending into sub-table operands.</li>
* <li>Solve context mutations, in reverse order.
* <li>Apply the required patterns
* </ol>
*
* <p>
* Note that for a single prototype, a constructor has already been selected for each sub-table
* operand. The resolution of sub-table operands follows the same process as for the root
* constructor.
*
* <p>
* For other operands, resolution proceeds by solving the operand's defining expression set equal to
* the value specified by the terminal. The resulting values are written into their respective token
* or context fields, generating an "assembly pattern." An assembly pattern is simply a masked bit
* sequence recording what is expected in the instruction buffer and context register. Each bit is
* 0, 1, or unspecified. In many cases, the "defining expression" is simply a field, so "solving"
* degenerates simply to "writing" the specified value into the field. Solving expressions is only
* required when a terminal defines the value of a temporary symbol. If the value is unspecified,
* i.e., it is a hidden operand, then no fields are written. Thus, hidden non-sub-table operands
* generate empty patterns.
*
* <p>
* As machine code generation proceeds right to left in a constructor, the resulting assembly
* patterns are accumulated. If a generated pattern conflicts with that accumulated so far, the
* pattern is pruned, likely halting generation of the current prototype. Once all operands have
* been successfully resolved, the constructor's context mutations are solved. These tend to get
* complicated since some fields may have values defined by the accumulated pattern, and some may
* not. The changes are processed in reverse order from specified in the constructor, since fields
* may be mutated in a way that forms data dependences among them. To solve, the field on the
* left-hand side of the mutation is read, then it is set equal to the right-hand size and passed to
* the solver. Because, from the disassembly perspective, the left-hand side is about to be written,
* its value is cleared before passed to the solver. If successful, the solver returns patterns that
* satisfy the equation. Resolution accumulates the patterns. If solving fails, or the patterns
* conflict, it is pruned. Finally, the patterns required to select the constructor are applied,
* again pruning conflicts. Note that a constructor may specify multiple patterns, e.g., if a
* constraint is <code>op == 5 || op == 6</code>. Thus, overall, it is possible a single prototype
* will generate multiple assembly patterns. This process is encapsulated in
* {@link AssemblyConstructState}.
*
* <h3>Handling Context and Prefixes</h3>
*
* <p>
* Once the root constructor has been completely resolved, the resulting instruction patterns
* comprise the generated instruction bytes. However, we must consider the context pattern, too. In
* practice, the assembler is invoked at a particular address, and the program database may provide
* an initial context (as marked during previous disassembly). In other words, when patching an
* instruction, we have to keep any persistent context in place. Thus, we can further cull patterns
* whose context does not match. This intuition is frustrated by the possibility of constructors
* with the mnemonic production <code>^instruction</code>, though. These "pure recursive"
* constructors are often (ab)used to handle instruction prefixes, e.g.:
*
* <code>
* :^instruction is prefixed=0 & byte=0xff; instruction [ prefixed=1; ] {}
* </code>
*
* <p>
* There are no syntactic elements that would cue the assembly parser to use this constructor.
* Instead, we rely on the context register. Were it not for these kinds of constructors, we could
* use the saved context as input to the prototype generation phase; however, we cannot. Instead, we
* use the empty context and delay this step until after machine code generation. During assembler
* construction, we pre-compute a "context transition graph." The mnemonic production
* <code>[instruction] => [instruction]</code> has associated with it all pure recursive
* constructors. Naturally, that production cannot be included in the parser, as it would generate
* increasingly deep parse trees <em>ad infinitum</em>. The graph starts with a seed node: the
* language's default context. Then each pure recursive constructor is considered as an edge,
* leading to the node resulting from applying that constructor, mimicking disassembly. This
* proceeds for each unvisited node until no new nodes are produced. This component is encapsulated
* in {@link AssemblyContextGraph}.
*
* <p>
* To generate prefixes, we seek the shortest paths from nodes whose context pattern match the
* initial context to nodes whose context pattern matches the generated assembly pattern. Note that
* the shortest path may be the zero-length path. If no paths are found, assembly fails. Machine
* code generation then proceeds by considering each path, and resolving the constructors in
* reverse, in the same manner as constructors from the prototype are resolved. Note that the
* patterns may need to be shifted to accommodate prefix tokens. This is accomplished by examining
* the shift of the nested instruction operand for each constructor. This process is implemented in
* {@link AssemblyTreeResolver#resolveRootRecursion(AssemblyResolutionResults)}.
*
* <h3>Final Steps</h3>
*
* <p>
* As a final fail safe, the generated instructions are fed back through the disassembler and the
* resulting constructor trees are compared. If not equivalent, the instruction is dropped. It is
* possible (common in fact) that the generated assembly instruction pattern is not fully defined.
* By default, the assembler will substitute 0 for each undefined bit. However, the assembler API
* allows the retrieval of the generated pattern, since a user may wish to substitute other values.
*
* <p>
* If, in the end, no instructions are generated, a semantic error is reported. Often, the
* description is unwieldy, since it comprises a list of reasons each pattern was pruned. From the
* user side, it is usually sufficient to say, "sorry." From the language developer side, it may be
* useful to manually reconstruct the prototype and discover the conflicts. To that end, the
* implementation includes optional diagnostics, but even then, decoding them takes some familiarity
* and expertise.
*/
public class SleighAssemblerBuilder implements AssemblerBuilder {
protected static final DbgTimer dbg = SystemUtilities.isInTestingBatchMode() ? DbgTimer.INACTIVE : DbgTimer.ACTIVE;
protected static final DbgTimer dbg =
SystemUtilities.isInTestingBatchMode() ? DbgTimer.INACTIVE : DbgTimer.ACTIVE;
protected SleighLanguage lang;
protected AssemblyGrammar grammar;
@@ -220,6 +429,7 @@ public class SleighAssemblerBuilder implements AssemblerBuilder {
/**
* Convert the given operand symbol to an {@link AssemblySymbol}
*
* <p>
* For subtables, this results in a non-terminal, for all others, the result in a terminal.
*
* @param cons the constructor to which the operand belongs
@@ -242,7 +452,9 @@ public class SleighAssemblerBuilder implements AssemblerBuilder {
return built;
}
if (defsym == null) {
built = new AssemblyNumericTerminal(name, getBitSize(cons, opsym));
HandleTpl htpl = getHandleTpl(cons, opsym);
built = htpl == null ? new AssemblyNumericTerminal(name, 0, null)
: new AssemblyNumericTerminal(name, htpl.getSize(), htpl.getAddressSpace());
}
else if (defsym instanceof SubtableSymbol) {
built = new AssemblyNonTerminal(name);
@@ -268,39 +480,40 @@ public class SleighAssemblerBuilder implements AssemblerBuilder {
}
/**
* Obtain the size in bits of a textual operand.
* Obtain the p-code result handle for the given operand
*
* This is a little odd, since the variables in pattern expressions do not have an explicit
* size. However, the value exported by a constructor's pCode may have an explicit size given
* (in bytes). Thus, there is a special case, where a constructor prints just one operand and
* exports that same operand with an explicit size. In that case, the size of the operand is
* printed according to that exported size.
* <p>
* This handles a special case, where a constructor prints just one operand and exports that
* same operand, often with an explicit size, or as an address in a given space. In such cases,
* the listing displays that operand according to that exported size.
*
* For disassembly, this information is used simply to truncate the bits before they are
* displayed. For assembly, we must do two things: 1) Ensure that the provided value fits in the
* given size, and 2) Mask the goal when solving the pattern expression for the operand.
* <p>
* For assembly, this gives a few opportunities: 1) We can/must ensure the specified value fits,
* by checking the size. 2) We can/must mask the goal when solving the defining pattern
* expression for the operand. 3)) We can/must check that a label's address space matches that
* represented by the operand, when used for a numeric terminal.
*
* @param cons the constructor from which the production is being derived
* @param opsym the operand symbol corresponding to the grammatical symbol, whose size we wish
* to determine.
* @return the size of the operand in bits
*/
protected int getBitSize(Constructor cons, OperandSymbol opsym) {
protected HandleTpl getHandleTpl(Constructor cons, OperandSymbol opsym) {
ConstructTpl ctpl = cons.getTempl();
if (null == ctpl) {
// No pcode, no size specification
return 0;
return null;
}
HandleTpl htpl = ctpl.getResult();
if (null == htpl) {
// If nothing is exported, the size is unspecified
return 0;
return null;
}
if (opsym.getIndex() != htpl.getOffsetOperandIndex()) {
// If the export is not of the same operand, it does not specify its size
return 0;
return null;
}
return htpl.getSize();
return htpl;
}
/**
@@ -326,30 +539,10 @@ public class SleighAssemblerBuilder implements AssemblerBuilder {
if (sym.takesOperandIndex()) {
indices.add(index);
}
rhs.add(sym);
rhs.addSymbol(sym);
}
else {
String tstr = str.trim();
if (tstr.equals("")) {
rhs.addWS();
}
else {
char first = tstr.charAt(0);
if (!str.startsWith(tstr)) {
rhs.addWS();
}
if (!Character.isLetterOrDigit(first)) {
rhs.addWS();
}
rhs.add(new AssemblyStringTerminal(str.trim()));
char last = tstr.charAt(tstr.length() - 1);
if (!str.endsWith(tstr)) {
rhs.addWS();
}
if (!Character.isLetterOrDigit(last)) {
rhs.addWS();
}
}
rhs.addSeparators(str);
}
}
}
@@ -384,7 +577,7 @@ public class SleighAssemblerBuilder implements AssemblerBuilder {
// Ignore. We don't do pcode.
}
else if (sym instanceof OperandSymbol) {
// Ignore. These are terminals, or will be produced by there defining symbol
// Ignore. These are terminals, or will be produced by their defining symbols
}
else if (sym instanceof ValueSymbol) {
// Ignore. These are now terminals
@@ -19,12 +19,12 @@ import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedConstructor;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.BinaryExpression;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
/**
* A solver that handles expressions of the form A [OP] B
* A solver that handles expressions of the form {@code A [OP] B}
*
* @param <T> the type of expression solved (the operator)
*/
@@ -37,10 +37,10 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
@Override
public AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException {
MaskedLong lval = solver.getValue(exp.getLeft(), vals, res, cur);
MaskedLong rval = solver.getValue(exp.getRight(), vals, res, cur);
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException {
MaskedLong lval = solver.getValue(exp.getLeft(), vals, cur);
MaskedLong rval = solver.getValue(exp.getRight(), vals, cur);
if (lval != null && !lval.isFullyDefined()) {
if (!lval.isFullyUndefined()) {
@@ -61,23 +61,23 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
return ConstantValueSolver.checkConstAgrees(cval, goal, description);
}
else if (lval != null) {
return solveRightSide(exp.getRight(), lval, goal, vals, res, cur, hints,
return solveRightSide(exp.getRight(), lval, goal, vals, cur, hints,
description);
}
else if (rval != null) {
return solveLeftSide(exp.getLeft(), rval, goal, vals, res, cur, hints, description);
return solveLeftSide(exp.getLeft(), rval, goal, vals, cur, hints, description);
}
else {
// Each solver may provide a strategy for solving expression where both sides are
// variable, e.g., two fields being concatenated via OR.
return solveTwoSided(exp, goal, vals, res, cur, hints, description);
return solveTwoSided(exp, goal, vals, cur, hints, description);
}
}
catch (NeedsBackfillException e) {
throw e;
}
catch (SolverException e) {
return AssemblyResolution.error(e.getMessage(), description, null);
return AssemblyResolution.error(e.getMessage(), description);
}
catch (AssertionError e) {
dbg.println("While solving: " + exp + " (" + description + ")");
@@ -86,30 +86,30 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
}
protected AssemblyResolution solveLeftSide(PatternExpression lexp, MaskedLong rval,
MaskedLong goal, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur, Set<SolverHint> hints, String description)
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
return solver.solve(lexp, computeLeft(rval, goal), vals, res, cur, hints, description);
return solver.solve(lexp, computeLeft(rval, goal), vals, cur, hints, description);
}
protected AssemblyResolution solveRightSide(PatternExpression rexp, MaskedLong lval,
MaskedLong goal, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur, Set<SolverHint> hints, String description)
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
return solver.solve(rexp, computeRight(lval, goal), vals, res, cur, hints, description);
return solver.solve(rexp, computeRight(lval, goal), vals, cur, hints, description);
}
protected AssemblyResolution solveTwoSided(T exp, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException, SolverException {
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
throw new NeedsBackfillException("_two_sided_");
}
@Override
public MaskedLong getValue(T exp, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur) throws NeedsBackfillException {
MaskedLong lval = solver.getValue(exp.getLeft(), vals, res, cur);
MaskedLong rval = solver.getValue(exp.getRight(), vals, res, cur);
public MaskedLong getValue(T exp, Map<String, Long> vals, AssemblyResolvedPatterns cur)
throws NeedsBackfillException {
MaskedLong lval = solver.getValue(exp.getLeft(), vals, cur);
MaskedLong rval = solver.getValue(exp.getRight(), vals, cur);
if (lval != null && rval != null) {
MaskedLong cval = compute(lval, rval);
return cval;
@@ -130,7 +130,9 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
/**
* Compute the right-hand-side value given that the result and the left are known
*
* NOTE: Assumes commutativity by default
* <p>
* <b>NOTE:</b> Assumes commutativity by default
*
* @param lval the left-hand-side value
* @param goal the result
* @return the right-hand-side value solution
@@ -150,16 +152,17 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
public abstract MaskedLong compute(MaskedLong lval, MaskedLong rval);
@Override
public int getInstructionLength(T exp, Map<Integer, Object> res) {
int ll = solver.getInstructionLength(exp.getLeft(), res);
int lr = solver.getInstructionLength(exp.getRight(), res);
public int getInstructionLength(T exp) {
int ll = solver.getInstructionLength(exp.getLeft());
int lr = solver.getInstructionLength(exp.getRight());
return Math.max(ll, lr);
}
@Override
public MaskedLong valueForResolution(T exp, AssemblyResolvedConstructor rc) {
MaskedLong lval = solver.valueForResolution(exp.getLeft(), rc);
MaskedLong rval = solver.valueForResolution(exp.getRight(), rc);
public MaskedLong valueForResolution(T exp, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
MaskedLong lval = solver.valueForResolution(exp.getLeft(), vals, rc);
MaskedLong rval = solver.valueForResolution(exp.getRight(), vals, rc);
return compute(lval, rval);
}
}
@@ -19,7 +19,7 @@ import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedConstructor;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
@@ -49,14 +49,13 @@ public abstract class AbstractExpressionSolver<T extends PatternExpression> {
* @param exp the expression to solve
* @param goal the desired value of the expression
* @param vals values of defined symbols
* @param res the results of subconstructor resolutions (used for lengths)
* @param hints describes techniques applied by calling solvers
* @param description the description to give to resolved solutions
* @return the resolution
* @throws NeedsBackfillException if the expression refers to an undefined symbol
*/
public abstract AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException;
/**
@@ -64,33 +63,34 @@ public abstract class AbstractExpressionSolver<T extends PatternExpression> {
*
* @param exp the expression
* @param vals values of defined symbols
* @param res the results of subconstructor resolutions (used for lengths)
* @return the constant value, or null if it depends on a variable
* @throws NeedsBackfillException if the expression refers to an undefined symbol
*/
public abstract MaskedLong getValue(T exp, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur) throws NeedsBackfillException;
public abstract MaskedLong getValue(T exp, Map<String, Long> vals,
AssemblyResolvedPatterns cur) throws NeedsBackfillException;
/**
* Determines the length of the subconstructor that would be returned had the expression not
* depended on an undefined symbol.
*
* <p>
* This is used by the backfilling process to ensure values are written to the correct offset
*
* @param exp the expression
* @param res the results of subconstructor resolutions (used for lengths)
* @return the length of filled in token field(s).
*/
public abstract int getInstructionLength(T exp, Map<Integer, Object> res);
public abstract int getInstructionLength(T exp);
/**
* Compute the value of the expression given the (possibly-intermediate) resolution
*
* @param exp the expression to evaluate
* @param vals values of defined symbols
* @param rc the resolution on which to evaluate it
* @return the result
*/
public abstract MaskedLong valueForResolution(T exp, AssemblyResolvedConstructor rc);
public abstract MaskedLong valueForResolution(T exp, Map<String, Long> vals,
AssemblyResolvedPatterns rc);
/**
* Register this particular solver with the general expression solver
@@ -19,11 +19,11 @@ import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedConstructor;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.UnaryExpression;
/**
* A solver that handles expressions of the form [OP]A
* A solver that handles expressions of the form {@code [OP]A}
*
* @param <T> the type of expression solved (the operator)
*/
@@ -36,9 +36,9 @@ public abstract class AbstractUnaryExpressionSolver<T extends UnaryExpression>
@Override
public AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException {
MaskedLong uval = solver.getValue(exp.getUnary(), vals, res, cur);
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException {
MaskedLong uval = solver.getValue(exp.getUnary(), vals, cur);
try {
if (uval != null && uval.isFullyDefined()) {
MaskedLong cval = compute(uval);
@@ -46,7 +46,7 @@ public abstract class AbstractUnaryExpressionSolver<T extends UnaryExpression>
return ConstantValueSolver.checkConstAgrees(cval, goal, description);
}
}
return solver.solve(exp.getUnary(), computeInverse(goal), vals, res, cur, hints,
return solver.solve(exp.getUnary(), computeInverse(goal), vals, cur, hints,
description);
}
/*
@@ -60,9 +60,9 @@ public abstract class AbstractUnaryExpressionSolver<T extends UnaryExpression>
}
@Override
public MaskedLong getValue(T exp, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur) throws NeedsBackfillException {
MaskedLong val = solver.getValue(exp.getUnary(), vals, res, cur);
public MaskedLong getValue(T exp, Map<String, Long> vals, AssemblyResolvedPatterns cur)
throws NeedsBackfillException {
MaskedLong val = solver.getValue(exp.getUnary(), vals, cur);
if (val != null) {
return compute(val);
}
@@ -72,7 +72,9 @@ public abstract class AbstractUnaryExpressionSolver<T extends UnaryExpression>
/**
* Compute the input value given that the result is known
*
* NOTE: Assumes an involution by default
* <p>
* <b>NOTE:</b> Assumes an involution by default
*
* @param goal the result
* @return the input value solution
*/
@@ -89,13 +91,14 @@ public abstract class AbstractUnaryExpressionSolver<T extends UnaryExpression>
public abstract MaskedLong compute(MaskedLong val);
@Override
public int getInstructionLength(T exp, Map<Integer, Object> res) {
return solver.getInstructionLength(exp.getUnary(), res);
public int getInstructionLength(T exp) {
return solver.getInstructionLength(exp.getUnary());
}
@Override
public MaskedLong valueForResolution(T exp, AssemblyResolvedConstructor rc) {
MaskedLong val = solver.valueForResolution(exp.getUnary(), rc);
public MaskedLong valueForResolution(T exp, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
MaskedLong val = solver.valueForResolution(exp.getUnary(), vals, rc);
return compute(val);
}
}
@@ -19,12 +19,13 @@ import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedConstructor;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.ConstantValue;
/**
* "Solves" constant expressions
*
* <p>
* Essentially, this either evaluates successfully when asked for a constant value, or checks that
* the goal is equal to the constant. Otherwise, there is no solution.
*/
@@ -36,25 +37,26 @@ public class ConstantValueSolver extends AbstractExpressionSolver<ConstantValue>
@Override
public AssemblyResolution solve(ConstantValue cv, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) {
MaskedLong value = getValue(cv, vals, res, cur);
MaskedLong value = getValue(cv, vals, cur);
return checkConstAgrees(value, goal, description);
}
@Override
public MaskedLong getValue(ConstantValue cv, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur) {
public MaskedLong getValue(ConstantValue cv, Map<String, Long> vals,
AssemblyResolvedPatterns cur) {
return MaskedLong.fromLong(cv.getValue());
}
@Override
public int getInstructionLength(ConstantValue cv, Map<Integer, Object> res) {
public int getInstructionLength(ConstantValue cv) {
return 0;
}
@Override
public MaskedLong valueForResolution(ConstantValue cv, AssemblyResolvedConstructor rc) {
public MaskedLong valueForResolution(ConstantValue cv, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
return MaskedLong.fromLong(cv.getValue());
}
@@ -62,9 +64,8 @@ public class ConstantValueSolver extends AbstractExpressionSolver<ConstantValue>
String description) {
if (!value.agrees(goal)) {
return AssemblyResolution.error(
"Constant value " + value + " does not agree with child requirements", description,
null);
"Constant value " + value + " does not agree with child requirements", description);
}
return AssemblyResolution.nop(description, null);
return AssemblyResolution.nop(description, null, null);
}
}
@@ -24,6 +24,7 @@ import ghidra.app.plugin.processors.sleigh.expression.ContextField;
/**
* Solves expressions of a context register field
*
* <p>
* Essentially, this just encodes the goal into the field, if it can be represented in the given
* space and format. Otherwise, there is no solution.
*/
@@ -35,33 +36,33 @@ public class ContextFieldSolver extends AbstractExpressionSolver<ContextField> {
@Override
public AssemblyResolution solve(ContextField cf, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
String description) {
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) {
assert cf.minValue() == 0; // In case someone decides to do signedness there.
if (!goal.isInRange(cf.maxValue(), cf.hasSignbit())) {
return AssemblyResolution.error("Value " + goal + " is not valid for " + cf,
description, null);
description);
}
AssemblyPatternBlock block = AssemblyPatternBlock.fromContextField(cf, goal);
return AssemblyResolution.contextOnly(block, description, null);
return AssemblyResolution.contextOnly(block, description);
}
@Override
public MaskedLong getValue(ContextField cf, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur) {
public MaskedLong getValue(ContextField cf, Map<String, Long> vals,
AssemblyResolvedPatterns cur) {
if (cur == null) {
return null;
}
return valueForResolution(cf, cur);
return valueForResolution(cf, vals, cur);
}
@Override
public int getInstructionLength(ContextField cf, Map<Integer, Object> res) {
public int getInstructionLength(ContextField cf) {
return 0; // this is a context field, not an instruction (token) field
}
@Override
public MaskedLong valueForResolution(ContextField cf, AssemblyResolvedConstructor rc) {
public MaskedLong valueForResolution(ContextField cf, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
int size = cf.getByteEnd() - cf.getByteStart() + 1;
MaskedLong res = rc.readContext(cf.getByteStart(), size);
res = res.shiftRight(cf.getShift());
@@ -24,8 +24,8 @@ public enum DefaultSolverHint implements SolverHint {
*/
GUESSING_REPETITION,
/**
* A boolean or solver which matches a circular shift is solving the value having guessed a
* shift
* A boolean {@code or} solver which matches a circular shift is solving the value having
* guessed a shift
*/
GUESSING_CIRCULAR_SHIFT_AMOUNT,
/**
@@ -18,7 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import ghidra.app.plugin.processors.sleigh.expression.DivExpression;
/**
* Solves expressions of the form A / B
* Solves expressions of the form {@code A / B}
*/
public class DivExpressionSolver extends AbstractBinaryExpressionSolver<DivExpression> {
@@ -37,7 +37,8 @@ public class DivExpressionSolver extends AbstractBinaryExpressionSolver<DivExpre
return MaskedLong.fromLong(1);
}
throw new SolverException(
"Encountered a division of the form A / x = B, where A != B. x has many solutions not easily expressed with masking.");
"Encountered a division of the form A / x = B, where A != B. x has many solutions " +
"not easily expressed with masking.");
}
@Override
@@ -24,10 +24,13 @@ import ghidra.app.plugin.processors.sleigh.expression.EndInstructionValue;
/**
* "Solves" expressions of {@code inst_next}
*
* <p>
* Works like the constant solver, but takes the value of {@code inst_next}, which is given by the
* assembly address and the resulting instruction length.
*
* NOTE: This solver requires backfill.
* <p>
* <b>NOTE:</b> This solver requires backfill, since the value of {@code inst_next} is not known
* until possible prefixes have been considered.
*/
public class EndInstructionValueSolver extends AbstractExpressionSolver<EndInstructionValue> {
@@ -37,32 +40,38 @@ public class EndInstructionValueSolver extends AbstractExpressionSolver<EndInstr
@Override
public AssemblyResolution solve(EndInstructionValue iv, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
String description) {
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) {
throw new AssertionError(
"INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_NEXT);
}
@Override
public MaskedLong getValue(EndInstructionValue iv, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur)
throws NeedsBackfillException {
AssemblyResolvedPatterns cur) throws NeedsBackfillException {
Long instNext = vals.get(AssemblyTreeResolver.INST_NEXT);
if (instNext == null) {
throw new NeedsBackfillException(AssemblyTreeResolver.INST_NEXT);
}
return MaskedLong.fromLong(vals.get(AssemblyTreeResolver.INST_NEXT));
return MaskedLong.fromLong(instNext);
}
@Override
public int getInstructionLength(EndInstructionValue iv, Map<Integer, Object> res) {
public int getInstructionLength(EndInstructionValue iv) {
return 0;
}
@Override
public MaskedLong valueForResolution(EndInstructionValue exp, AssemblyResolvedConstructor rc) {
// Would need to pass in symbol values, and perhaps consider child resolutions.
throw new UnsupportedOperationException(
"The solver should never ask for this value given a resolved constructor.");
public MaskedLong valueForResolution(EndInstructionValue exp, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
Long instNext = vals.get(AssemblyTreeResolver.INST_NEXT);
if (instNext == null) {
/**
* This method is used in forward state construction, so just leave unknown. This may
* cause unresolvable trees to get generated, but we can't know that until we try to
* resolve them.
*/
return MaskedLong.UNKS;
}
return MaskedLong.fromLong(instNext);
}
}
@@ -19,12 +19,12 @@ import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedConstructor;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.LeftShiftExpression;
import ghidra.util.Msg;
/**
* {@literal Solves expressions of the form A << B}
* Solves expressions of the form {@code A << B}
*/
public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<LeftShiftExpression> {
@@ -61,13 +61,12 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<Le
@Override
protected AssemblyResolution solveTwoSided(LeftShiftExpression exp, MaskedLong goal,
Map<String, Long> vals, Map<Integer, Object> res, AssemblyResolvedConstructor cur,
Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException, SolverException {
// Do not guess the same parameter recursively
if (hints.contains(DefaultSolverHint.GUESSING_LEFT_SHIFT_AMOUNT)) {
// NOTE: Nested left shifts ought to be written as a left shift by a sum
return super.solveTwoSided(exp, goal, vals, res, cur, hints, description);
return super.solveTwoSided(exp, goal, vals, cur, hints, description);
}
// Count the number of zeros to the right, and consider this the maximum shift value
// Any higher shift amount would produce too many zeros to the right
@@ -76,24 +75,41 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<Le
// use of the leading zero count, at least AFAIK. Maybe to better restrict the max???
Set<SolverHint> hintsWithLShift =
SolverHint.with(hints, DefaultSolverHint.GUESSING_LEFT_SHIFT_AMOUNT);
if (maxShift == 64) {
// If the goal is 0s, then any shift will do, so long as the shifted value is 0
try {
// NB. goal is already 0s, so just use it as subgoal for lhs of shift
AssemblyResolution lres =
solver.solve(exp.getLeft(), goal, vals, cur, hintsWithLShift, description);
if (lres.isError()) {
throw new SolverException("Solving left:=0 failed");
}
// If this works, then the rhs can have any value, so nothing to solve for
return lres;
}
catch (SolverException | UnsupportedOperationException e) {
Msg.trace(this, "Trying left:=0 in shift resulted in " + e);
// Fall through to the guessing method
}
}
for (int shift = maxShift; shift >= 0; shift--) {
try {
MaskedLong reqr = MaskedLong.fromLong(shift);
MaskedLong reql = computeLeft(reqr, goal);
AssemblyResolution lres =
solver.solve(exp.getLeft(), reql, vals, res, cur, hintsWithLShift, description);
solver.solve(exp.getLeft(), reql, vals, cur, hintsWithLShift, description);
if (lres.isError()) {
throw new SolverException("Solving left failed");
}
AssemblyResolution rres =
solver.solve(exp.getRight(), reqr, vals, res, cur, hints, description);
solver.solve(exp.getRight(), reqr, vals, cur, hints, description);
if (rres.isError()) {
throw new SolverException("Solving right failed");
}
AssemblyResolvedConstructor lsol = (AssemblyResolvedConstructor) lres;
AssemblyResolvedConstructor rsol = (AssemblyResolvedConstructor) rres;
AssemblyResolvedConstructor sol = lsol.combine(rsol);
AssemblyResolvedPatterns lsol = (AssemblyResolvedPatterns) lres;
AssemblyResolvedPatterns rsol = (AssemblyResolvedPatterns) rres;
AssemblyResolvedPatterns sol = lsol.combine(rsol);
if (sol == null) {
throw new SolverException(
"Left and right solutions conflict for shift=" + shift);
@@ -105,6 +121,6 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<Le
// try the next
}
}
return super.solveTwoSided(exp, goal, vals, res, cur, hints, description);
return super.solveTwoSided(exp, goal, vals, cur, hints, description);
}
}
@@ -45,6 +45,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Create a masked value from a mask and a long
*
* <p>
* Any positions in {@code msk} set to 0 create an {@code x} in the corresponding position of
* the result. Otherwise, the position takes the corresponding bit from {@code val}.
*
@@ -92,6 +93,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Get the mask as a long
*
* <p>
* Positions with a defined bit are {@code 1}; positions with an undefined bit are {@code 0}.
*
* @return the mask as a long
@@ -126,6 +128,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Apply an additional mask to this masked long
*
* <p>
* Any {@code 0} bit in {@code msk} will result in an undefined bit in the result. {@code 1}
* bits result in a copy of the corresponding bit in the result.
*
@@ -139,6 +142,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Sign extend the masked value, according to its mask, to a full long
*
* <p>
* The leftmost defined bit is taken as the sign bit, and extended to the left.
*
* @return the sign-extended masked long
@@ -151,6 +155,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Zero extend the masked value, according to its mask, to a full long
*
* <p>
* All bits to the left of the leftmost defined bit are set to 0.
*
* @return the zero-extended masked long
@@ -199,6 +204,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Combine this and another masked long into one, by taking defined bits from either
*
* <p>
* If this masked long agrees with the other, then the two are combined. For each bit position
* in the result, the defined bit from either corresponding position is taken. If neither is
* defined, then the position is undefined in the result. If both are defined, they must agree.
@@ -217,6 +223,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift {@code size} bits @{code n) positions circularly in a given direction
*
* <p>
* The shifted bits are the least significant {@code size} bits. The remaining bits are
* unaffected.
*
@@ -247,6 +254,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift {@code size} bits @{code n) positions circularly in a given direction
*
* <p>
* The shifted bits are the least significant {@code size} bits. The remaining bits are
* unaffected.
*
@@ -265,6 +273,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift the bits @{code n} positions left
*
* <p>
* This implements both a signed and unsigned shift.
*
* @param n the number of positions.
@@ -282,6 +291,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift the bits {@code n} positions left
*
* <p>
* This implements both a signed and unsigned shift.
*
* @param n the number of positions.
@@ -297,6 +307,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Invert a left shift of {@code n} positions, that is shift right
*
* <p>
* This is different from a normal shift right, in that it inserts unknowns at the left. The
* normal right shift inserts zeros or sign bits. Additionally, if any ones would fall off the
* right, the inversion is undefined.
@@ -319,6 +330,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Invert a left shift of {@code n} positions, that is shift right
*
* <p>
* This is different from a normal shift right, in that it inserts unknowns at the left. The
* normal right shift inserts zeros or sign bits. Additionally, if any ones would fall off the
* right, the inversion is undefined.
@@ -337,6 +349,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift the bits arithmetically {@code n} positions right
*
* <p>
* This implements a signed shift.
*
* @param n the number of positions.
@@ -352,6 +365,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift the bits arithmetically {@code n} positions right
*
* <p>
* This implements a signed shift.
*
* @param n the number of positions.
@@ -367,6 +381,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Invert an arithmetic right shift of {@code n} positions, that is shift left
*
* <p>
* This is different from a normal shift left, in that it inserts unknowns at the right. The
* normal left shift inserts zeros. Additionally, all bits that fall off the left must match the
* resulting sign bit, or else the inversion is undefined.
@@ -400,6 +415,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Invert an arithmetic right shift of {@code n} positions, that is shift left
*
* <p>
* This is different from a normal shift left, in that it inserts unknowns at the right. The
* normal left shift inserts zeros. Additionally, all bits that fall off the left must match the
* resulting sign bit, or else the inversion is undefined.
@@ -418,6 +434,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift the bits logically {@code n} positions right
*
* <p>
* This implements an unsigned shift.
*
* @param n the number of positions.
@@ -435,6 +452,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift the bits logically {@code n} positions right
*
* <p>
* This implements an unsigned shift.
*
* @param n the number of positions.
@@ -451,6 +469,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Shift the bits positionally {@code n} positions right
*
* <p>
* This fills the left with unknown bits
*
* @param n
@@ -463,6 +482,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Invert a logical right shift of {@code n} positions, that is shift left
*
* <p>
* This is different from a normal shift left, in that it inserts unknowns at the right. The
* normal left shift inserts zeros. Additionally, if any ones would fall off the left, the
* inversion is undefined.
@@ -486,6 +506,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Invert a logical right shift of {@code n} positions, that is shift left
*
* <p>
* This is different from a normal shift left, in that it inserts unknowns at the right. The
* normal left shift inserts zeros. Additionally, if any ones would fall off the left, the
* inversion is undefined.
@@ -504,6 +525,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Reverse the least significant {@code n} bytes
*
* <p>
* This interprets the bits as an {@code n}-byte value and changes the endianness. Any bits
* outside of the interpretation are truncated, i.e., become unknown.
*
@@ -517,16 +539,17 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Compute the bitwise AND of this and another masked long
*
* <p>
* To handle unknown bits, the result is derived from the following truth table:
*
* <pre>{@literal
* <pre>
* 0 x 1 <= A (this)
* 0 0 0 0
* x 0 x x
* 1 0 x 1
* ^
* B (that)
* }</pre>
* </pre>
*
* @param that the other masked long ({@code B}).
* @return the result.
@@ -547,18 +570,19 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Solves the expression {@code A & B = C, for B, given C and A}
*
* <p>
* To handle unknown bits, the solution is derived from the following truth table, where
* {@code *} indicates no solution:
*
* <pre>{@literal
* <pre>
* 0 x 1 <= A (that)
* 0 x x 0
* x x x x
* 1 * 1 1
* ^
* B (this)
* }</pre>
* </pre>
*
* @param that the other masked long ({@code B}).
* @return the result.
@@ -587,16 +611,17 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Compute the bitwise OR of this and another masked long
*
* <p>
* To handle unknown bits, the result is derived from the following truth table:
*
* <pre>{@literal
* <pre>
* 0 x 1 <= A (this)
* 0 0 x 1
* x x x 1
* 1 1 1 1
* ^
* B (that)
* }</pre>
* </pre>
*
* @param that the other masked long ({@code B}).
* @return the result.
@@ -620,17 +645,18 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Solves the expression A | B = C, for B, given C and A
*
* <p>
* To handle unknown bits, the solution is derived from the following truth table, where
* {@code *} indicates no solution:
*
* <pre>{@literal
* <pre>
* 0 x 1 <= A (that)
* 0 0 0 *
* x x x x
* 1 1 x x
* ^
* B (this)
* }</pre>
* </pre>
*
* @param that the other masked long ({@code B}).
* @return the result.
@@ -658,16 +684,17 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Compute the bitwise XOR of this and another masked long
*
* <p>
* To handle unknown bits, the result is derived from the following truth table:
*
* <pre>{@literal
* <pre>
* 0 x 1 <= A (this)
* 0 0 x 1
* x x x x
* 1 1 x 0
* ^
* B (that)
* }</pre>
* </pre>
*
* @param that the other masked long ({@code B}).
* @return the result.
@@ -696,12 +723,13 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Compute the bitwise NOT
*
* <p>
* To handle unknown bits, the result is derived from the following truth table:
*
* <pre>{@literal
* <pre>
* 0 x 1 <= A (this)
* 1 x 0
* }</pre>
* </pre>
*
* @return the result.
*/
@@ -769,7 +797,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
if (lmv == 2 || rmv == 2) {
return 2;
}
else if (lmv == 3 || rmv == 3) {
else if (lmv == 3 && rmv == 3) {
return 3;
}
return 0;
@@ -893,6 +921,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Compute the arithmetic quotient as a solution to unsigned multiplication
*
* <p>
* This is slightly different than {@link #divideUnsigned(MaskedLong)} in its treatment of
* unknowns.
*
@@ -924,6 +953,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Checks if this and another masked long agree
*
* <p>
* Two masked longs agree iff their corresponding defined bit positions are equal. Where either
* or both positions are undefined, no check is applied. In the case that both masked longs are
* fully-defined, this is the same as an equality check on the values.
@@ -942,6 +972,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Checks if this and a long agree
*
* <p>
* The masked long agrees with the given long iff the masked long's defined bit positions agree
* with the corresponding bit positions in the given long. Where there are undefined bits, no
* check is applied. In the case that the masked long is fully-defined, this is the same as an
@@ -978,10 +1009,12 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Check if the masked value falls within a given range
*
* <p>
* The range is defined by a maximum and a signedness. The maximum must be one less than a
* positive power of 2. In other words, it defines a maximum number of bits, including the sign
* bit if applicable.
*
* <p>
* The defined bits of this masked long are then checked to fall in the given range. The
* effective value is derived by sign/zero extending the value according to its mask. In
* general, if any {@code 1} bits exist outside of the given max, the value is rejected, unless
@@ -1013,6 +1046,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* "Compare" two masked longs
*
* <p>
* This is not meant to reflect a numerical comparison. Rather, this is just to impose an
* ordering for the sake of storing these in sorted collections.
*/
@@ -1038,6 +1072,7 @@ public class MaskedLong implements Comparable<MaskedLong> {
/**
* Check for equality
*
* <p>
* This will only return true if the other object is a masked long, even if this one is
* fully-defined, and the value is equal to a given long (or {@link Long}). The other masked
* long must have the same mask and value to be considered equal. For other sorts of "equality"
@@ -18,7 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import ghidra.app.plugin.processors.sleigh.expression.MinusExpression;
/**
* Solves expressions of the form -A
* Solves expressions of the form {@code -A}
*/
public class MinusExpressionSolver extends AbstractUnaryExpressionSolver<MinusExpression> {
@@ -19,12 +19,12 @@ import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedConstructor;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.MultExpression;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
/**
* Solves expressions of the form A * B
* Solves expressions of the form {@code A * B}
*/
public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExpression> {
@@ -103,25 +103,24 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
}
protected AssemblyResolution tryRep(PatternExpression lexp, MaskedLong rval, MaskedLong repGoal,
MaskedLong goal, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException {
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
Set<SolverHint> hints, String description) throws NeedsBackfillException {
MaskedLong lval = repGoal.divideUnsigned(rval);
if (lval.multiply(rval).agrees(goal)) {
return solver.solve(lexp, lval, vals, res, cur, hints, description);
return solver.solve(lexp, lval, vals, cur, hints, description);
}
return null;
}
@Override
protected AssemblyResolution solveLeftSide(PatternExpression lexp, MaskedLong rval,
MaskedLong goal, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur, Set<SolverHint> hints, String description)
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
// Try the usual case first
ResultTracker tracker = new ResultTracker();
AssemblyResolution sol = tracker.trySolverFunc(() -> {
return super.solveLeftSide(lexp, rval, goal, vals, res, cur, hints, description);
return super.solveLeftSide(lexp, rval, goal, vals, cur, hints, description);
});
if (sol != null) {
return sol;
@@ -151,8 +150,8 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
if (reps > 0) {
MaskedLong repRightGoal = MaskedLong.fromMaskAndValue(repMsk, repVal);
sol = tracker.trySolverFunc(() -> {
return tryRep(lexp, rval, repRightGoal, goal, vals, res, cur,
hintsWithRepetition, description);
return tryRep(lexp, rval, repRightGoal, goal, vals, cur, hintsWithRepetition,
description);
});
if (sol != null) {
return sol;
@@ -169,8 +168,8 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
repMsk = -1L >>> i;
MaskedLong repLeftGoal = MaskedLong.fromMaskAndValue(repMsk, repVal);
sol = tracker.trySolverFunc(() -> {
return tryRep(lexp, rval, repLeftGoal, goal, vals, res, cur,
hintsWithRepetition, description);
return tryRep(lexp, rval, repLeftGoal, goal, vals, cur, hintsWithRepetition,
description);
});
if (sol != null) {
return sol;
@@ -182,10 +181,10 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
@Override
protected AssemblyResolution solveRightSide(PatternExpression rexp, MaskedLong lval,
MaskedLong goal, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur, Set<SolverHint> hints, String description)
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
return solveLeftSide(rexp, lval, goal, vals, res, cur, hints, description);
return solveLeftSide(rexp, lval, goal, vals, cur, hints, description);
}
@Override
@@ -18,13 +18,15 @@ package ghidra.app.plugin.assembler.sleigh.expr;
/**
* An exception to indicate that the solution of an expression is not yet known
*
* <p>
* Furthermore, it cannot be determined whether or not the expression is even solvable. When this
* exception is thrown, a backfill record is placed on the encoded resolution indicating that
* exception is thrown, a backfill record is placed on the encoded resolution indicating that the
* resolver must attempt to solve the expression again, once the encoding is otherwise complete.
* This is needed, most notably, when an encoding depends on the address of the <em>next</em>
* instruction, because the length of the current instruction is not known until resolution has
* finished.
*
* <p>
* Backfill becomes a possibility when an expression depends on a symbol that is not (yet) defined.
* Thus, as a matter of good record keeping, the exception takes the name of the missing symbol.
*/
@@ -33,6 +35,7 @@ public class NeedsBackfillException extends SolverException {
/**
* Construct a backfill exception, resulting from the given missing symbol name
*
* @param symbol the missing symbol name
*/
public NeedsBackfillException(String symbol) {
@@ -42,6 +45,7 @@ public class NeedsBackfillException extends SolverException {
/**
* Retrieve the missing symbol name from the original solution attempt
*
* @return the missing symbol name
*/
public String getSymbol() {
@@ -18,7 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import ghidra.app.plugin.processors.sleigh.expression.NotExpression;
/**
* Solves expressions of the form ~A
* Solves expressions of the form {@code ~A}
*/
public class NotExpressionSolver extends AbstractUnaryExpressionSolver<NotExpression> {
@@ -28,6 +28,7 @@ import ghidra.app.plugin.processors.sleigh.symbol.TripleSymbol;
/**
* Solves expressions of an operand value
*
* <p>
* These are a sort of named sub-expression, but they may also specify a shift in encoding.
*/
public class OperandValueSolver extends AbstractExpressionSolver<OperandValue> {
@@ -39,12 +40,13 @@ public class OperandValueSolver extends AbstractExpressionSolver<OperandValue> {
/**
* Obtains the "defining expression"
*
* <p>
* This is either the symbols assigned defining expression, or the expression associated with
* its defining symbol.
*
* @return the defining expression, or null if neither is available
*/
protected PatternExpression getDefiningExpression(OperandSymbol sym) {
public static PatternExpression getDefiningExpression(OperandSymbol sym) {
PatternExpression patexp = sym.getDefiningExpression();
if (patexp != null) {
return patexp;
@@ -59,62 +61,63 @@ public class OperandValueSolver extends AbstractExpressionSolver<OperandValue> {
@Override
public AssemblyResolution solve(OperandValue ov, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException {
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException {
Constructor cons = ov.getConstructor();
OperandSymbol sym = cons.getOperand(ov.getIndex());
PatternExpression patexp = getDefiningExpression(sym);
if (patexp == null) {
if (goal.equals(MaskedLong.ZERO)) {
return AssemblyResolution.nop(description, null);
return AssemblyResolution.nop(description, null, null);
}
return AssemblyResolution.error("Operand " + sym.getName() +
" is undefined and does not agree with child requirements", description, null);
" is undefined and does not agree with child requirements", description);
}
AssemblyResolution result = solver.solve(patexp, goal, vals, res, cur, hints, description);
AssemblyResolution result = solver.solve(patexp, goal, vals, cur, hints, description);
if (result.isError()) {
AssemblyResolvedError err = (AssemblyResolvedError) result;
return AssemblyResolution.error(err.getError(),
"Solution to " + sym.getName() + " := " + goal + " = " + patexp,
List.of(result));
List.of(result), null);
}
// TODO: Shifting here seems like a hack to me.
// I assume this only comes at the top of an expression
AssemblyResolvedConstructor con = (AssemblyResolvedConstructor) result;
int shamt = AssemblyTreeResolver.computeOffset(sym, cons, res);
AssemblyResolvedPatterns con = (AssemblyResolvedPatterns) result;
int shamt = AssemblyTreeResolver.computeOffset(sym, cons);
return con.shift(shamt);
}
@Override
public MaskedLong getValue(OperandValue ov, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur) throws NeedsBackfillException {
public MaskedLong getValue(OperandValue ov, Map<String, Long> vals,
AssemblyResolvedPatterns cur) throws NeedsBackfillException {
Constructor cons = ov.getConstructor();
OperandSymbol sym = cons.getOperand(ov.getIndex());
PatternExpression patexp = getDefiningExpression(sym);
if (patexp == null) {
return MaskedLong.ZERO;
}
int shamt = AssemblyTreeResolver.computeOffset(sym, cons, res);
int shamt = AssemblyTreeResolver.computeOffset(sym, cons);
cur = cur == null ? null : cur.truncate(shamt);
MaskedLong result = solver.getValue(patexp, vals, res, cur);
MaskedLong result = solver.getValue(patexp, vals, cur);
return result;
}
@Override
public int getInstructionLength(OperandValue ov, Map<Integer, Object> res) {
public int getInstructionLength(OperandValue ov) {
Constructor cons = ov.getConstructor();
OperandSymbol sym = cons.getOperand(ov.getIndex());
PatternExpression patexp = sym.getDefiningExpression();
if (patexp == null) {
return 0;
}
int length = solver.getInstructionLength(patexp, res);
int shamt = AssemblyTreeResolver.computeOffset(sym, cons, res);
int length = solver.getInstructionLength(patexp);
int shamt = AssemblyTreeResolver.computeOffset(sym, cons);
return length + shamt;
}
@Override
public MaskedLong valueForResolution(OperandValue ov, AssemblyResolvedConstructor rc) {
public MaskedLong valueForResolution(OperandValue ov, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
Constructor cons = ov.getConstructor();
OperandSymbol sym = cons.getOperand(ov.getIndex());
PatternExpression patexp = sym.getDefiningExpression();
@@ -135,7 +138,7 @@ public class OperandValueSolver extends AbstractExpressionSolver<OperandValue> {
// Since I'm using this just for context, ignore shifting for now.
//int shamt = AssemblyTreeResolver.computeOffset(sym, cons, rc.children);
// Children would be null here, anyway.
return solver.valueForResolution(patexp, rc);
return solver.valueForResolution(patexp, vals, rc);
// NOTE: To be paranoid, I could check for the existence of TokenField in the expression
// And also check if a shift would be performed.
}
@@ -17,35 +17,27 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.*;
import ghidra.app.plugin.assembler.sleigh.expr.match.ExpressionMatcher;
import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx;
import ghidra.app.plugin.processors.sleigh.ParserWalker;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.processors.sleigh.expression.*;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.Msg;
import ghidra.xml.XmlPullParser;
/**
* Solves expressions of the form A | B
* Solves expressions of the form {@code A | B}
*/
public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpression> {
static final PatternExpression DUMMY = new PatternExpression() {
@Override
public long getValue(ParserWalker walker) throws MemoryAccessException {
return 0;
}
protected static class Matchers implements ExpressionMatcher.Context {
protected ExpressionMatcher<ConstantValue> val = var(ConstantValue.class);
protected ExpressionMatcher<ConstantValue> size = var(ConstantValue.class);
protected ExpressionMatcher<PatternValue> fld = fldSz(size);
@Override
public void restoreXml(XmlPullParser parser, SleighLanguage lang) {
// Dummy intentionally left empty
}
protected ExpressionMatcher<?> neqConst = or(
and(shr(sub(opnd(fld), val), size), cv(1)),
and(shr(sub(val, opnd(fld)), size), cv(1)));
}
@Override
public String toString() {
return null;
}
};
protected static final Matchers MATCHERS = new Matchers();
public OrExpressionSolver() {
super(OrExpression.class);
@@ -62,8 +54,8 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
}
protected AssemblyResolution tryCatenationExpression(OrExpression exp, MaskedLong goal,
Map<String, Long> vals, Map<Integer, Object> res, AssemblyResolvedConstructor cur,
Set<SolverHint> hints, String description) throws SolverException {
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws SolverException {
/*
* If OR is being used to concatenate fields, then we can solve with some symbolic
* manipulation. We'll descend to see if this is a tree of ORs with SHIFTs or fields at the
@@ -71,12 +63,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
* component independently.
*/
Map<Long, PatternExpression> fields = new TreeMap<>();
fields.put(0L, new ConstantValue(0));
collectComponentsOr(exp, 0, fields, vals, res, cur);
collectComponentsOr(exp, 0, fields, vals, cur);
fields.computeIfAbsent(0L, __ -> new ConstantValue(0));
fields.put(64L, new ConstantValue(0));
long lo = 0;
PatternExpression fieldExp = null;
AssemblyResolvedConstructor result = AssemblyResolution.nop(description, null);
AssemblyResolvedPatterns result = AssemblyResolution.nop(description);
try (DbgCtx dc = dbg.start("Trying solution of field catenation")) {
dbg.println("Original: " + goal + ":= " + exp);
for (Map.Entry<Long, PatternExpression> ent : fields.entrySet()) {
@@ -89,12 +81,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
dbg.println("Part(" + hi + ":" + lo + "]:= " + fieldExp);
MaskedLong part = goal.shiftLeft(64 - hi).shiftRightPositional(64 - hi + lo);
dbg.println("Solving: " + part + ":= " + fieldExp);
AssemblyResolution sol = solver.solve(fieldExp, part, vals, res, cur, hints,
AssemblyResolution sol = solver.solve(fieldExp, part, vals, cur, hints,
description + " with shift " + lo);
if (sol.isError()) {
return sol;
}
result = result.combine((AssemblyResolvedConstructor) sol);
result = result.combine((AssemblyResolvedPatterns) sol);
if (result == null) {
throw new SolverException("Solutions to individual fields produced conflict");
}
@@ -107,8 +99,8 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
}
protected AssemblyResolution tryCircularShiftExpression(OrExpression exp, MaskedLong goal,
Map<String, Long> vals, Map<Integer, Object> res, AssemblyResolvedConstructor cur,
Set<SolverHint> hints, String description) throws SolverException {
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws SolverException {
// If OR is being used to accomplish a circular shift, then we can apply a clever solver.
// We'll match against the patterns: (f << (C - g)) | (f >> g)
// (f >> (C - g)) | (f << g)
@@ -144,7 +136,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
expShift = sub.getRight();
if (expShift.equals(s2)) {
PatternExpression c = sub.getLeft();
MaskedLong cc = solver.getValue(c, vals, res, cur);
MaskedLong cc = solver.getValue(c, vals, cur);
if (cc.isFullyDefined()) {
// the left side has the subtraction, so the overall shift is the opposite
// of the direction of the shift on the left
@@ -158,7 +150,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
expShift = sub.getRight();
if (expShift.equals(s1)) {
PatternExpression c = sub.getLeft();
MaskedLong cc = solver.getValue(c, vals, res, cur);
MaskedLong cc = solver.getValue(c, vals, cur);
if (cc.isFullyDefined()) {
// the right side has the subtraction, so the overall shift is the same
// as the direction of the shift on the left
@@ -174,16 +166,16 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
// At this point, I know it's a circular shift
dbg.println("Identified circular shift: value:= " + expValu1 + ", shift:= " + expShift +
", size:= " + size + ", dir:= " + (dir == 1 ? "right" : "left"));
return solveLeftCircularShift(expValu1, expShift, size, dir, goal, vals, res, cur, hints,
return solveLeftCircularShift(expValu1, expShift, size, dir, goal, vals, cur, hints,
description);
}
protected AssemblyResolution solveLeftCircularShift(PatternExpression expValue,
PatternExpression expShift, int size, int dir, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException, SolverException {
MaskedLong valValue = solver.getValue(expValue, vals, res, cur);
MaskedLong valShift = solver.getValue(expShift, vals, res, cur);
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
MaskedLong valValue = solver.getValue(expValue, vals, cur);
MaskedLong valShift = solver.getValue(expShift, vals, cur);
if (valValue != null && !valValue.isFullyDefined()) {
if (!valValue.isFullyUndefined()) {
@@ -202,12 +194,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
throw new AssertionError("Should not have constants when solving special forms");
}
else if (valValue != null) {
return solver.solve(expShift, computeCircShiftG(valValue, size, dir, goal), vals, res,
cur, hints, description);
return solver.solve(expShift, computeCircShiftG(valValue, size, dir, goal), vals, cur,
hints, description);
}
else if (valShift != null) {
return solver.solve(expValue, computeCircShiftF(valShift, size, dir, goal), vals, res,
cur, hints, description);
return solver.solve(expValue, computeCircShiftF(valShift, size, dir, goal), vals, cur,
hints, description);
}
// Oiy. Try guessing the shift amount, starting at 0
@@ -221,21 +213,21 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
try {
MaskedLong reqShift = MaskedLong.fromLong(shift);
MaskedLong reqValue = computeCircShiftF(reqShift, size, dir, goal);
AssemblyResolution resValue = solver.solve(expValue, reqValue, vals, res, cur,
AssemblyResolution resValue = solver.solve(expValue, reqValue, vals, cur,
hintsWithCircularShift, description);
if (resValue.isError()) {
AssemblyResolvedError err = (AssemblyResolvedError) resValue;
throw new SolverException("Solving f failed: " + err.getError());
}
AssemblyResolution resShift =
solver.solve(expShift, reqShift, vals, res, cur, hints, description);
solver.solve(expShift, reqShift, vals, cur, hints, description);
if (resShift.isError()) {
AssemblyResolvedError err = (AssemblyResolvedError) resShift;
throw new SolverException("Solving g failed: " + err.getError());
}
AssemblyResolvedConstructor solValue = (AssemblyResolvedConstructor) resValue;
AssemblyResolvedConstructor solShift = (AssemblyResolvedConstructor) resShift;
AssemblyResolvedConstructor sol = solValue.combine(solShift);
AssemblyResolvedPatterns solValue = (AssemblyResolvedPatterns) resValue;
AssemblyResolvedPatterns solShift = (AssemblyResolvedPatterns) resShift;
AssemblyResolvedPatterns sol = solValue.combine(solShift);
if (sol == null) {
throw new SolverException(
"value and shift solutions conflict for shift=" + shift);
@@ -276,11 +268,10 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
@Override
protected AssemblyResolution solveTwoSided(OrExpression exp, MaskedLong goal,
Map<String, Long> vals, Map<Integer, Object> res, AssemblyResolvedConstructor cur,
Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException, SolverException {
try {
return tryCatenationExpression(exp, goal, vals, res, cur, hints, description);
return tryCatenationExpression(exp, goal, vals, cur, hints, description);
}
catch (Exception e) {
dbg.println("while solving: " + goal + "=:" + exp);
@@ -288,46 +279,73 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
}
try {
return tryCircularShiftExpression(exp, goal, vals, res, cur, hints, description);
return tryCircularShiftExpression(exp, goal, vals, cur, hints, description);
}
catch (Exception e) {
dbg.println("while solving: " + goal + "=:" + exp);
dbg.println(e.getMessage());
}
Map<ExpressionMatcher<?>, PatternExpression> match = MATCHERS.neqConst.match(exp);
if (match != null) {
long value = MATCHERS.val.get(match).getValue();
PatternValue field = MATCHERS.fld.get(match);
// Solve for equals, then either return that, or forbid it, depending on goal
AssemblyResolution solution =
solver.solve(field, MaskedLong.fromLong(value), vals, cur, hints, description);
if (goal.equals(MaskedLong.fromMaskAndValue(0, 1))) {
return solution;
}
if (goal.equals(MaskedLong.fromMaskAndValue(1, 1))) {
if (solution.isError()) {
return AssemblyResolution.nop(description);
}
if (solution.isBackfill()) {
throw new AssertionError();
}
AssemblyResolvedPatterns forbidden = (AssemblyResolvedPatterns) solution;
forbidden = forbidden.withDescription("Solved 'not equals'");
return AssemblyResolution.nop(description).withForbids(Set.of(forbidden));
}
}
throw new SolverException("Could not solve two-sided OR");
}
void collectComponents(PatternExpression exp, long shift,
Map<Long, PatternExpression> components, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur) throws SolverException {
AssemblyResolvedPatterns cur) throws SolverException {
if (exp instanceof OrExpression) {
collectComponentsOr((OrExpression) exp, shift, components, vals, res, cur);
collectComponentsOr((OrExpression) exp, shift, components, vals, cur);
}
else if (exp instanceof LeftShiftExpression) {
collectComponentsLeft((LeftShiftExpression) exp, shift, components, vals, res, cur);
collectComponentsLeft((LeftShiftExpression) exp, shift, components, vals, cur);
}
else if (exp instanceof RightShiftExpression) {
collectComponentsRight((RightShiftExpression) exp, shift, components, vals, res, cur);
collectComponentsRight((RightShiftExpression) exp, shift, components, vals, cur);
}
else {
assert shift < 64;
components.put(shift, exp);
PatternExpression conflict = components.put(shift, exp);
if (conflict != null) {
throw new SolverException("Two 'fields' at the same shift indicates conflict");
}
}
}
void collectComponentsOr(OrExpression exp, long shift, Map<Long, PatternExpression> components,
Map<String, Long> vals, Map<Integer, Object> res, AssemblyResolvedConstructor cur)
Map<String, Long> vals, AssemblyResolvedPatterns cur)
throws SolverException {
collectComponents(exp.getLeft(), shift, components, vals, res, cur);
collectComponents(exp.getRight(), shift, components, vals, res, cur);
collectComponents(exp.getLeft(), shift, components, vals, cur);
collectComponents(exp.getRight(), shift, components, vals, cur);
}
void collectComponentsLeft(LeftShiftExpression exp, long shift,
Map<Long, PatternExpression> components, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur) throws SolverException {
AssemblyResolvedPatterns cur) throws SolverException {
MaskedLong adj;
try {
adj = solver.getValue(exp.getRight(), vals, res, cur);
adj = solver.getValue(exp.getRight(), vals, cur);
}
catch (NeedsBackfillException e) {
throw new SolverException("Variable shifts break field catenation solver", e);
@@ -335,15 +353,15 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
if (adj == null || !adj.isFullyDefined()) {
throw new SolverException("Variable shifts break field catenation solver");
}
collectComponents(exp.getLeft(), shift + adj.val, components, vals, res, cur);
collectComponents(exp.getLeft(), shift + adj.val, components, vals, cur);
}
void collectComponentsRight(RightShiftExpression exp, long shift,
Map<Long, PatternExpression> components, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur) throws SolverException {
AssemblyResolvedPatterns cur) throws SolverException {
MaskedLong adj;
try {
adj = solver.getValue(exp.getRight(), vals, res, cur);
adj = solver.getValue(exp.getRight(), vals, cur);
}
catch (NeedsBackfillException e) {
throw new SolverException("Variable shifts break field catenation solver", e);
@@ -351,6 +369,6 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
if (adj == null || !adj.isFullyDefined()) {
throw new SolverException("Variable shifts break field catenation solver");
}
collectComponents(exp.getLeft(), shift - adj.val, components, vals, res, cur);
collectComponents(exp.getLeft(), shift - adj.val, components, vals, cur);
}
}
@@ -18,7 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import ghidra.app.plugin.processors.sleigh.expression.PlusExpression;
/**
* Solves expressions of the form A + B
* Solves expressions of the form {@code A + B}
*/
public class PlusExpressionSolver extends AbstractBinaryExpressionSolver<PlusExpression> {
@@ -18,24 +18,30 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedConstructor;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
/**
* This singleton class seeks solutions to {@link PatternExpression}s
*
* It is called naive, because it does not perform algebraic transformations. Rather, it attempts to
* fold constants, assuming there is a single variable in the expression, modifying the goal as it
* <p>
* It is rather naive. It does not perform algebraic transformations. Instead, it attempts to fold
* constants, assuming there is a single variable in the expression, modifying the goal as it
* descends toward that variable. If it finds a variable, i.e., token or context field, it encodes
* the solution, positioned in the field. If the expression is constant, it checks that the goal
* agrees. If not, an error is returned.
* agrees. If not, an error is returned. There are some common cases where it is forced to solve
* expressions involving multiple variables. Those cases are addressed in the derivatives of
* {@link AbstractBinaryExpressionSolver} where the situation can be detected. One common example is
* field concatenation using the {@code (A << 4) | B} pattern.
*
* TODO This whole mechanism ought to just be factored directly into {@link PatternExpression}.
* <p>
* TODO: Perhaps this whole mechanism ought to just be factored directly into
* {@link PatternExpression}.
*/
public class RecursiveDescentSolver {
protected static final DbgTimer dbg = DbgTimer.INACTIVE;
private static final RecursiveDescentSolver solver = new RecursiveDescentSolver();
protected static final DbgTimer DBG = DbgTimer.INACTIVE;
private static final RecursiveDescentSolver INSTANCE = new RecursiveDescentSolver();
// A mapping from each subclass of PatternExpression to the appropriate solver
protected Map<Class<?>, AbstractExpressionSolver<?>> registry = new HashMap<>();
@@ -67,7 +73,7 @@ public class RecursiveDescentSolver {
* @return the singleton instance
*/
public static RecursiveDescentSolver getSolver() {
return solver;
return INSTANCE;
}
/**
@@ -103,59 +109,52 @@ public class RecursiveDescentSolver {
* @param exp the expression to solve
* @param goal the desired output (modulo a mask) of the expression
* @param vals any defined symbols (usually {@code inst_start}, and {@code inst_next})
* @param res resolved subconstructors, by operand index (see method details)
* @param hints describes techniques applied by calling solvers
* @param description a description to attached to the encoded solution
* @return the encoded solution
* @throws NeedsBackfillException a solution may exist, but a required symbol is missing
*/
protected AssemblyResolution solve(PatternExpression exp, MaskedLong goal,
Map<String, Long> vals, Map<Integer, Object> res, AssemblyResolvedConstructor cur,
Set<SolverHint> hints, String description) throws NeedsBackfillException {
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException {
try {
return getRegistered(exp.getClass()).solve(exp, goal, vals, res, cur, hints,
description);
return getRegistered(exp.getClass()).solve(exp, goal, vals, cur, hints, description);
}
catch (UnsupportedOperationException e) {
dbg.println("Error solving " + exp + " = " + goal);
DBG.println("Error solving " + exp + " = " + goal);
throw e;
}
}
/**
* Solve a given expression, assuming it outputs a given masked value
* Solve a given expression, given a masked-value goal
*
* <p>
* From a simplified perspective, we need only the expression and the desired value to solve it.
* Generally speaking, the expression may have only contain a single variable, and the encoded
* result represents that single variable. It must be absorbed into the overall instruction
* and/or context encoding.
* Generally speaking, the expression may only contain a single field, and the encoded result
* specifies the bits of the solved field. It must be absorbed into the overall assembly
* pattern.
*
* More realistically, however, these expressions may depend on quite a bit of extra
* information. For example, PC-relative encodings (i.e., those involving {@code inst_start} or
* <p>
* More realistically, these expressions may depend on quite a bit of extra information. For
* example, PC-relative encodings (i.e., those involving {@code inst_start} or
* {@code inst_next}, need to know the starting address of the resulting instruction. {@code
* inst_start} must be provided to the solver by the assembler. {@code inst_next} cannot be
* known until the instruction length is known. Thus, expressions using it always result in a
* {@link NeedsBackfillException}. The symbols, when known, are provided to the solver via the
* {@code vals} parameter.
*
* Expressions involving {@link OperandValueSolver}s are a little more complicated, because they
* specify an offset that affects its encoding in the instruction. To compute this offset, the
* lengths of other surrounding operands must be known. Thus, when solving a context change for
* a given constructor, its resolved subconstructors must be provided to the solver via the
* {@code res} parameter.
*
* @param exp the expression to solve
* @param goal the desired output (modulo a mask) of the expression
* @param vals any defined symbols (usually {@code inst_start}, and {@code inst_next})
* @param res resolved subconstructors, by operand index (see method details)
* @param description a description to attached to the encoded solution
* @return the encoded solution
* @throws NeedsBackfillException a solution may exist, but a required symbol is missing
*/
public AssemblyResolution solve(PatternExpression exp, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, String description)
AssemblyResolvedPatterns cur, String description)
throws NeedsBackfillException {
return solve(exp, goal, vals, res, cur, Set.of(), description);
return solve(exp, goal, vals, cur, Set.of(), description);
}
/**
@@ -163,45 +162,44 @@ public class RecursiveDescentSolver {
*
* @param exp the (sub-)expression to fold
* @param vals any defined symbols (usually {@code inst_start}, and {@code inst_next})
* @param res resolved subconstructors, by operand index (see
* {@link #solve(PatternExpression, MaskedLong, Map, Map, AssemblyResolvedConstructor, String)})
* @return the masked solution
* @throws NeedsBackfillException it may be folded, but a required symbol is missing
*/
protected <T extends PatternExpression> MaskedLong getValue(T exp, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur)
throws NeedsBackfillException {
MaskedLong value = getRegistered(exp.getClass()).getValue(exp, vals, res, cur);
dbg.println("Expression: " + value + " =: " + exp);
AssemblyResolvedPatterns cur) throws NeedsBackfillException {
MaskedLong value = getRegistered(exp.getClass()).getValue(exp, vals, cur);
DBG.println("Expression: " + value + " =: " + exp);
return value;
}
/**
* Determine the length of the instruction part of the encoded solution to the given expression
*
* <p>
* This is used to keep operands in their appropriate position when backfilling becomes
* applicable. Normally, the instruction length is taken from the encoding of a solution, but if
* the solution cannot be determined yet, the instruction length must still be obtained.
*
* <p>
* The length can be determined by finding token fields in the expression.
*
* @param exp the expression, presumably containing a token field
* @param res resolved subconstructors, by operand index (see
* {@link #solve(PatternExpression, MaskedLong, Map, Map, AssemblyResolvedConstructor, String)})
* @return the anticipated length, in bytes, of the instruction encoding
*/
public int getInstructionLength(PatternExpression exp, Map<Integer, Object> res) {
return getRegistered(exp.getClass()).getInstructionLength(exp, res);
public int getInstructionLength(PatternExpression exp) {
return getRegistered(exp.getClass()).getInstructionLength(exp);
}
/**
* Compute the value of an expression given a (possibly-intermediate) resolution
*
* @param exp the expression to evaluate
* @param rc the resolution on which to evalute it
* @param vals values of defined symbols
* @param rc the resolution on which to evaluate it
* @return the result
*/
public MaskedLong valueForResolution(PatternExpression exp, AssemblyResolvedConstructor rc) {
return getRegistered(exp.getClass()).valueForResolution(exp, rc);
public MaskedLong valueForResolution(PatternExpression exp, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
return getRegistered(exp.getClass()).valueForResolution(exp, vals, rc);
}
}
@@ -19,12 +19,12 @@ import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedConstructor;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.RightShiftExpression;
import ghidra.util.Msg;
/**
* {@literal Solves expressions of the form A >> B}
* Solves expressions of the form {@code A >> B}
*/
public class RightShiftExpressionSolver
extends AbstractBinaryExpressionSolver<RightShiftExpression> {
@@ -62,15 +62,14 @@ public class RightShiftExpressionSolver
@Override
protected AssemblyResolution solveTwoSided(RightShiftExpression exp, MaskedLong goal,
Map<String, Long> vals, Map<Integer, Object> res, AssemblyResolvedConstructor cur,
Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException, SolverException {
// Do the similar thing as in {@link LeftShiftExpressionSolver}
// Do not guess the same parameter recursively
if (hints.contains(DefaultSolverHint.GUESSING_RIGHT_SHIFT_AMOUNT)) {
// NOTE: Nested right shifts ought to be written as a right shift by a sum
return super.solveTwoSided(exp, goal, vals, res, cur, hints, description);
return super.solveTwoSided(exp, goal, vals, cur, hints, description);
}
int maxShift = Long.numberOfLeadingZeros(goal.val);
@@ -82,18 +81,18 @@ public class RightShiftExpressionSolver
MaskedLong reql = computeLeft(reqr, goal);
AssemblyResolution lres =
solver.solve(exp.getLeft(), reql, vals, res, cur, hintsWithRShift, description);
solver.solve(exp.getLeft(), reql, vals, cur, hintsWithRShift, description);
if (lres.isError()) {
throw new SolverException("Solving left failed");
}
AssemblyResolution rres =
solver.solve(exp.getRight(), reqr, vals, res, cur, hints, description);
solver.solve(exp.getRight(), reqr, vals, cur, hints, description);
if (rres.isError()) {
throw new SolverException("Solving right failed");
}
AssemblyResolvedConstructor lsol = (AssemblyResolvedConstructor) lres;
AssemblyResolvedConstructor rsol = (AssemblyResolvedConstructor) rres;
AssemblyResolvedConstructor sol = lsol.combine(rsol);
AssemblyResolvedPatterns lsol = (AssemblyResolvedPatterns) lres;
AssemblyResolvedPatterns rsol = (AssemblyResolvedPatterns) rres;
AssemblyResolvedPatterns sol = lsol.combine(rsol);
if (sol == null) {
throw new SolverException(
"Left and right solutions conflict for shift=" + shift);
@@ -105,6 +104,6 @@ public class RightShiftExpressionSolver
// try the next
}
}
return super.solveTwoSided(exp, goal, vals, res, cur, hints, description);
return super.solveTwoSided(exp, goal, vals, cur, hints, description);
}
}
@@ -20,11 +20,13 @@ import java.util.*;
/**
* A type for solver hints
*
* Hints inform "sub-"solvers of the techniques already being applied by the calling solvers. This
* <p>
* Hints inform sub-solvers of the techniques already being applied by the calling solvers. This
* helps prevent situations where, e.g., two multiplication solvers (applied to repeated or nested
* multiplication) both attempt to synthesize new goals for repetition. This sort of expression is
* common when decoding immediates in the AArch64 specification.
*
* <p>
* Using an interface implemented by an enumeration (instead of just using the enumeration directly)
* eases expansion by extension without modifying the core code.
*
@@ -24,6 +24,7 @@ import ghidra.app.plugin.processors.sleigh.expression.StartInstructionValue;
/**
* "Solves" expression of {@code inst_start}
*
* <p>
* Works like the constant solver, but takes the value of {@code inst_start}, which is given by the
* assembly address.
*/
@@ -35,28 +36,26 @@ public class StartInstructionValueSolver extends AbstractExpressionSolver<StartI
@Override
public AssemblyResolution solve(StartInstructionValue iv, MaskedLong goal,
Map<String, Long> vals, Map<Integer, Object> res, AssemblyResolvedConstructor cur,
Set<SolverHint> hints, String description) {
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) {
throw new AssertionError(
"INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_START);
}
@Override
public MaskedLong getValue(StartInstructionValue iv, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur) {
AssemblyResolvedPatterns cur) {
return MaskedLong.fromLong(vals.get(AssemblyTreeResolver.INST_START));
}
@Override
public int getInstructionLength(StartInstructionValue exp, Map<Integer, Object> res) {
public int getInstructionLength(StartInstructionValue exp) {
return 0;
}
@Override
public MaskedLong valueForResolution(StartInstructionValue exp,
AssemblyResolvedConstructor rc) {
// Would need to pass in symbol values.
throw new UnsupportedOperationException(
"The solver should never ask for this value given a resolved constructor.");
public MaskedLong valueForResolution(StartInstructionValue exp, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
return MaskedLong.fromLong(vals.get(AssemblyTreeResolver.INST_START));
}
}
@@ -18,7 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import ghidra.app.plugin.processors.sleigh.expression.SubExpression;
/**
* Solves expressions of the form A - B
* Solves expressions of the form {@code A - B}
*/
public class SubExpressionSolver extends AbstractBinaryExpressionSolver<SubExpression> {
@@ -24,6 +24,7 @@ import ghidra.app.plugin.processors.sleigh.expression.TokenField;
/**
* Solves expressions of a token (instruction encoding) field
*
* <p>
* Essentially, this just encodes the goal into the field, if it can be represented in the given
* space and format. Otherwise, there is no solution.
*/
@@ -35,33 +36,33 @@ public class TokenFieldSolver extends AbstractExpressionSolver<TokenField> {
@Override
public AssemblyResolution solve(TokenField tf, MaskedLong goal, Map<String, Long> vals,
Map<Integer, Object> res, AssemblyResolvedConstructor cur, Set<SolverHint> hints,
String description) {
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) {
assert tf.minValue() == 0; // In case someone decides to do signedness there.
if (!goal.isInRange(tf.maxValue(), tf.hasSignbit())) {
return AssemblyResolution.error("Value " + goal + " is not valid for " + tf,
description, null);
description);
}
AssemblyPatternBlock block = AssemblyPatternBlock.fromTokenField(tf, goal);
return AssemblyResolution.instrOnly(block, description, null);
return AssemblyResolution.instrOnly(block, description);
}
@Override
public MaskedLong getValue(TokenField tf, Map<String, Long> vals, Map<Integer, Object> res,
AssemblyResolvedConstructor cur) {
public MaskedLong getValue(TokenField tf, Map<String, Long> vals,
AssemblyResolvedPatterns cur) {
if (cur == null) {
return null;
}
return valueForResolution(tf, cur);
return valueForResolution(tf, vals, cur);
}
@Override
public int getInstructionLength(TokenField tf, Map<Integer, Object> res) {
public int getInstructionLength(TokenField tf) {
return tf.getByteEnd() + 1;
}
@Override
public MaskedLong valueForResolution(TokenField tf, AssemblyResolvedConstructor rc) {
public MaskedLong valueForResolution(TokenField tf, Map<String, Long> vals,
AssemblyResolvedPatterns rc) {
int size = tf.getByteEnd() - tf.getByteStart() + 1;
MaskedLong res = rc.readInstruction(tf.getByteStart(), size);
if (!tf.isBigEndian()) {
@@ -18,7 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import ghidra.app.plugin.processors.sleigh.expression.XorExpression;
/**
* Solves expressions of the form A $xor B
* Solves expressions of the form {@code A $xor B}
*/
public class XorExpressionSolver extends AbstractBinaryExpressionSolver<XorExpression> {
@@ -0,0 +1,122 @@
/* ###
* 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.app.plugin.assembler.sleigh.expr.match;
import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.processors.sleigh.expression.*;
/**
* Base implementation for expression matchers
*
* @param <T> the type of expression matched
*/
public abstract class AbstractExpressionMatcher<T extends PatternExpression>
implements ExpressionMatcher<T> {
protected final Set<Class<? extends T>> ops;
public AbstractExpressionMatcher(Set<Class<? extends T>> ops) {
this.ops = Set.copyOf(ops);
}
public AbstractExpressionMatcher(Class<? extends T> cls) {
this.ops = Set.of(cls);
}
protected T opMatches(PatternExpression expression) {
return ops.stream()
.filter(op -> op.isInstance(expression))
.map(op -> op.cast(expression))
.findAny()
.orElse(null);
}
protected abstract boolean matchDetails(T expression,
Map<ExpressionMatcher<?>, PatternExpression> result);
@Override
public boolean match(PatternExpression expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
T t = opMatches(expression);
if (t == null) {
return false;
}
if (!matchDetails(t, result)) {
return false;
}
return recordResult(t, result);
}
protected boolean recordResult(PatternExpression expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
PatternExpression already = result.put(this, expression);
if (already == null) {
return true;
}
return expressionsIdenticallyDefined(already, expression);
}
protected static boolean expressionsIdenticallyDefined(PatternExpression a,
PatternExpression b) {
if (a.getClass() != b.getClass()) {
return false;
}
if (a instanceof EndInstructionValue) {
return true;
}
if (a instanceof StartInstructionValue) {
return true;
}
if (a instanceof ConstantValue) {
ConstantValue ca = (ConstantValue) a;
ConstantValue cb = (ConstantValue) b;
return ca.getValue() == cb.getValue();
}
if (a instanceof UnaryExpression) {
UnaryExpression ua = (UnaryExpression) a;
UnaryExpression ub = (UnaryExpression) b;
return expressionsIdenticallyDefined(ua.getUnary(), ub.getUnary());
}
if (a instanceof BinaryExpression) {
BinaryExpression ba = (BinaryExpression) a;
BinaryExpression bb = (BinaryExpression) b;
return expressionsIdenticallyDefined(ba.getLeft(), bb.getLeft()) &&
expressionsIdenticallyDefined(ba.getRight(), bb.getRight());
}
if (a instanceof TokenField) {
TokenField ta = (TokenField) a;
TokenField tb = (TokenField) b;
return ta.getBitStart() == tb.getBitStart() &&
ta.getBitEnd() == tb.getBitEnd() &&
ta.hasSignbit() == tb.hasSignbit();
}
if (a instanceof ContextField) {
ContextField ca = (ContextField) a;
ContextField cb = (ContextField) b;
return ca.getStartBit() == cb.getStartBit() &&
ca.getEndBit() == cb.getEndBit() &&
ca.hasSignbit() == cb.hasSignbit();
}
if (a instanceof OperandValue) {
OperandValue va = (OperandValue) a;
OperandValue vb = (OperandValue) b;
return va.getConstructor() == vb.getConstructor() &&
va.getIndex() == vb.getIndex();
}
throw new AssertionError();
}
}
@@ -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.app.plugin.assembler.sleigh.expr.match;
import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
/**
* A matcher which accept any expression of the required type
*
* <p>
* This requires no further consideration of the expressions operands. If the type matches, the
* expression matches.
*
* @param <T> the type to match
*/
public class AnyMatcher<T extends PatternExpression> extends AbstractExpressionMatcher<T> {
public static AnyMatcher<PatternExpression> any() {
return new AnyMatcher<>(PatternExpression.class);
}
public AnyMatcher(Set<Class<? extends T>> ops) {
super(ops);
}
public AnyMatcher(Class<T> cls) {
super(cls);
}
@Override
protected boolean matchDetails(T expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
return true;
}
}
@@ -0,0 +1,91 @@
/* ###
* 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.app.plugin.assembler.sleigh.expr.match;
import java.util.*;
import ghidra.app.plugin.processors.sleigh.expression.BinaryExpression;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
/**
* A matcher for a binary expression
*
* <p>
* If the required type matches, the matching descends to the left then right operands.
*
* @param <T> the type of expression matched
*/
public class BinaryExpressionMatcher<T extends BinaryExpression>
extends AbstractExpressionMatcher<T> {
/**
* A matcher for binary expression allowing commutativity
*
* <p>
* This behaves the same as {@link BinaryExpressionMatcher}, but if the first attempt fails, the
* operand match is re-attempted with the operands swapped.
*
* @param <T> the type of expression matched
*/
public static class Commutative<T extends BinaryExpression> extends BinaryExpressionMatcher<T> {
public Commutative(Set<Class<? extends T>> ops,
ExpressionMatcher<?> leftMatcher, ExpressionMatcher<?> rightMatcher) {
super(ops, leftMatcher, rightMatcher);
}
public Commutative(Class<T> cls, ExpressionMatcher<?> leftMatcher,
ExpressionMatcher<?> rightMatcher) {
super(cls, leftMatcher, rightMatcher);
}
@Override
protected boolean matchDetails(T expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
Set<ExpressionMatcher<?>> reset = new HashSet<>(result.keySet());
if (leftMatcher.match(expression.getLeft(), result) &&
rightMatcher.match(expression.getRight(), result)) {
return true;
}
result.keySet().retainAll(reset);
return rightMatcher.match(expression.getLeft(), result) &&
leftMatcher.match(expression.getRight(), result);
}
}
protected final ExpressionMatcher<?> leftMatcher;
protected final ExpressionMatcher<?> rightMatcher;
public BinaryExpressionMatcher(Set<Class<? extends T>> ops,
ExpressionMatcher<?> leftMatcher, ExpressionMatcher<?> rightMatcher) {
super(ops);
this.leftMatcher = leftMatcher;
this.rightMatcher = rightMatcher;
}
public BinaryExpressionMatcher(Class<T> cls, ExpressionMatcher<?> leftMatcher,
ExpressionMatcher<?> rightMatcher) {
super(cls);
this.leftMatcher = leftMatcher;
this.rightMatcher = rightMatcher;
}
@Override
protected boolean matchDetails(T expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
return leftMatcher.match(expression.getLeft(), result) &&
rightMatcher.match(expression.getRight(), result);
}
}
@@ -0,0 +1,39 @@
/* ###
* 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.app.plugin.assembler.sleigh.expr.match;
import java.util.Map;
import ghidra.app.plugin.processors.sleigh.expression.ConstantValue;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
/**
* A matcher for a given constant value
*/
public class ConstantValueMatcher extends AbstractExpressionMatcher<ConstantValue> {
protected final long value;
public ConstantValueMatcher(long value) {
super(ConstantValue.class);
this.value = value;
}
@Override
protected boolean matchDetails(ConstantValue expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
return expression.getValue() == value;
}
}
@@ -0,0 +1,309 @@
/* ###
* 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.app.plugin.assembler.sleigh.expr.match;
import java.util.HashMap;
import java.util.Map;
import ghidra.app.plugin.processors.sleigh.expression.*;
/**
* A matcher for a form of patten expression
*
* <p>
* Some solvers may need to apply sophisticated heuristics to recognize certain forms that commonly
* occur in pattern expressions. These can certainly be programmed manually, but for many cases, the
* form recognition can be accomplished by describing the form as an expression matcher. For a
* shorter syntax to construct such matchers. See {@link Context}.
*
* @param <T> the type of expression matched
*/
public interface ExpressionMatcher<T extends PatternExpression> {
/**
* Attempt to match the given expression, recording the substitutions if successful
*
* @param expression the expression to match
* @return a map of matchers to substituted expressions
*/
default Map<ExpressionMatcher<?>, PatternExpression> match(PatternExpression expression) {
Map<ExpressionMatcher<?>, PatternExpression> result = new HashMap<>();
if (match(expression, result)) {
return result;
}
return null;
}
/**
* Retrieve the expression substituted for this matcher from a previous successful match
*
* <p>
* Calling this on the root matcher is relatively useless, as it would simply return the
* expression passed to {@link #match(PatternExpression)}. Instead, sub-matchers should be saved
* in a variable, allowing their values to be retrieved. See {@link Context}, for an example.
*
* @param results the previous match results
* @return the substituted expression
*/
@SuppressWarnings("unchecked")
default T get(Map<ExpressionMatcher<?>, PatternExpression> results) {
return (T) results.get(this);
}
/**
* Attempt to match the given expression, recording substitutions in the given map
*
* <p>
* Even if the match was unsuccessful, the result map may contain attempted substitutions. Thus,
* the map should be discarded if unsuccessful.
*
* @param expression the expression to match
* @param result a map to store matchers to substituted expressions
* @return true if successful, false if not
*/
boolean match(PatternExpression expression,
Map<ExpressionMatcher<?>, PatternExpression> result);
/**
* A context for defining expression matcher succinctly
*
* <p>
* Implementations of this interface have easy access to factory methods for each kind of
* {@link PatternExpression}. Additionally, the class itself provide a convenient container for
* saving important sub-matchers, so that important sub-expression can be readily retrieved. For
* example:
*
* <pre>
* static class MyMatchers implements ExpressionMatcher.Context {
* ExpressionMatcher<ConstantValue> shamt = var(ConstantValue.class);
* ExpressionMatcher<LeftShiftExpression> exp = shl(var(), shamt);
* }
*
* static final MyMatchers MATCHERS = new MyMatchers();
*
* public long getConstantShift(PatternExpression expression) {
* Map<ExpressionMatcher<?>, PatternExpression> result = MATCHERS.exp.match(expression);
* if (result == null) {
* return -1;
* }
* return MATCHERS.shamt.get(result).getValue();
* }
* </pre>
*
* <p>
* Saving a sub-matcher to a field (as in the example) also permits that sub-matcher to appear
* in multiple places. In that case, the sub-matcher must match identical expressions wherever
* it appears. For example, if {@code cv} matches any constant value, then {@code plus(cv, cv)}
* would match {@code 2 + 2}, but not {@code 2 + 3}.
*/
interface Context {
/**
* Match the form {@code L & R} or {@code R & L}
*
* @param left the matcher for the left operand
* @param right the matcher for the right operand
* @return the matcher
*/
default ExpressionMatcher<AndExpression> and(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher.Commutative<>(AndExpression.class, left, right);
}
/**
* Match the form {@code L / R}
*
* @param left the matcher for the dividend
* @param right the matcher for the divisor
* @return the matcher for the quotient
*/
default ExpressionMatcher<DivExpression> div(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher<>(DivExpression.class, left, right);
}
/**
* Match the form {@code L << R}
*
* @param left the matcher for the left operand
* @param right the matcher for the shift amount
* @return the matcher
*/
default ExpressionMatcher<LeftShiftExpression> shl(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher<>(LeftShiftExpression.class, left, right);
}
/**
* Match the form {@code L * R} or {@code R * L}
*
* @param left the matcher for the left factor
* @param right the matcher for the right factor
* @return the matcher for the product
*/
default ExpressionMatcher<MultExpression> mul(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher.Commutative<>(MultExpression.class, left, right);
}
/**
* Match the form {@code L | R} or {@code R | L}
*
* @param left the matcher for the left operand
* @param right the matcher for the right operand
* @return the matcher
*/
default ExpressionMatcher<OrExpression> or(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher.Commutative<>(OrExpression.class, left, right);
}
/**
* Match the form {@code L + R} or {@code R + L}
*
* @param left the matcher for the left term
* @param right the matcher for the right term
* @return the matcher for the sum
*/
default ExpressionMatcher<PlusExpression> plus(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher<>(PlusExpression.class, left, right);
}
/**
* Match the form {@code L >> R}
*
* @param left the matcher for the left operand
* @param right the matcher for the shift amount
* @return the matcher
*/
default ExpressionMatcher<RightShiftExpression> shr(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher<>(RightShiftExpression.class, left, right);
}
/**
* Match the form {@code L - R}
*
* @param left the matcher for the left term
* @param right the matcher for the right term
* @return the matcher for the difference
*/
default ExpressionMatcher<SubExpression> sub(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher<>(SubExpression.class, left, right);
}
/**
* Match the form {@code L $xor R} or {@code R $xor L}
*
* @param left the matcher for the left operand
* @param right the matcher for the right operand
* @return the matcher
*/
default ExpressionMatcher<XorExpression> xor(ExpressionMatcher<?> left,
ExpressionMatcher<?> right) {
return new BinaryExpressionMatcher<>(XorExpression.class, left, right);
}
/**
* Match a given constant value
*
* <p>
* <b>NOTE:</b> To match an unspecified constant value, use {@link #var(Class)} with
* {@link ConstantValue}.
*
* @param value the value to match
* @return the matcher
*/
default ExpressionMatcher<ConstantValue> cv(long value) {
return new ConstantValueMatcher(value);
}
/**
* Match any expression
*
* <p>
* This matches any expression without consideration of its operands, except insofar when it
* appears in multiple places, it will check that subsequent matches are identical to the
* first.
*
* @return the matcher
*/
default ExpressionMatcher<PatternExpression> var() {
return AnyMatcher.any();
}
/**
* Match any expression of the given type
*
* @param <T> the type of expression to match
* @param cls the class of expression to match
* @return the matcher
*/
default <T extends PatternExpression> ExpressionMatcher<T> var(Class<T> cls) {
return new AnyMatcher<>(cls);
}
/**
* Match an operand value
*
* <p>
* Typically, this must wrap any use of a field, since that field is considered an operand
* from the constructor's perspective.
*
* @param def the matcher for the operand's defining expression.
* @return the operand matcher
*/
default ExpressionMatcher<OperandValue> opnd(ExpressionMatcher<?> def) {
return new OperandValueMatcher(def);
}
/**
* Match a field by its size
*
* <p>
* This matches either a {@link TokenField} or a {@link ContextField}. If matched, it then
* passes a {@link ConstantValue} of the field's size (in bits) into the given size matcher.
*
* @param size the matcher for the field's size
* @return the field matcher
*/
default ExpressionMatcher<PatternValue> fldSz(ExpressionMatcher<?> size) {
return new FieldSizeMatcher(size);
}
/**
* Match the form {@code -U}
*
* @param unary the child matcher
* @return the matcher
*/
default ExpressionMatcher<MinusExpression> neg(ExpressionMatcher<?> unary) {
return new UnaryExpressionMatcher<>(MinusExpression.class, unary);
}
/**
* Match the form {@code ~U}
*
* @param unary the child matcher
* @return the matcher
*/
default ExpressionMatcher<NotExpression> not(ExpressionMatcher<?> unary) {
return new UnaryExpressionMatcher<>(NotExpression.class, unary);
}
}
}
@@ -0,0 +1,49 @@
/* ###
* 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.app.plugin.assembler.sleigh.expr.match;
import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.processors.sleigh.expression.*;
/**
* A matcher for a token or context field, constrained by its size in bits
*/
public class FieldSizeMatcher extends AbstractExpressionMatcher<PatternValue> {
protected final ExpressionMatcher<?> sizeMatcher;
public FieldSizeMatcher(ExpressionMatcher<?> sizeMatcher) {
super(Set.of(ContextField.class, TokenField.class));
this.sizeMatcher = sizeMatcher;
}
@Override
protected boolean matchDetails(PatternValue expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
if (expression instanceof ContextField) {
ContextField cf = (ContextField) expression;
long size = cf.getEndBit() - cf.getStartBit() + 1;
return sizeMatcher.match(new ConstantValue(size), result);
}
if (expression instanceof TokenField) {
TokenField tf = (TokenField) expression;
long size = tf.getBitEnd() - tf.getBitStart() + 1;
return sizeMatcher.match(new ConstantValue(size), result);
}
return false;
}
}
@@ -0,0 +1,42 @@
/* ###
* 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.app.plugin.assembler.sleigh.expr.match;
import java.util.Map;
import ghidra.app.plugin.assembler.sleigh.expr.OperandValueSolver;
import ghidra.app.plugin.processors.sleigh.expression.OperandValue;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
/**
* A matcher for a constructor's operand value, constrained by its defining expression
*/
public class OperandValueMatcher extends AbstractExpressionMatcher<OperandValue> {
protected final ExpressionMatcher<?> defMatcher;
public OperandValueMatcher(ExpressionMatcher<?> defMatcher) {
super(OperandValue.class);
this.defMatcher = defMatcher;
}
@Override
protected boolean matchDetails(OperandValue expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
OperandSymbol opSym = expression.getConstructor().getOperand(expression.getIndex());
return defMatcher.match(OperandValueSolver.getDefiningExpression(opSym), result);
}
}
@@ -0,0 +1,51 @@
/* ###
* 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.app.plugin.assembler.sleigh.expr.match;
import java.util.Map;
import java.util.Set;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
import ghidra.app.plugin.processors.sleigh.expression.UnaryExpression;
/**
* A matcher for a unnary expression
*
* <p>
* If the required type matches, the matching descends to the child operand.
*
* @param <T> the type of expression matched
*/
public class UnaryExpressionMatcher<T extends UnaryExpression>
extends AbstractExpressionMatcher<T> {
protected final ExpressionMatcher<?> unaryMatcher;
public UnaryExpressionMatcher(Set<Class<? extends T>> ops, ExpressionMatcher<?> unaryMatcher) {
super(ops);
this.unaryMatcher = unaryMatcher;
}
public UnaryExpressionMatcher(Class<T> cls, ExpressionMatcher<?> unaryMatcher) {
super(cls);
this.unaryMatcher = unaryMatcher;
}
@Override
protected boolean matchDetails(T expression,
Map<ExpressionMatcher<?>, PatternExpression> result) {
return unaryMatcher.match(expression.getUnary(), result);
}
}
@@ -34,16 +34,17 @@ import ghidra.generic.util.datastruct.TreeSetValuedTreeMap;
/**
* Defines a context-free grammar, usually for the purpose of parsing mnemonic assembly instructions
*
* As in classic computer science, a CFG consists of productions of non-terminals and terminals.
* The left-hand side of the a production must be a single non-terminal, but the right-hand side
* may be any string of symbols. To avoid overloading the term "String," here we call it a
* "Sentential."
* <p>
* As in classic computer science, a CFG consists of productions of non-terminals and terminals. The
* left-hand side of the a production must be a single non-terminal, but the right-hand side may be
* any string of symbols. To avoid overloading the term "String," here we call it a "Sentential."
*
* <p>
* To define a grammar, simply construct an appropriate subclass (probably {@link AssemblyGrammar})
* and call {@link #addProduction(AbstractAssemblyProduction)} or
* {@link #addProduction(AssemblyNonTerminal, AssemblySentential)}. The grammar object will collect
* the non-terminals and terminals.
* {@link #addProduction(AssemblyNonTerminal, AssemblySentential)}.
*
* <p>
* By default, the start symbol is taken from the left-hand side of the first production added to
* the grammar.
*
@@ -71,6 +72,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Add a production to the grammar
*
* @param lhs the left-hand side
* @param rhs the right-hand side
*/
@@ -81,6 +83,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Add a production to the grammar
*
* @param prod the production
*/
public void addProduction(P prod) {
@@ -96,7 +99,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
String lhsName = lhs.getName();
symbols.put(lhsName, lhs);
nonterminals.put(lhsName, lhs);
for (AssemblySymbol sym : prod) {
for (AssemblySymbol sym : prod.getRHS()) {
if (sym instanceof AssemblyNonTerminal) {
@SuppressWarnings("unchecked")
NT nt = (NT) sym;
@@ -115,14 +118,15 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Check if the given production is purely recursive, i.e., of the form I =&gt; I
*
* @param prod the production to check
* @return true iff the production is purely recursive
*/
protected boolean isPureRecursive(P prod) {
if (prod.size() != 1) {
if (prod.getRHS().size() != 1) {
return false;
}
if (!prod.getLHS().equals(prod.getRHS().get(0))) {
if (!prod.getLHS().equals(prod.getRHS().getSymbol(0))) {
return false;
}
return true;
@@ -130,6 +134,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Change the start symbol for the grammar
*
* @param nt the new start symbol
*/
public void setStart(AssemblyNonTerminal nt) {
@@ -138,6 +143,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Change the start symbol for the grammar
*
* @param startName the name of the new start symbol
*/
public void setStartName(String startName) {
@@ -146,6 +152,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Get the start symbol for the grammar
*
* @return the start symbol
*/
public NT getStart() {
@@ -154,6 +161,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Get the name of the start symbol for the grammar
*
* @return the name of the start symbol
*/
public String getStartName() {
@@ -162,6 +170,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Get the named non-terminal
*
* @param name the name of the desired non-terminal
* @return the non-terminal, or null if it is not in this grammar
*/
@@ -171,6 +180,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Get the named terminal
*
* @param name the name of the desired terminal
* @return the terminal, or null if it is not in this grammar
*/
@@ -180,6 +190,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Add all the productions of a given grammar to this one
*
* @param that the grammar whose productions to add
*/
public void combine(AbstractAssemblyGrammar<NT, P> that) {
@@ -190,6 +201,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Print the productions of this grammar to the given stream
*
* @param out the stream
*/
public void print(PrintStream out) {
@@ -201,17 +213,19 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Check that the grammar is consistent
*
* The grammar is consistent if every non-terminal appearing in the grammar, also appears as
* the left-hand side of some production. If not, such non-terminals are said to be undefined.
* <p>
* The grammar is consistent if every non-terminal appearing in the grammar also appears as the
* left-hand side of some production. If not, such non-terminals are said to be undefined.
*
* @throws AssemblyGrammarException the grammar is inconsistent, i.e., contains undefined
* non-terminals.
* non-terminals.
*/
public void verify() throws AssemblyGrammarException {
if (!productions.containsKey(startName)) {
throw new AssemblyGrammarException("Start symbol has no defining production");
}
for (P prod : productions.values()) {
for (AssemblySymbol sym : prod) {
for (AssemblySymbol sym : prod.getRHS()) {
if (sym instanceof AssemblyNonTerminal) {
AssemblyNonTerminal nt = (AssemblyNonTerminal) sym;
if (!(productions.containsKey(nt.getName()))) {
@@ -233,6 +247,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Get the non-terminals
*
* @return
*/
public Collection<NT> nonTerminals() {
@@ -241,6 +256,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Get the terminals
*
* @return
*/
public Collection<AssemblyTerminal> terminals() {
@@ -249,6 +265,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Get all productions where the left-hand side non-terminal has the given name
*
* @param name the name of the non-terminal
* @return all productions "defining" the named non-terminal
*/
@@ -261,6 +278,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Get all productions where the left-hand side is the given non-terminal
*
* @param nt the non-terminal whose defining productions to find
* @return all productions "defining" the given non-terminal
*/
@@ -270,6 +288,7 @@ public abstract class AbstractAssemblyGrammar<NT extends AssemblyNonTerminal, P
/**
* Check if the grammar contains any symbol with the given name
*
* @param name the name to find
* @return true iff a terminal or non-terminal has the given name
*/
@@ -15,12 +15,7 @@
*/
package ghidra.app.plugin.assembler.sleigh.grammars;
import java.util.List;
import org.apache.commons.collections4.list.AbstractListDecorator;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblySymbol;
/**
* Defines a production in a context-free grammar, usually for parsing mnemonic assembly
@@ -29,7 +24,6 @@ import ghidra.app.plugin.assembler.sleigh.symbol.AssemblySymbol;
* @param <NT> the type of non-terminals
*/
public abstract class AbstractAssemblyProduction<NT extends AssemblyNonTerminal>
extends AbstractListDecorator<AssemblySymbol>
implements Comparable<AbstractAssemblyProduction<NT>> {
private final NT lhs;
private final AssemblySentential<NT> rhs;
@@ -38,6 +32,7 @@ public abstract class AbstractAssemblyProduction<NT extends AssemblyNonTerminal>
/**
* Construct a production with the given LHS and RHS
*
* @param lhs the left-hand side
* @param rhs the right-hand side
*/
@@ -47,16 +42,13 @@ public abstract class AbstractAssemblyProduction<NT extends AssemblyNonTerminal>
this.rhs = rhs;
}
@Override
protected List<AssemblySymbol> decorated() {
return rhs;
}
/**
* Get the index of the production
*
* Instead of using deep comparison, the index is often used as the identify of the production
* <p>
* Instead of using deep comparison, the index is often used as the identity of the production
* within a grammar.
*
* @return the index
*/
public int getIndex() {
@@ -65,6 +57,7 @@ public abstract class AbstractAssemblyProduction<NT extends AssemblyNonTerminal>
/**
* Get the left-hand side
*
* @return the LHS
*/
public NT getLHS() {
@@ -73,6 +66,7 @@ public abstract class AbstractAssemblyProduction<NT extends AssemblyNonTerminal>
/**
* Get the right-hand side
*
* @return the RHS
*/
public AssemblySentential<NT> getRHS() {
@@ -123,15 +117,12 @@ public abstract class AbstractAssemblyProduction<NT extends AssemblyNonTerminal>
return result;
}
@Override
public AssemblySentential<NT> subList(int fromIndex, int toIndex) {
return rhs.subList(fromIndex, toIndex);
}
/**
* Get the "name" of this production
*
* <p>
* This is mostly just notional and for debugging. The name is taken as the name of the LHS.
*
* @return the name of the LHS
*/
public String getName() {
@@ -20,9 +20,10 @@ import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyExtendedNonTerminal;
/**
* Defines an "extended" grammar
*
* "Extended grammar" as in a grammar extended with state numbers from an LR0 parser.
* See <a href="http://web.cs.dal.ca/~sjackson/lalr1.html">LALR(1) Parsing</a> from Stephen Jackson
* of Dalhousie University, Halifax, Nova Scotia, Canada.
* <p>
* "Extended grammar" as in a grammar extended with state numbers from an LR0 parser. See
* <a href="http://web.cs.dal.ca/~sjackson/lalr1.html">LALR(1) Parsing</a> from Stephen Jackson of
* Dalhousie University, Halifax, Nova Scotia, Canada.
*/
public class AssemblyExtendedGrammar
extends AbstractAssemblyGrammar<AssemblyExtendedNonTerminal, AssemblyExtendedProduction> {
@@ -29,6 +29,7 @@ public class AssemblyExtendedProduction
/**
* Construct an extended production based on the given ancestor
*
* @param lhs the extended left-hand side
* @param rhs the extended right-hand side
* @param finalState the end state of the final symbol of the RHS
@@ -49,6 +50,7 @@ public class AssemblyExtendedProduction
/**
* Get the final state of this production
*
* @return the end state of the last symbol of the RHS
*/
public int getFinalState() {
@@ -57,6 +59,7 @@ public class AssemblyExtendedProduction
/**
* Get the original production from which this production was derived
*
* @return the original production
*/
public AssemblyProduction getAncestor() {
@@ -17,8 +17,6 @@ package ghidra.app.plugin.assembler.sleigh.grammars;
import java.util.*;
import org.apache.commons.collections4.map.LazyMap;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyConstructorSemantic;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal;
import ghidra.app.plugin.processors.sleigh.Constructor;
@@ -27,6 +25,7 @@ import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern;
/**
* Defines a context free grammar, used to parse mnemonic assembly instructions
*
* <p>
* This stores the CFG and the associated semantics for each production. It also has mechanisms for
* tracking "purely recursive" productions. These are productions of the form I =&gt; I, and they
* necessarily create ambiguity. Thus, when constructing a parser, it is useful to identify them
@@ -35,8 +34,10 @@ import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern;
public class AssemblyGrammar
extends AbstractAssemblyGrammar<AssemblyNonTerminal, AssemblyProduction> {
// a nested map of semantics by production, by constructor
protected final Map<AssemblyProduction, Map<Constructor, AssemblyConstructorSemantic>> semantics =
LazyMap.lazyMap(new TreeMap<>(), () -> new TreeMap<>());
protected final Map<AssemblyProduction, Map<Constructor, AssemblyConstructorSemantic>> semanticsByProduction =
new TreeMap<>();
protected final Map<Constructor, AssemblyConstructorSemantic> semanticsByConstructor =
new HashMap<>();
// a map of purely recursive, e.g., I => I, productions by name of LHS
protected final Map<String, AssemblyProduction> pureRecursive = new TreeMap<>();
@@ -58,6 +59,7 @@ public class AssemblyGrammar
/**
* Add a production associated with a SLEIGH constructor semantic
*
* @param lhs the left-hand side
* @param rhs the right-hand side
* @param pattern the pattern associated with the constructor
@@ -68,27 +70,32 @@ public class AssemblyGrammar
DisjointPattern pattern, Constructor cons, List<Integer> indices) {
AssemblyProduction prod = newProduction(lhs, rhs);
addProduction(prod);
Map<Constructor, AssemblyConstructorSemantic> map = semantics.get(prod);
AssemblyConstructorSemantic sem = map.get(cons);
if (sem == null) {
sem = new AssemblyConstructorSemantic(cons, indices);
map.put(cons, sem);
}
else if (!indices.equals(sem.getOperandIndices())) {
Map<Constructor, AssemblyConstructorSemantic> map =
semanticsByProduction.computeIfAbsent(prod, p -> new TreeMap<>());
AssemblyConstructorSemantic sem =
map.computeIfAbsent(cons, c -> new AssemblyConstructorSemantic(cons, indices));
if (!indices.equals(sem.getOperandIndices())) {
throw new IllegalStateException(
"Productions of the same constructor must have same operand indices");
}
semanticsByConstructor.put(cons, sem);
sem.addPattern(pattern);
}
/**
* Get the semantics associated with a given production
*
* @param prod the production
* @return all semantics associated with the given production
*/
public Collection<AssemblyConstructorSemantic> getSemantics(AssemblyProduction prod) {
return Collections.unmodifiableCollection(semantics.get(prod).values());
return Collections.unmodifiableCollection(
semanticsByProduction.computeIfAbsent(prod, p -> new TreeMap<>()).values());
}
public AssemblyConstructorSemantic getSemantic(Constructor cons) {
return semanticsByConstructor.get(cons);
}
@Override
@@ -96,13 +103,15 @@ public class AssemblyGrammar
super.combine(that);
if (that instanceof AssemblyGrammar) {
AssemblyGrammar ag = (AssemblyGrammar) that;
this.semantics.putAll(ag.semantics);
this.semanticsByProduction.putAll(ag.semanticsByProduction);
this.semanticsByConstructor.putAll(ag.semanticsByConstructor);
this.pureRecursive.putAll(ag.pureRecursive);
}
}
/**
* Get all productions in the grammar that are purely recursive
*
* @return
*/
public Collection<AssemblyProduction> getPureRecursive() {
@@ -111,6 +120,7 @@ public class AssemblyGrammar
/**
* Obtain, if present, the purely recursive production having the given LHS
*
* @param lhs the left-hand side
* @return the desired production, or null
*/
@@ -16,8 +16,9 @@
package ghidra.app.plugin.assembler.sleigh.grammars;
import java.util.*;
import org.apache.commons.collections4.list.AbstractListDecorator;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import ghidra.app.plugin.assembler.sleigh.symbol.*;
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseToken;
@@ -25,29 +26,29 @@ import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseToken;
/**
* A "string" of symbols
*
* To avoid overloading the word "String", we call this a "sentential". Technically, to be a
* <p>
* To avoid overloading the word "string", we call this a "sentential". Technically, to be a
* "sentential" in the classic sense, it must be a possible element in the derivation of a sentence
* in the grammar starting with the start symbol. We ignore that if only for the sake of naming.
*
* @param <NT> the type of non-terminals
*/
public class AssemblySentential<NT extends AssemblyNonTerminal> extends
AbstractListDecorator<AssemblySymbol> implements Comparable<AssemblySentential<NT>> {
public class AssemblySentential<NT extends AssemblyNonTerminal>
implements Comparable<AssemblySentential<NT>>, Iterable<AssemblySymbol> {
private List<AssemblySymbol> symbols;
private final List<AssemblySymbol> unmodifiableSymbols;
private boolean finished = false;
public static final AssemblyStringTerminal WHITE_SPACE = new WhiteSpace();
private static final Pattern PAT_COMMA_WS = Pattern.compile(",\\s+");
/**
* Construct a string from the given list of symbols
*
* @param symbols
*/
public AssemblySentential(List<? extends AssemblySymbol> symbols) {
this.symbols = new ArrayList<>(symbols);
}
@Override
protected List<AssemblySymbol> decorated() {
return symbols;
this.unmodifiableSymbols = Collections.unmodifiableList(symbols);
}
/**
@@ -58,19 +59,22 @@ public class AssemblySentential<NT extends AssemblyNonTerminal> extends
*/
public AssemblySentential() {
this.symbols = new ArrayList<>();
this.unmodifiableSymbols = Collections.unmodifiableList(symbols);
}
/**
* Construct a string from any number of symbols
*
* @param syms
*/
public AssemblySentential(AssemblySymbol... syms) {
this.symbols = Arrays.asList(syms);
this.unmodifiableSymbols = Collections.unmodifiableList(symbols);
}
@Override
public String toString() {
if (symbols.size() == 0) {
if (symbols.isEmpty()) {
return "e";
}
Iterator<? extends AssemblySymbol> symIt = symbols.iterator();
@@ -117,6 +121,7 @@ public class AssemblySentential<NT extends AssemblyNonTerminal> extends
/**
* A "whitespace" terminal
*
* <p>
* This terminal represents "optional" whitespace. "Optional" because in certain circumstances,
* whitespace is not actually required, i.e., before or after a special character.
*/
@@ -132,7 +137,7 @@ public class AssemblySentential<NT extends AssemblyNonTerminal> extends
@Override
public Collection<AssemblyParseToken> match(String buffer, int pos, AssemblyGrammar grammar,
Map<String, Long> labels) {
AssemblyNumericSymbols symbols) {
if (buffer.length() == 0) {
return Collections.singleton(new WhiteSpaceParseToken(grammar, this, ""));
}
@@ -158,7 +163,7 @@ public class AssemblySentential<NT extends AssemblyNonTerminal> extends
}
@Override
public Collection<String> getSuggestions(String got, Map<String, Long> labels) {
public Collection<String> getSuggestions(String got, AssemblyNumericSymbols symbols) {
return Collections.singleton(" ");
}
}
@@ -175,6 +180,7 @@ public class AssemblySentential<NT extends AssemblyNonTerminal> extends
/**
* The token consumed by a whitespace terminal when it anticipates the end of input
*
* <p>
* "Expected" tokens given by a parse machine when this is the last token it has consumed are
* not valid suggestions. The machine should instead suggest a whitespace character.
*/
@@ -185,7 +191,18 @@ public class AssemblySentential<NT extends AssemblyNonTerminal> extends
}
/**
* Add "optional" whitespace, if not already preceded by whitespace
* Add a symbol to the right of this sentential
*
* @param symbol the symbol to add
* @return true
*/
public boolean addSymbol(AssemblySymbol symbol) {
return symbols.add(symbol);
}
/**
* Add optional whitespace, if not already preceded by whitespace
*
* @return true if whitespace was added
*/
public boolean addWS() {
@@ -193,7 +210,95 @@ public class AssemblySentential<NT extends AssemblyNonTerminal> extends
if (last != null) {
return false;
}
return add(WHITE_SPACE);
return addSymbol(WHITE_SPACE);
}
/**
* Add a comma followed by optional whitespace.
*/
public void addCommaWS() {
addSymbol(new AssemblyStringTerminal(","));
addWS();
}
/**
* Add a syntactic terminal element, but with consideration for optional whitespace surrounding
* special characters
*
* @param str the expected terminal
*/
public void addSeparatorPart(String str) {
String tstr = str.trim();
if (tstr.equals("")) {
addWS();
return;
}
char first = tstr.charAt(0);
if (!str.startsWith(tstr)) {
addWS();
}
if (!Character.isLetterOrDigit(first)) {
addWS();
}
addSymbol(new AssemblyStringTerminal(tstr));
char last = tstr.charAt(tstr.length() - 1);
if (!str.endsWith(tstr)) {
addWS();
}
if (!Character.isLetterOrDigit(last)) {
addWS();
}
}
/**
* Get the symbols in this sentential
*
* @return the symbols;
*/
public List<AssemblySymbol> getSymbols() {
return unmodifiableSymbols;
}
public AssemblySymbol getSymbol(int pos) {
return symbols.get(pos);
}
/**
* Split the given string into pieces matched by the pattern, and the pieces between
*
* <p>
* This invokes the given callbacks as the string is processed from left to right.
*
* @param str the string to split
* @param pat the pattern to match
* @param matched the callback for matched portions
* @param unmatched the callback for unmatched portions
*/
private static void forMatchUnmatch(String str, Pattern pat, Consumer<String> matched,
Consumer<String> unmatched) {
int startU = 0;
Matcher mat = pat.matcher(str);
while (mat.find()) {
if (startU < mat.start()) {
unmatched.accept(str.substring(startU, mat.start()));
}
matched.accept(mat.group());
startU = mat.end();
}
if (startU < str.length()) {
unmatched.accept(str.substring(startU));
}
}
/**
* Add a syntactic terminal element, but considering that commas contained within may be
* followed by optional whitespace
*
* @param str the expected terminal
*/
public void addSeparators(String str) {
// NB. When displaying print pieces, the disassembler replaces all ",\\s+" with ","
forMatchUnmatch(str, PAT_COMMA_WS, matched -> addCommaWS(), this::addSeparatorPart);
}
// If the right-most symbol is whitespace, return it
@@ -209,18 +314,31 @@ public class AssemblySentential<NT extends AssemblyNonTerminal> extends
}
/**
* Trim leading and trailing whitespace, and make the string immutable
* Trim leading and trailing whitespace, and make the sentential immutable
*/
public void finish() {
if (finished) {
return;
}
symbols = Collections.unmodifiableList(symbols);
symbols = unmodifiableSymbols;
finished = true;
}
@Override
public AssemblySentential<NT> subList(int fromIndex, int toIndex) {
public Iterator<AssemblySymbol> iterator() {
return unmodifiableSymbols.iterator();
}
public AssemblySentential<NT> sub(int fromIndex, int toIndex) {
return new AssemblySentential<>(symbols.subList(fromIndex, toIndex));
}
/**
* Get the number of symbols, including whitespace, in this sentential
*
* @return the number of symbols
*/
public int size() {
return symbols.size();
}
}
@@ -28,6 +28,7 @@ import ghidra.generic.util.datastruct.TreeSetValuedTreeMap;
/**
* A class to compute the first and follow of every non-terminal in a grammar
*
* <p>
* See Alfred V. Aho, Monica S. Lam, Ravi Sethi, Jeffrey D. Ullman, <i>Compilers: Principles,
* Techniques, &amp; Tools</i>. Bostom, MA: Pearson, 2007, pp. 220-2.
*/
@@ -43,6 +44,7 @@ public class AssemblyFirstFollow {
/**
* Compute the first and follow sets for every non-terminal in the given grammar
*
* @param grammar the grammar
*/
public AssemblyFirstFollow(AbstractAssemblyGrammar<?, ?> grammar) {
@@ -61,7 +63,7 @@ public class AssemblyFirstFollow {
while (changed) {
changed = false;
for (AbstractAssemblyProduction<?> prod : grammar) {
if (nullable.containsAll(prod)) {
if (nullable.containsAll(prod.getRHS().getSymbols())) {
changed |= nullable.add(prod.getLHS());
}
}
@@ -81,7 +83,7 @@ public class AssemblyFirstFollow {
// Add the first of all each symbol
// Terminate after a terminal or non-nullable symbol
for (AbstractAssemblyProduction<?> prod : grammar) {
for (AssemblySymbol sym : prod) {
for (AssemblySymbol sym : prod.getRHS()) {
if (sym instanceof AssemblyNonTerminal) {
AssemblyNonTerminal nt = (AssemblyNonTerminal) sym;
changed |= first.putAll(prod.getLHS(), first.get(nt));
@@ -116,13 +118,13 @@ public class AssemblyFirstFollow {
// Finish the subwalk after a terminal or non-nullable symbol
// If you hit the end, add follow(LHS) to follow the current symbol
for (AbstractAssemblyProduction<?> prod : grammar) {
nextX: for (int i = 0; i < prod.size(); i++) {
AssemblySymbol px = prod.get(i);
nextX: for (int i = 0; i < prod.getRHS().size(); i++) {
AssemblySymbol px = prod.getRHS().getSymbol(i);
if (px instanceof AssemblyNonTerminal) {
AssemblyNonTerminal X = (AssemblyNonTerminal) px;
int j;
for (j = i + 1; j < prod.size(); j++) {
AssemblySymbol B = prod.get(j);
for (j = i + 1; j < prod.getRHS().size(); j++) {
AssemblySymbol B = prod.getRHS().getSymbol(j);
if (B instanceof AssemblyNonTerminal) {
AssemblyNonTerminal nt = (AssemblyNonTerminal) B;
changed |= follow.putAll(X, first.get(nt));
@@ -149,7 +151,9 @@ public class AssemblyFirstFollow {
/**
* Get the nullable set
*
* <p>
* That is the set of all non-terminals, which through some derivation, can produce epsilon.
*
* @return the set
*/
public Collection<AssemblyNonTerminal> getNullable() {
@@ -159,8 +163,10 @@ public class AssemblyFirstFollow {
/**
* Get the first set for a given non-terminal
*
* <p>
* That is the set of all terminals, which through some derivation from the given non-terminal,
* can appear first in a sentential form.
*
* @param nt the non-terminal
* @return the set
*/
@@ -171,8 +177,10 @@ public class AssemblyFirstFollow {
/**
* Get the follow set for a given non-terminal
*
* <p>
* That is the set of all terminals, which through some derivation from the start symbol, can
* appear immediately after the given non-terminal in a sentential form.
*
* @param nt the non-terminal
* @return the set
*/
@@ -182,6 +190,7 @@ public class AssemblyFirstFollow {
/**
* For debugging, print out the computed sets to the given stream
*
* @param out the stream
*/
public void print(PrintStream out) {
@@ -40,6 +40,7 @@ public class AssemblyParseAcceptResult extends AssemblyParseResult {
/**
* Get the tree
*
* @return the tree
*/
public AssemblyParseBranch getTree() {

Some files were not shown because too many files have changed in this diff Show More