From e7458ed08ba8d7b5b230c4a4bec8bfb0c39cee4c Mon Sep 17 00:00:00 2001 From: Dan <46821332+nsadeveloper789@users.noreply.github.com> Date: Fri, 3 Nov 2023 17:38:07 -0400 Subject: [PATCH] GP-4185: Make Assembler more extensible --- .../core/debug/stack/StackUnwinderTest.java | 7 +- .../AssemblyThrasherDevScript.java | 4 +- .../app/plugin/assembler/Assembler.java | 201 +---- .../plugin/assembler/AssemblerBuilder.java | 34 +- .../plugin/assembler/AssemblySelector.java | 19 +- .../plugin/assembler/GenericAssembler.java | 218 +++++ .../assembler/GenericAssemblerBuilder.java | 55 ++ .../sleigh/AbstractSleighAssembler.java | 279 ++++++ .../AbstractSleighAssemblerBuilder.java | 397 ++++++++ .../assembler/sleigh/SleighAssembler.java | 242 +---- .../sleigh/SleighAssemblerBuilder.java | 350 +------ .../expr/AbstractBinaryExpressionSolver.java | 52 +- .../sleigh/expr/AbstractExpressionSolver.java | 9 +- .../expr/AbstractUnaryExpressionSolver.java | 13 +- .../sleigh/expr/ConstantValueSolver.java | 22 +- .../sleigh/expr/ContextFieldSolver.java | 13 +- .../expr/EndInstructionValueSolver.java | 18 +- .../expr/LeftShiftExpressionSolver.java | 24 +- .../sleigh/expr/MultExpressionSolver.java | 35 +- .../expr/Next2InstructionValueSolver.java | 7 +- .../sleigh/expr/OperandValueSolver.java | 28 +- .../sleigh/expr/OrExpressionSolver.java | 49 +- .../sleigh/expr/RecursiveDescentSolver.java | 21 +- .../expr/RightShiftExpressionSolver.java | 20 +- .../expr/StartInstructionValueSolver.java | 6 +- .../sleigh/expr/TokenFieldSolver.java | 13 +- .../sleigh/grammars/AssemblyGrammar.java | 12 +- .../sleigh/grammars/AssemblyProduction.java | 4 + .../sleigh/grammars/AssemblySentential.java | 11 +- .../sem/AbstractAssemblyResolution.java | 170 ++++ .../AbstractAssemblyResolutionFactory.java | 442 +++++++++ .../sleigh/sem/AbstractAssemblyState.java | 29 +- .../sem/AbstractAssemblyStateGenerator.java | 10 +- .../sem/AbstractAssemblyTreeResolver.java | 527 +++++++++++ .../sleigh/sem/AssemblyConstructState.java | 10 +- .../sem/AssemblyConstructStateGenerator.java | 16 +- .../sem/AssemblyConstructorSemantic.java | 24 +- .../sleigh/sem/AssemblyContextGraph.java | 10 +- ...AssemblyHiddenConstructStateGenerator.java | 18 +- .../sleigh/sem/AssemblyNopState.java | 32 +- .../sleigh/sem/AssemblyNopStateGenerator.java | 9 +- .../sleigh/sem/AssemblyOperandState.java | 53 +- .../sem/AssemblyOperandStateGenerator.java | 4 +- .../sleigh/sem/AssemblyPatternBlock.java | 156 +++- .../sleigh/sem/AssemblyResolution.java | 343 +------ .../sleigh/sem/AssemblyResolutionResults.java | 52 +- .../sleigh/sem/AssemblyResolvedBackfill.java | 111 +-- .../sleigh/sem/AssemblyResolvedError.java | 79 +- .../sleigh/sem/AssemblyResolvedPatterns.java | 853 ++++-------------- .../sem/AssemblyStringStateGenerator.java | 39 + .../sleigh/sem/AssemblyTreeResolver.java | 550 +---------- .../sem/DefaultAssemblyResolutionFactory.java | 30 + .../sem/DefaultAssemblyResolvedBackfill.java | 158 ++++ .../sem/DefaultAssemblyResolvedError.java | 111 +++ .../sem/DefaultAssemblyResolvedPatterns.java | 775 ++++++++++++++++ .../symbol/AssemblyFixedNumericTerminal.java | 6 +- .../symbol/AssemblyNumericMapTerminal.java | 4 + .../symbol/AssemblyNumericTerminal.java | 4 + .../symbol/AssemblyStringMapTerminal.java | 7 +- .../sleigh/symbol/AssemblyStringTerminal.java | 20 +- .../sleigh/tree/AssemblyParseBranch.java | 3 +- .../sleigh/tree/AssemblyParseHiddenNode.java | 42 + .../tree/AssemblyParseNumericToken.java | 5 - .../sleigh/tree/AssemblyParseTreeNode.java | 18 - .../assembler/sleigh/ARMAssemblyTest.java | 7 +- .../sleigh/AbstractAssemblyTest.java | 16 +- .../assembler/sleigh/AssemblyTestCase.java | 6 +- .../assembler/sleigh/MIPSAssemblyTest.java | 15 + .../plugin/assembler/sleigh/SolverTest.java | 123 ++- .../assembler/sleigh/parse/ParserTest.java | 26 +- .../sleigh/sem/AssemblyPatternBlockTest.java | 30 + .../assembler/sleigh/x86AssemblyTest.java | 10 + 72 files changed, 4303 insertions(+), 2813 deletions(-) create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssembler.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssemblerBuilder.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssembler.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssemblerBuilder.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyResolution.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyResolutionFactory.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyTreeResolver.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyStringStateGenerator.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolutionFactory.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedBackfill.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedError.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedPatterns.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseHiddenNode.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyPatternBlockTest.java diff --git a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java index ca28ab6fe9..534d1b1a4e 100644 --- a/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java +++ b/Ghidra/Debug/Debugger/src/test/java/ghidra/app/plugin/core/debug/stack/StackUnwinderTest.java @@ -85,8 +85,8 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { public static final AssemblySelector NO_16BIT_CALLS = new AssemblySelector() { @Override - public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, - AssemblyPatternBlock ctx) throws AssemblySemanticException { + public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx) + throws AssemblySemanticException { for (AssemblyResolvedPatterns res : filterCompatibleAndSort(rr, ctx)) { byte[] ins = res.getInstruction().getVals(); // HACK to avoid 16-bit CALL.... TODO: Why does this happen? @@ -95,8 +95,7 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest { "Filtered 16-bit call " + NumericUtilities.convertBytesToString(ins)); continue; } - return AssemblyResolution.resolved(res.getInstruction().fillMask(), - res.getContext(), "Selected", null, null, null); + return new Selection(res.getInstruction().fillMask(), res.getContext()); } throw new AssemblySemanticException(semanticErrors); } diff --git a/Ghidra/Features/Base/ghidra_scripts/AssemblyThrasherDevScript.java b/Ghidra/Features/Base/ghidra_scripts/AssemblyThrasherDevScript.java index ea96dff9b4..e5f6806b42 100644 --- a/Ghidra/Features/Base/ghidra_scripts/AssemblyThrasherDevScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/AssemblyThrasherDevScript.java @@ -63,8 +63,8 @@ public class AssemblyThrasherDevScript extends GhidraScript { } @Override - public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, - AssemblyPatternBlock ctx) throws AssemblySemanticException { + public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx) + throws AssemblySemanticException { StringBuilder sb = new StringBuilder(); boolean gotOne = false; boolean failedOne = false; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/Assembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/Assembler.java index 0c0e30d50b..9218d58c9b 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/Assembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/Assembler.java @@ -15,15 +15,7 @@ */ package ghidra.app.plugin.assembler; -import java.util.Collection; - -import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult; -import ghidra.app.plugin.assembler.sleigh.sem.*; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressOverflowException; -import ghidra.program.model.listing.Instruction; -import ghidra.program.model.listing.InstructionIterator; -import ghidra.program.model.mem.MemoryAccessException; +import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; /** * The primary interface for performing assembly in Ghidra. @@ -32,194 +24,5 @@ import ghidra.program.model.mem.MemoryAccessException; * Use the {@link Assemblers} class to obtain a suitable implementation for a given program or * language. */ -public interface Assembler { - /** - * Assemble a sequence of instructions and place them at the given address. - * - *

- * This method is only valid if the assembler is bound to a program. An instance may optionally - * implement this method without a program binding. In that case, the returned iterator will - * refer to pseudo instructions. - * - *

- * NOTE: 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 - * @return an iterator over the resulting instructions - * @throws AssemblySyntaxException a textual instruction is non well-formed - * @throws AssemblySemanticException a well-formed instruction cannot be assembled - * @throws MemoryAccessException there is an issue writing the result to program memory - * @throws AddressOverflowException the resulting block is beyond the valid address range - */ - public InstructionIterator assemble(Address at, String... listing) - throws AssemblySyntaxException, - AssemblySemanticException, MemoryAccessException, AddressOverflowException; - - /** - * Assemble a line instruction at the given address. - * - *

- * This method is valid with or without a bound program. Even if bound, the program is not - * modified; however, the appropriate context information is taken from the bound program. - * Without a program, the language's default context is taken at the given location. - * - * @param at the location of the start of the instruction - * @param line the textual assembly code - * @return the binary machine code, suitable for placement at the given address - * @throws AssemblySyntaxException the textual instruction is not well-formed - * @throws AssemblySemanticException the the well-formed instruction cannot be assembled - */ - public byte[] assembleLine(Address at, String line) - throws AssemblySyntaxException, AssemblySemanticException; - - /** - * Assemble a line instruction at the given address, assuming the given context. - * - *

- * This method works like {@link #assembleLine(Address, String)} except that it allows you to - * override the assumed context at that location. - * - * @param at the location of the start of the instruction - * @param line the textual assembly code - * @param ctx the context register value at the start of the instruction - * @return the results of semantic resolution (from all parse results) - * @throws AssemblySyntaxException the textual instruction is not well-formed - * @throws AssemblySemanticException the well-formed instruction cannot be assembled - */ - public byte[] assembleLine(Address at, String line, AssemblyPatternBlock ctx) - throws AssemblySemanticException, AssemblySyntaxException; - - /** - * Parse a line instruction. - * - *

- * Generally, you should just use {@link #assembleLine(Address, String)}, but if you'd like - * access to the parse trees outside of an {@link AssemblySelector}, then this may be an - * acceptable option. Most notably, this is an excellent way to obtain suggestions for - * auto-completion. - * - *

- * Each item in the returned collection is either a complete parse tree, or a syntax error - * Because all parse paths are attempted, it's possible to get many mixed results. For example, - * The input line may be a valid instruction; however, there may be suggestions to continue the - * line toward another valid instruction. - * - * @param line the line (or partial line) to parse - * @return the results of parsing - */ - public Collection parseLine(String line); - - /** - * Resolve a given parse tree at the given address, assuming the given context - * - *

- * Each item in the returned collection is either a completely resolved instruction, or a - * semantic error. Because all resolutions are attempted, it's possible to get many mixed - * results. - * - *

- * NOTE: 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 - * @param ctx the context register value at the start of the instruction - * @return the results of semantic resolution - */ - public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at, - AssemblyPatternBlock ctx); - - /** - * Resolve a given parse tree at the given address. - * - *

- * Each item in the returned collection is either a completely resolved instruction, or a - * semantic error. Because all resolutions are attempted, it's possible to get many mixed - * results. - * - *

- * NOTE: 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 - * @return the results of semantic resolution - */ - public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at); - - /** - * Assemble a line instruction at the given address. - * - *

- * This method works like {@link #resolveLine(Address, String, AssemblyPatternBlock)}, except - * that it derives the context using {@link #getContextAt(Address)}. - * - * @param at the location of the start of the instruction - * @param line the textual assembly code - * @return the collection of semantic resolution results - * @throws AssemblySyntaxException the textual instruction is not well-formed - */ - public AssemblyResolutionResults resolveLine(Address at, String line) - throws AssemblySyntaxException; - - /** - * Assemble a line instruction at the given address, assuming the given context. - * - *

- * This method works like {@link #assembleLine(Address, String, AssemblyPatternBlock)}, except - * that it returns all possible resolutions for the parse trees that pass the - * {@link AssemblySelector}. - * - * @param at the location of the start of the instruction - * @param line the textual assembly code - * @param ctx the context register value at the start of the instruction - * @return the collection of semantic resolution results - * @throws AssemblySyntaxException the textual instruction is not well-formed - */ - public AssemblyResolutionResults resolveLine(Address at, String line, AssemblyPatternBlock ctx) - throws AssemblySyntaxException; - - /** - * Place a resolved (and fully-masked) instruction into the bound program. - * - *

- * This method is not valid without a program binding. Also, this method must be called during a - * program database transaction. - * - * @param res the resolved and fully-masked instruction - * @param at the location of the start of the instruction - * @return the new {@link Instruction} code unit - * @throws MemoryAccessException there is an issue writing the result to program memory - */ - public Instruction patchProgram(AssemblyResolvedPatterns res, Address at) - throws MemoryAccessException; - - /** - * Place instruction bytes into the bound program. - * - *

- * This method is not valid without a program binding. Also, this method must be called during a - * program database transaction. - * - * @param insbytes the instruction data - * @param at the location of the start of the instruction - * @return an iterator over the disassembled instructions - * @throws MemoryAccessException there is an issue writing the result to program memory - */ - public InstructionIterator patchProgram(byte[] insbytes, Address at) - throws MemoryAccessException; - - /** - * Get the context at a given address - * - *

- * If there is a program binding, this will extract the actual context at the given address. - * Otherwise, it will obtain the default context at the given address for the language. - * - * @param addr the address - * @return the context - */ - public AssemblyPatternBlock getContextAt(Address addr); +public interface Assembler extends GenericAssembler { } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblerBuilder.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblerBuilder.java index c29b8cee42..1f816f17ac 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblerBuilder.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblerBuilder.java @@ -15,42 +15,18 @@ */ package ghidra.app.plugin.assembler; -import ghidra.program.model.lang.Language; -import ghidra.program.model.lang.LanguageID; +import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; import ghidra.program.model.listing.Program; /** * An interface to build an assembler for a given language */ -public interface AssemblerBuilder { - /** - * Get the ID of the language for which this instance builds an assembler - * - * @return the language ID - */ - public LanguageID getLanguageID(); +public interface AssemblerBuilder + extends GenericAssemblerBuilder { - /** - * 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 - */ + @Override public Assembler getAssembler(AssemblySelector selector); - /** - * 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 - */ + @Override public Assembler getAssembler(AssemblySelector selector, Program program); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblySelector.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblySelector.java index 5733ba229c..279bc0dce1 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblySelector.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/AssemblySelector.java @@ -118,6 +118,16 @@ public class AssemblySelector { return sorted; } + /** + * A resolved selection from the results given to + * {@link AssemblySelector#select(AssemblyResolutionResults, AssemblyPatternBlock)} + * + * @param ins the resolved instructions bytes, ideally with a full mask + * @param ctx the resolved context bytes for compatibility checks + */ + public record Selection(AssemblyPatternBlock ins, AssemblyPatternBlock ctx) { + } + /** * Select an instruction from the possible results. * @@ -134,16 +144,15 @@ 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 if all the given results are semantic errors */ - public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, - AssemblyPatternBlock ctx) throws AssemblySemanticException { + public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx) + throws AssemblySemanticException { List sorted = filterCompatibleAndSort(rr, ctx); // Pick just the first 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, null, null); + return new Selection(res.getInstruction().fillMask(), res.getContext()); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssembler.java new file mode 100644 index 0000000000..af059a3c69 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssembler.java @@ -0,0 +1,218 @@ +/* ### + * 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; + +import java.util.Collection; + +import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult; +import ghidra.app.plugin.assembler.sleigh.sem.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressOverflowException; +import ghidra.program.model.listing.Instruction; +import ghidra.program.model.listing.InstructionIterator; +import ghidra.program.model.mem.MemoryAccessException; + +public interface GenericAssembler { + /** + * Assemble a sequence of instructions and place them at the given address. + * + *

+ * This method is only valid if the assembler is bound to a program. An instance may optionally + * implement this method without a program binding. In that case, the returned iterator will + * refer to pseudo instructions. + * + *

+ * NOTE: 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 + * @return an iterator over the resulting instructions + * @throws AssemblySyntaxException a textual instruction is non well-formed + * @throws AssemblySemanticException a well-formed instruction cannot be assembled + * @throws MemoryAccessException there is an issue writing the result to program memory + * @throws AddressOverflowException the resulting block is beyond the valid address range + */ + public InstructionIterator assemble(Address at, String... listing) + throws AssemblySyntaxException, + AssemblySemanticException, MemoryAccessException, AddressOverflowException; + + /** + * Assemble a line instruction at the given address. + * + *

+ * This method is valid with or without a bound program. Even if bound, the program is not + * modified; however, the appropriate context information is taken from the bound program. + * Without a program, the language's default context is taken at the given location. + * + * @param at the location of the start of the instruction + * @param line the textual assembly code + * @return the binary machine code, suitable for placement at the given address + * @throws AssemblySyntaxException the textual instruction is not well-formed + * @throws AssemblySemanticException the the well-formed instruction cannot be assembled + */ + public byte[] assembleLine(Address at, String line) + throws AssemblySyntaxException, AssemblySemanticException; + + /** + * Assemble a line instruction at the given address, assuming the given context. + * + *

+ * This method works like {@link #assembleLine(Address, String)} except that it allows you to + * override the assumed context at that location. + * + * @param at the location of the start of the instruction + * @param line the textual assembly code + * @param ctx the context register value at the start of the instruction + * @return the results of semantic resolution (from all parse results) + * @throws AssemblySyntaxException the textual instruction is not well-formed + * @throws AssemblySemanticException the well-formed instruction cannot be assembled + */ + public byte[] assembleLine(Address at, String line, AssemblyPatternBlock ctx) + throws AssemblySemanticException, AssemblySyntaxException; + + /** + * Parse a line instruction. + * + *

+ * Generally, you should just use {@link #assembleLine(Address, String)}, but if you'd like + * access to the parse trees outside of an {@link AssemblySelector}, then this may be an + * acceptable option. Most notably, this is an excellent way to obtain suggestions for + * auto-completion. + * + *

+ * Each item in the returned collection is either a complete parse tree, or a syntax error + * Because all parse paths are attempted, it's possible to get many mixed results. For example, + * The input line may be a valid instruction; however, there may be suggestions to continue the + * line toward another valid instruction. + * + * @param line the line (or partial line) to parse + * @return the results of parsing + */ + public Collection parseLine(String line); + + /** + * Resolve a given parse tree at the given address, assuming the given context + * + *

+ * Each item in the returned collection is either a completely resolved instruction, or a + * semantic error. Because all resolutions are attempted, it's possible to get many mixed + * results. + * + *

+ * NOTE: 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 + * @param ctx the context register value at the start of the instruction + * @return the results of semantic resolution + */ + public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at, + AssemblyPatternBlock ctx); + + /** + * Resolve a given parse tree at the given address. + * + *

+ * Each item in the returned collection is either a completely resolved instruction, or a + * semantic error. Because all resolutions are attempted, it's possible to get many mixed + * results. + * + *

+ * NOTE: 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 + * @return the results of semantic resolution + */ + public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at); + + /** + * Assemble a line instruction at the given address. + * + *

+ * This method works like {@link #resolveLine(Address, String, AssemblyPatternBlock)}, except + * that it derives the context using {@link #getContextAt(Address)}. + * + * @param at the location of the start of the instruction + * @param line the textual assembly code + * @return the collection of semantic resolution results + * @throws AssemblySyntaxException the textual instruction is not well-formed + */ + public AssemblyResolutionResults resolveLine(Address at, String line) + throws AssemblySyntaxException; + + /** + * Assemble a line instruction at the given address, assuming the given context. + * + *

+ * This method works like {@link #assembleLine(Address, String, AssemblyPatternBlock)}, except + * that it returns all possible resolutions for the parse trees that pass the + * {@link AssemblySelector}. + * + * @param at the location of the start of the instruction + * @param line the textual assembly code + * @param ctx the context register value at the start of the instruction + * @return the collection of semantic resolution results + * @throws AssemblySyntaxException the textual instruction is not well-formed + */ + public AssemblyResolutionResults resolveLine(Address at, String line, AssemblyPatternBlock ctx) + throws AssemblySyntaxException; + + /** + * Place a resolved (and fully-masked) instruction into the bound program. + * + *

+ * This method is not valid without a program binding. Also, this method must be called during a + * program database transaction. + * + * @param res the resolved and fully-masked instruction + * @param at the location of the start of the instruction + * @return the new {@link Instruction} code unit + * @throws MemoryAccessException there is an issue writing the result to program memory + */ + public Instruction patchProgram(AssemblyResolvedPatterns res, Address at) + throws MemoryAccessException; + + /** + * Place instruction bytes into the bound program. + * + *

+ * This method is not valid without a program binding. Also, this method must be called during a + * program database transaction. + * + * @param insbytes the instruction data + * @param at the location of the start of the instruction + * @return an iterator over the disassembled instructions + * @throws MemoryAccessException there is an issue writing the result to program memory + */ + public InstructionIterator patchProgram(byte[] insbytes, Address at) + throws MemoryAccessException; + + /** + * Get the context at a given address + * + *

+ * If there is a program binding, this will extract the actual context at the given address. + * Otherwise, it will obtain the default context at the given address for the language. + * + * @param addr the address + * @return the context + */ + public AssemblyPatternBlock getContextAt(Address addr); +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssemblerBuilder.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssemblerBuilder.java new file mode 100644 index 0000000000..18524bcceb --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/GenericAssemblerBuilder.java @@ -0,0 +1,55 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.assembler; + +import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; +import ghidra.program.model.lang.Language; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.listing.Program; + +public interface GenericAssemblerBuilder< // + RP extends AssemblyResolvedPatterns, A extends GenericAssembler> { + /** + * 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 + */ + public A getAssembler(AssemblySelector selector); + + /** + * 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 + */ + public A getAssembler(AssemblySelector selector, Program program); +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssembler.java new file mode 100644 index 0000000000..509846a6bb --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssembler.java @@ -0,0 +1,279 @@ +/* ### + * 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; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Collection; + +import ghidra.app.plugin.assembler.*; +import ghidra.app.plugin.assembler.AssemblySelector.Selection; +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.tree.AssemblyParseBranch; +import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; +import ghidra.framework.model.DomainObjectChangedEvent; +import ghidra.framework.model.DomainObjectListener; +import ghidra.program.disassemble.Disassembler; +import ghidra.program.disassemble.DisassemblerMessageListener; +import ghidra.program.model.address.*; +import ghidra.program.model.lang.Register; +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.util.ChangeManager; +import ghidra.util.task.TaskMonitor; + +public abstract class AbstractSleighAssembler + implements GenericAssembler { + protected static final DbgTimer dbg = DbgTimer.INACTIVE; + + protected class ListenerForSymbolsRefresh implements DomainObjectListener { + @Override + public void domainObjectChanged(DomainObjectChangedEvent ev) { + if (ev.containsEvent(ChangeManager.DOCR_SYMBOL_ADDED) || + ev.containsEvent(ChangeManager.DOCR_SYMBOL_ADDRESS_CHANGED) || + ev.containsEvent(ChangeManager.DOCR_SYMBOL_REMOVED) || + ev.containsEvent(ChangeManager.DOCR_SYMBOL_RENAMED)) { + synchronized (lock) { + symbols = null; + } + } + } + } + + protected final Object lock = new Object(); + + protected final SleighLanguage lang; + protected final Program program; + protected final Listing listing; + protected final Memory memory; + + protected final AbstractAssemblyResolutionFactory factory; + protected final AssemblySelector selector; + protected final AssemblyParser parser; + protected final AssemblyDefaultContext defaultContext; + protected final AssemblyContextGraph ctxGraph; + + protected AssemblyNumericSymbols symbols; + + protected AbstractSleighAssembler(AbstractAssemblyResolutionFactory factory, + AssemblySelector selector, Program program, AssemblyParser parser, + AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) { + this.factory = factory; + this.selector = selector; + this.program = program; + this.parser = parser; + this.defaultContext = defaultContext; + this.ctxGraph = ctxGraph; + + this.lang = (SleighLanguage) program.getLanguage(); + this.listing = program.getListing(); + this.memory = program.getMemory(); + } + + protected AbstractSleighAssembler(AbstractAssemblyResolutionFactory factory, + AssemblySelector selector, SleighLanguage lang, AssemblyParser parser, + AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) { + this.factory = factory; + this.selector = selector; + this.lang = lang; + this.parser = parser; + this.defaultContext = defaultContext; + this.ctxGraph = ctxGraph; + + this.program = null; + this.listing = null; + this.memory = null; + } + + protected abstract AbstractAssemblyTreeResolver newResolver(Address at, + AssemblyParseBranch tree, AssemblyPatternBlock ctx); + + @Override + public Instruction patchProgram(AssemblyResolvedPatterns res, Address at) + throws MemoryAccessException { + if (!res.getInstruction().isFullMask()) { + throw new AssemblySelectionError("Selected instruction must have a full mask."); + } + return patchProgram(res.getInstruction().getVals(), at).next(); + } + + @Override + public InstructionIterator patchProgram(byte[] insbytes, Address at) + throws MemoryAccessException { + if (insbytes.length == 0) { + return listing.getInstructions(new AddressSet(), true); + } + Address end = at.add(insbytes.length - 1); + listing.clearCodeUnits(at, end, false); + memory.setBytes(at, insbytes); + AddressSet set = new AddressSet(at, end); + + // Creating this at construction causes it to assess memory flags too early. + Disassembler dis = Disassembler.getDisassembler(program, TaskMonitor.DUMMY, + DisassemblerMessageListener.IGNORE); + dis.disassemble(at, set); + return listing.getInstructions(set, true); + } + + @Override + public InstructionIterator assemble(Address at, String... assembly) + throws AssemblySyntaxException, AssemblySemanticException, MemoryAccessException, + AddressOverflowException { + Address start = at; + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + for (String part : assembly) { + for (String line : part.split("\n")) { + RegisterValue rv = program.getProgramContext().getDisassemblyContext(at); + dbg.println(rv); + AssemblyPatternBlock ctx = AssemblyPatternBlock.fromRegisterValue(rv); + ctx = ctx.fillMask(); + byte[] insbytes = assembleLine(at, line, ctx); + if (insbytes == null) { + return null; + } + try { + buf.write(insbytes); + } + catch (IOException e) { + throw new AssertionError(e); + } + at = at.addNoWrap(insbytes.length); + } + } + return patchProgram(buf.toByteArray(), start); + } + + @Override + public byte[] assembleLine(Address at, String line) + throws AssemblySyntaxException, AssemblySemanticException { + AssemblyPatternBlock ctx = defaultContext.getDefaultAt(at); + ctx = ctx.fillMask(); + return assembleLine(at, line, ctx); + } + + @Override + public Collection parseLine(String line) { + return parser.parse(line, getNumericSymbols()); + } + + @Override + public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at, + AssemblyPatternBlock ctx) { + if (parse.isError()) { + AssemblyResolutionResults results = factory.newAssemblyResolutionResults(); + AssemblyParseErrorResult err = (AssemblyParseErrorResult) parse; + results.add(factory.newErrorBuilder() + .error(err.describeError()) + .description("Parsing") + .build()); + return results; + } + + AssemblyParseAcceptResult acc = (AssemblyParseAcceptResult) parse; + AbstractAssemblyTreeResolver tr = newResolver(at, acc.getTree(), ctx); + return tr.resolve(); + } + + @Override + public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at) { + AssemblyPatternBlock ctx = getContextAt(at); + return resolveTree(parse, at, ctx); + } + + @Override + public AssemblyResolutionResults resolveLine(Address at, String line) + throws AssemblySyntaxException { + return resolveLine(at, line, getContextAt(at).fillMask()); + } + + @Override + public AssemblyResolutionResults resolveLine(Address at, String line, AssemblyPatternBlock ctx) + throws AssemblySyntaxException { + + if (!ctx.isFullMask()) { + throw new AssemblyError( + "Context must be fully-specified (full length, no shift, no unknowns)"); + } + if (lang.getContextBaseRegister() != Register.NO_CONTEXT && + ctx.length() < lang.getContextBaseRegister().getMinimumByteSize()) { + throw new AssemblyError( + "Context must be fully-specified (full length, no shift, no unknowns)"); + } + Collection parse = parseLine(line); + parse = selector.filterParse(parse); + if (!parse.iterator().hasNext()) { // Iterator.isEmpty()??? + throw new AssemblySelectionError( + "Must select at least one parse result. Report errors via AssemblySyntaxError"); + } + AssemblyResolutionResults results = factory.newAssemblyResolutionResults(); + for (AssemblyParseResult p : parse) { + results.absorb(resolveTree(p, at, ctx)); + } + return results; + } + + @Override + public byte[] assembleLine(Address at, String line, AssemblyPatternBlock ctx) + throws AssemblySemanticException, AssemblySyntaxException { + AssemblyResolutionResults results = resolveLine(at, line, ctx); + Selection sel = selector.select(results, ctx); + if (sel == null) { + throw new AssemblySelectionError( + "Must select exactly one instruction. Report errors via AssemblySemanticError"); + } + if (!sel.ins().isFullMask()) { + throw new AssemblySelectionError("Selected instruction must have a full mask."); + } + if (sel.ctx().combine(ctx) == null) { + throw new AssemblySelectionError("Selected instruction must have compatible context"); + } + return sel.ins().getVals(); + } + + /** + * A convenience to obtain assembly symbols + * + * @return the map + */ + protected AssemblyNumericSymbols getNumericSymbols() { + synchronized (lock) { + if (symbols != null) { + return symbols; + } + if (program == null) { + symbols = AssemblyNumericSymbols.fromLanguage(lang); + } + else { + symbols = AssemblyNumericSymbols.fromProgram(program); + } + return symbols; + } + } + + @Override + public AssemblyPatternBlock getContextAt(Address addr) { + if (program != null) { + RegisterValue rv = program.getProgramContext().getDisassemblyContext(addr); + return AssemblyPatternBlock.fromRegisterValue(rv); + } + return defaultContext.getDefaultAt(addr); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssemblerBuilder.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssemblerBuilder.java new file mode 100644 index 0000000000..9f25ddbfeb --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/AbstractSleighAssemblerBuilder.java @@ -0,0 +1,397 @@ +/* ### + * 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; + +import java.util.*; + +import org.apache.commons.collections4.MultiMapUtils; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.HashSetValuedHashMap; + +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.*; +import ghidra.app.plugin.assembler.sleigh.symbol.*; +import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; +import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx; +import ghidra.app.plugin.languages.sleigh.SleighLanguages; +import ghidra.app.plugin.languages.sleigh.SubtableEntryVisitor; +import ghidra.app.plugin.processors.sleigh.*; +import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern; +import ghidra.app.plugin.processors.sleigh.symbol.*; +import ghidra.app.plugin.processors.sleigh.template.ConstructTpl; +import ghidra.app.plugin.processors.sleigh.template.HandleTpl; +import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.listing.Program; +import ghidra.util.SystemUtilities; + +public abstract class AbstractSleighAssemblerBuilder< // + RP extends AssemblyResolvedPatterns, A extends GenericAssembler> + implements GenericAssemblerBuilder { + protected static final DbgTimer dbg = + SystemUtilities.isInTestingBatchMode() ? DbgTimer.INACTIVE : DbgTimer.ACTIVE; + + protected final SleighLanguage lang; + protected final AbstractAssemblyResolutionFactory factory; + protected AssemblyGrammar grammar; + protected AssemblyDefaultContext defaultContext; + protected AssemblyContextGraph ctxGraph; + protected AssemblyParser parser; + + protected boolean generated = false; + + // A cache for symbols converted during grammar construction + protected Map builtSymbols = new HashMap<>(); + + public AbstractSleighAssemblerBuilder(SleighLanguage lang) { + this.lang = lang; + this.factory = newResolutionFactory(); + } + + protected abstract AbstractAssemblyResolutionFactory newResolutionFactory(); + + protected abstract A newAssembler(AssemblySelector selector); + + protected abstract A newAssembler(AssemblySelector selector, + Program program); + + @Override + public LanguageID getLanguageID() { + return lang.getLanguageID(); + } + + @Override + public SleighLanguage getLanguage() { + return lang; + } + + /** + * Do the actual work to construct an assembler from a SLEIGH language + * + * @throws SleighException if there's an issue accessing the language + */ + protected void generateAssembler() throws SleighException { + if (generated) { + return; + } + generated = true; + try { + buildGrammar(); + grammar.verify(); + buildContext(); + buildContextGraph(); + buildParser(); + } + catch (SleighException e) { + // Not sure this can actually happen here + throw e; + } + catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public A getAssembler(AssemblySelector selector) { + generateAssembler(); + return newAssembler(selector); + } + + @Override + public A getAssembler(AssemblySelector selector, Program program) { + generateAssembler(); + return newAssembler(selector, program); + } + + /** + * Invert a varnode list to a map suitable for use with {@link AssemblyStringMapTerminal} + * + * @param vnlist the varnode list symbol + * @return the inverted string map + */ + protected MultiValuedMap invVarnodeList(VarnodeListSymbol vnlist) { + MultiValuedMap result = new HashSetValuedHashMap<>(); + int index = -1; + for (VarnodeSymbol vnsym : vnlist.getVarnodeTable()) { + index++; + if (vnsym != null) { + // nulls are _ in the spec, meaning the index is undefined. + result.put(vnsym.getName(), index); + } + } + return MultiMapUtils.unmodifiableMultiValuedMap(result); + } + + /** + * Invert a value map to a map suitable for use with {@link AssemblyNumericMapTerminal} + * + * @param vm the value map symbol + * @return the inverted numeric map + */ + protected Map invValueMap(ValueMapSymbol vm) { + Map result = new HashMap<>(); + List map = vm.getMap(); + for (int i = 0; i < map.size(); i++) { + long v = map.get(i); + result.put(v, i); + } + return Collections.unmodifiableMap(result); + } + + /** + * Invert a name table to a map suitable for use with {@link AssemblyStringMapTerminal} + * + * @param ns the name symbol + * @return the inverted string map + */ + protected MultiValuedMap invNameSymbol(NameSymbol ns) { + MultiValuedMap result = new HashSetValuedHashMap<>(); + int index = -1; + for (String name : ns.getNameTable()) { + index++; + if (name != null) { + result.put(name, index); + } + } + return MultiMapUtils.unmodifiableMultiValuedMap(result); + } + + /** + * Convert the given operand symbol to an {@link AssemblySymbol} + * + *

+ * 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 + * @param opsym the operand symbol to convert + * @return the converted assembly grammar symbol + */ + protected AssemblySymbol getSymbolFor(Constructor cons, OperandSymbol opsym) { + TripleSymbol defsym = opsym.getDefiningSymbol(); + // If the symbol has no defining symbol, that means the name is only valid in the local + // scope. We must keep them unique. + String name; + if (defsym == null) { + name = cons.getParent().getName() + ":" + opsym.getName(); + } + else { + name = opsym.getName(); + } + AssemblySymbol built = builtSymbols.get(name); + if (built != null) { + return built; + } + if (defsym == null) { + 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); + } + else if (defsym instanceof VarnodeListSymbol vnListSym) { + built = new AssemblyStringMapTerminal(name, invVarnodeList(vnListSym)); + } + else if (defsym instanceof VarnodeSymbol vnSym) { + built = new AssemblyStringTerminal(name, vnSym); + // Does this need to consume an operand? It seems not. + } + else if (defsym instanceof ValueMapSymbol vnMapSym) { + built = new AssemblyNumericMapTerminal(name, invValueMap(vnMapSym)); + } + else if (defsym instanceof NameSymbol nameSym) { + built = new AssemblyStringMapTerminal(name, invNameSymbol(nameSym)); + } + else { + throw new RuntimeException("Unknown symbol for " + name + ": " + defsym); + } + builtSymbols.put(name, built); + return built; + } + + /** + * Obtain the p-code result handle for the given operand + * + *

+ * 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 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 HandleTpl getHandleTpl(Constructor cons, OperandSymbol opsym) { + ConstructTpl ctpl = cons.getTempl(); + if (null == ctpl) { + // No pcode, no size specification + return null; + } + HandleTpl htpl = ctpl.getResult(); + if (null == htpl) { + // If nothing is exported, the size is unspecified + return null; + } + if (opsym.getIndex() != htpl.getOffsetOperandIndex()) { + // If the export is not of the same operand, it does not specify its size + return null; + } + return htpl; + } + + /** + * Build a portion of the grammar representing a table of constructors + * + * @param subtable the table + * @return the partial grammar + */ + protected AssemblyGrammar buildSubGrammar(SubtableSymbol subtable) { + final AssemblyGrammar subgrammar = new AssemblyGrammar(factory); + final AssemblyNonTerminal lhs = new AssemblyNonTerminal(subtable.getName()); + SleighLanguages.traverseConstructors(subtable, new SubtableEntryVisitor() { + @Override + public int visit(DisjointPattern pattern, Constructor cons) { + AssemblySentential rhs = new AssemblySentential<>(); + List indices = new ArrayList<>(); + for (String str : cons.getPrintPieces()) { + if (str.length() != 0) { + if (str.charAt(0) == '\n') { + int index = str.charAt(1) - 'A'; + OperandSymbol opsym = cons.getOperand(index); + AssemblySymbol sym = getSymbolFor(cons, opsym); + if (sym.takesOperandIndex()) { + indices.add(index); + } + rhs.addSymbol(sym); + } + else { + rhs.addSeparators(str); + } + } + } + addProduction(subgrammar, lhs, rhs, pattern, cons, indices); + return CONTINUE; + } + }); + return subgrammar; + } + + /** + * Extension point: Allows a chance to modify or derive a new production from a given one. + * + * @param subgrammar the sub-grammar for the sub-table symbol being processed + * @see AssemblyGrammar#addProduction(AssemblyNonTerminal, AssemblySentential, DisjointPattern, + * Constructor, List) + */ + protected void addProduction(AssemblyGrammar subgrammar, AssemblyNonTerminal lhs, + AssemblySentential rhs, DisjointPattern pattern, Constructor cons, + List indices) { + subgrammar.addProduction(lhs, rhs, pattern, cons, indices); + } + + /** + * Build the full grammar for the language + */ + protected void buildGrammar() { + try (DbgCtx dc = dbg.start("Building grammar")) { + grammar = new AssemblyGrammar(factory); + for (Symbol sym : lang.getSymbolTable().getSymbolList()) { + if (sym instanceof SubtableSymbol) { + SubtableSymbol subtable = (SubtableSymbol) sym; + grammar.combine(buildSubGrammar(subtable)); + } + else if (sym instanceof VarnodeSymbol) { + // Ignore. This just becomes a string terminal + } + else if (sym instanceof StartSymbol) { + // Ignore. We handle inst_start in semantic processing + } + else if (sym instanceof EndSymbol) { + // Ignore. We handle inst_next in semantic processing + } + + else if (sym instanceof Next2Symbol) { + // Ignore. We handle inst_next2 in semantic processing + } + else if (sym instanceof UseropSymbol) { + // Ignore. We don't do pcode. + } + else if (sym instanceof OperandSymbol) { + // Ignore. These are terminals, or will be produced by their defining symbols + } + else if (sym instanceof ValueSymbol) { + // Ignore. These are now terminals + } + else { + throw new RuntimeException("Unexpected type: " + sym.getClass()); + } + } + grammar.setStartName("instruction"); + } + } + + /** + * Build the default context for the language + */ + protected void buildContext() { + defaultContext = new AssemblyDefaultContext(lang); + } + + /** + * Build the context transition graph for the language + */ + protected void buildContextGraph() { + try (DbgCtx dc = dbg.start("Building context graph")) { + ctxGraph = new AssemblyContextGraph(factory, lang, grammar); + } + } + + /** + * Build the parser for the language + */ + protected void buildParser() { + try (DbgCtx dc = dbg.start("Building parser")) { + parser = new AssemblyParser(grammar); + } + } + + /** + * Get the built grammar for the language + * + * @return the grammar + */ + protected AssemblyGrammar getGrammar() { + return grammar; + } + + /** + * Get the built parser for the language + * + * @return the parser + */ + protected AssemblyParser getParser() { + return parser; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java index 25f8c83568..f399c80a6e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssembler.java @@ -15,28 +15,13 @@ */ package ghidra.app.plugin.assembler.sleigh; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Collection; - import ghidra.app.plugin.assembler.*; -import ghidra.app.plugin.assembler.sleigh.parse.*; +import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParser; 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.assembler.sleigh.tree.AssemblyParseBranch; import ghidra.app.plugin.processors.sleigh.SleighLanguage; -import ghidra.framework.model.DomainObjectChangedEvent; -import ghidra.framework.model.DomainObjectListener; -import ghidra.program.disassemble.Disassembler; -import ghidra.program.disassemble.DisassemblerMessageListener; -import ghidra.program.model.address.*; -import ghidra.program.model.lang.Register; -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.util.ChangeManager; -import ghidra.util.task.TaskMonitor; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; /** * An {@link Assembler} for a {@link SleighLanguage}. @@ -45,35 +30,8 @@ import ghidra.util.task.TaskMonitor; * 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 { - protected static final DbgTimer dbg = DbgTimer.INACTIVE; - - protected class ListenerForSymbolsRefresh implements DomainObjectListener { - @Override - public void domainObjectChanged(DomainObjectChangedEvent ev) { - if (ev.containsEvent(ChangeManager.DOCR_SYMBOL_ADDED) || - ev.containsEvent(ChangeManager.DOCR_SYMBOL_ADDRESS_CHANGED) || - ev.containsEvent(ChangeManager.DOCR_SYMBOL_REMOVED) || - ev.containsEvent(ChangeManager.DOCR_SYMBOL_RENAMED)) { - synchronized (lock) { - symbols = null; - } - } - } - } - - protected final Object lock = new Object(); - - protected AssemblySelector selector; - protected Program program; - protected Listing listing; - protected Memory memory; - protected AssemblyParser parser; - protected AssemblyDefaultContext defaultContext; - protected AssemblyContextGraph ctxGraph; - protected SleighLanguage lang; - - protected AssemblyNumericSymbols symbols; +public class SleighAssembler extends AbstractSleighAssembler + implements Assembler { /** * Construct a SleighAssembler. @@ -84,13 +42,11 @@ public class SleighAssembler implements Assembler { * @param defaultContext the default context for the language * @param ctxGraph the context graph */ - protected SleighAssembler(AssemblySelector selector, Program program, AssemblyParser parser, + protected SleighAssembler( + AbstractAssemblyResolutionFactory factory, + AssemblySelector selector, Program program, AssemblyParser parser, AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) { - this(selector, (SleighLanguage) program.getLanguage(), parser, defaultContext, ctxGraph); - this.program = program; - - this.listing = program.getListing(); - this.memory = program.getMemory(); + super(factory, selector, program, parser, defaultContext, ctxGraph); } /** @@ -105,182 +61,16 @@ public class SleighAssembler implements Assembler { * @param defaultContext the default context for the language * @param ctxGraph the context graph */ - protected SleighAssembler(AssemblySelector selector, SleighLanguage lang, AssemblyParser parser, + protected SleighAssembler( + AbstractAssemblyResolutionFactory factory, + AssemblySelector selector, SleighLanguage lang, AssemblyParser parser, AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) { - this.selector = selector; - this.lang = lang; - this.parser = parser; - this.defaultContext = defaultContext; - this.ctxGraph = ctxGraph; + super(factory, selector, lang, parser, defaultContext, ctxGraph); } @Override - public Instruction patchProgram(AssemblyResolvedPatterns res, Address at) - throws MemoryAccessException { - if (!res.getInstruction().isFullMask()) { - throw new AssemblySelectionError("Selected instruction must have a full mask."); - } - return patchProgram(res.getInstruction().getVals(), at).next(); - } - - @Override - public InstructionIterator patchProgram(byte[] insbytes, Address at) - throws MemoryAccessException { - if (insbytes.length == 0) { - return listing.getInstructions(new AddressSet(), true); - } - Address end = at.add(insbytes.length - 1); - listing.clearCodeUnits(at, end, false); - memory.setBytes(at, insbytes); - AddressSet set = new AddressSet(at, end); - - // Creating this at construction causes it to assess memory flags too early. - Disassembler dis = Disassembler.getDisassembler(program, TaskMonitor.DUMMY, - DisassemblerMessageListener.IGNORE); - dis.disassemble(at, set); - return listing.getInstructions(set, true); - } - - @Override - public InstructionIterator assemble(Address at, String... assembly) - throws AssemblySyntaxException, AssemblySemanticException, MemoryAccessException, - AddressOverflowException { - Address start = at; - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - for (String part : assembly) { - for (String line : part.split("\n")) { - RegisterValue rv = program.getProgramContext().getDisassemblyContext(at); - dbg.println(rv); - AssemblyPatternBlock ctx = AssemblyPatternBlock.fromRegisterValue(rv); - ctx = ctx.fillMask(); - byte[] insbytes = assembleLine(at, line, ctx); - if (insbytes == null) { - return null; - } - try { - buf.write(insbytes); - } - catch (IOException e) { - throw new AssertionError(e); - } - at = at.addNoWrap(insbytes.length); - } - } - return patchProgram(buf.toByteArray(), start); - } - - @Override - public byte[] assembleLine(Address at, String line) - throws AssemblySyntaxException, AssemblySemanticException { - AssemblyPatternBlock ctx = defaultContext.getDefaultAt(at); - ctx = ctx.fillMask(); - return assembleLine(at, line, ctx); - } - - @Override - public Collection parseLine(String line) { - return parser.parse(line, getNumericSymbols()); - } - - @Override - public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at) { - AssemblyPatternBlock ctx = getContextAt(at); - ctx = ctx.fillMask(); - return resolveTree(parse, at, ctx); - } - - @Override - public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at, + protected AssemblyTreeResolver newResolver(Address at, AssemblyParseBranch tree, AssemblyPatternBlock ctx) { - if (parse.isError()) { - AssemblyResolutionResults results = new AssemblyResolutionResults(); - AssemblyParseErrorResult err = (AssemblyParseErrorResult) parse; - results.add(AssemblyResolution.error(err.describeError(), "Parsing")); - return results; - } - - AssemblyParseAcceptResult acc = (AssemblyParseAcceptResult) parse; - AssemblyTreeResolver tr = - new AssemblyTreeResolver(lang, at, acc.getTree(), ctx, ctxGraph); - return tr.resolve(); - } - - @Override - public AssemblyResolutionResults resolveLine(Address at, String line) - throws AssemblySyntaxException { - return resolveLine(at, line, getContextAt(at).fillMask()); - } - - @Override - public AssemblyResolutionResults resolveLine(Address at, String line, AssemblyPatternBlock ctx) - throws AssemblySyntaxException { - - if (!ctx.isFullMask()) { - throw new AssemblyError( - "Context must be fully-specified (full length, no shift, no unknowns)"); - } - if (lang.getContextBaseRegister() != Register.NO_CONTEXT && - ctx.length() < lang.getContextBaseRegister().getMinimumByteSize()) { - throw new AssemblyError( - "Context must be fully-specified (full length, no shift, no unknowns)"); - } - Collection parse = parseLine(line); - parse = selector.filterParse(parse); - if (!parse.iterator().hasNext()) { // Iterator.isEmpty()??? - throw new AssemblySelectionError( - "Must select at least one parse result. Report errors via AssemblySyntaxError"); - } - AssemblyResolutionResults results = new AssemblyResolutionResults(); - for (AssemblyParseResult p : parse) { - results.absorb(resolveTree(p, at, ctx)); - } - return results; - } - - @Override - public byte[] assembleLine(Address at, String line, AssemblyPatternBlock ctx) - throws AssemblySemanticException, AssemblySyntaxException { - AssemblyResolutionResults results = resolveLine(at, line, ctx); - AssemblyResolvedPatterns res = selector.select(results, ctx); - if (res == null) { - throw new AssemblySelectionError( - "Must select exactly one instruction. Report errors via AssemblySemanticError"); - } - if (!res.getInstruction().isFullMask()) { - throw new AssemblySelectionError("Selected instruction must have a full mask."); - } - if (res.getContext().combine(ctx) == null) { - throw new AssemblySelectionError("Selected instruction must have compatible context"); - } - return res.getInstruction().getVals(); - } - - /** - * A convenience to obtain assembly symbols - * - * @return the map - */ - protected AssemblyNumericSymbols getNumericSymbols() { - synchronized (lock) { - if (symbols != null) { - return symbols; - } - if (program == null) { - symbols = AssemblyNumericSymbols.fromLanguage(lang); - } - else { - symbols = AssemblyNumericSymbols.fromProgram(program); - } - return symbols; - } - } - - @Override - public AssemblyPatternBlock getContextAt(Address addr) { - if (program != null) { - RegisterValue rv = program.getProgramContext().getDisassemblyContext(addr); - return AssemblyPatternBlock.fromRegisterValue(rv); - } - return defaultContext.getDefaultAt(addr); + return new AssemblyTreeResolver(factory, lang, at, tree, ctx, ctxGraph); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssemblerBuilder.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssemblerBuilder.java index 1a6f544b1a..ccf42cbb41 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssemblerBuilder.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/SleighAssemblerBuilder.java @@ -15,29 +15,11 @@ */ package ghidra.app.plugin.assembler.sleigh; -import java.util.*; - -import org.apache.commons.collections4.MultiValuedMap; -import org.apache.commons.collections4.multimap.HashSetValuedHashMap; - 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.*; -import ghidra.app.plugin.assembler.sleigh.symbol.*; -import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; -import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx; -import ghidra.app.plugin.languages.sleigh.SleighLanguages; -import ghidra.app.plugin.languages.sleigh.SubtableEntryVisitor; -import ghidra.app.plugin.processors.sleigh.*; -import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern; -import ghidra.app.plugin.processors.sleigh.symbol.*; -import ghidra.app.plugin.processors.sleigh.template.ConstructTpl; -import ghidra.app.plugin.processors.sleigh.template.HandleTpl; -import ghidra.program.model.lang.LanguageID; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.program.model.listing.Program; -import ghidra.util.SystemUtilities; /** * An {@link AssemblerBuilder} capable of supporting almost any {@link SleighLanguage} @@ -300,20 +282,9 @@ import ghidra.util.SystemUtilities; * 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 SleighLanguage lang; - protected AssemblyGrammar grammar; - protected AssemblyDefaultContext defaultContext; - protected AssemblyContextGraph ctxGraph; - protected AssemblyParser parser; - - protected boolean generated = false; - - // A cache for symbols converted during grammar construction - protected Map builtSymbols = new HashMap<>(); +public class SleighAssemblerBuilder + extends AbstractSleighAssemblerBuilder + implements AssemblerBuilder { /** * Construct an assembler builder for the given SLEIGH language @@ -321,319 +292,32 @@ public class SleighAssemblerBuilder implements AssemblerBuilder { * @param lang the language */ public SleighAssemblerBuilder(SleighLanguage lang) { - this.lang = lang; - } - - /** - * Do the actual work to construct an assembler from a SLEIGH language - * - * @throws SleighException if there's an issue accessing the language - */ - protected void generateAssembler() throws SleighException { - if (generated) { - return; - } - generated = true; - try { - buildGrammar(); - grammar.verify(); - buildContext(); - buildContextGraph(); - buildParser(); - } - catch (SleighException e) { - // Not sure this can actually happen here - throw e; - } - catch (Exception e) { - throw new RuntimeException(e); - } + super(lang); } @Override - public LanguageID getLanguageID() { - return lang.getLanguageID(); + protected AbstractAssemblyResolutionFactory< // + AssemblyResolvedPatterns, AssemblyResolvedBackfill> newResolutionFactory() { + return new DefaultAssemblyResolutionFactory(); } @Override - public SleighLanguage getLanguage() { - return lang; + protected SleighAssembler newAssembler(AssemblySelector selector) { + return new SleighAssembler(factory, selector, lang, parser, defaultContext, ctxGraph); + } + + @Override + protected SleighAssembler newAssembler(AssemblySelector selector, Program program) { + return new SleighAssembler(factory, selector, program, parser, defaultContext, ctxGraph); } @Override public SleighAssembler getAssembler(AssemblySelector selector) { - generateAssembler(); - SleighAssembler asm = new SleighAssembler(selector, lang, parser, defaultContext, ctxGraph); - return asm; + return (SleighAssembler) super.getAssembler(selector); } @Override public SleighAssembler getAssembler(AssemblySelector selector, Program program) { - generateAssembler(); - return new SleighAssembler(selector, program, parser, defaultContext, ctxGraph); - } - - /** - * Invert a varnode list to a map suitable for use with {@link AssemblyStringMapTerminal} - * - * @param vnlist the varnode list symbol - * @return the inverted string map - */ - protected MultiValuedMap invVarnodeList(VarnodeListSymbol vnlist) { - MultiValuedMap result = new HashSetValuedHashMap<>(); - int index = -1; - for (VarnodeSymbol vnsym : vnlist.getVarnodeTable()) { - index++; - if (vnsym != null) { - // nulls are _ in the spec, meaning the index is undefined. - result.put(vnsym.getName(), index); - } - } - return result; - } - - /** - * Invert a value map to a map suitable for use with {@link AssemblyNumericMapTerminal} - * - * @param vm the value map symbol - * @return the inverted numeric map - */ - protected Map invValueMap(ValueMapSymbol vm) { - Map result = new HashMap<>(); - List map = vm.getMap(); - for (int i = 0; i < map.size(); i++) { - long v = map.get(i); - result.put(v, i); - } - return result; - } - - /** - * Invert a name table to a map suitable for use with {@link AssemblyStringMapTerminal} - * - * @param ns the name symbol - * @return the inverted string map - */ - protected MultiValuedMap invNameSymbol(NameSymbol ns) { - MultiValuedMap result = new HashSetValuedHashMap<>(); - int index = -1; - for (String name : ns.getNameTable()) { - index++; - if (name != null) { - result.put(name, index); - } - } - return result; - } - - /** - * Convert the given operand symbol to an {@link AssemblySymbol} - * - *

- * 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 - * @param opsym the operand symbol to convert - * @return the converted assembly grammar symbol - */ - protected AssemblySymbol getSymbolFor(Constructor cons, OperandSymbol opsym) { - TripleSymbol defsym = opsym.getDefiningSymbol(); - // If the symbol has no defining symbol, that means the name is only valid in the local - // scope. We must keep them unique. - String name; - if (defsym == null) { - name = cons.getParent().getName() + ":" + opsym.getName(); - } - else { - name = opsym.getName(); - } - AssemblySymbol built = builtSymbols.get(name); - if (built != null) { - return built; - } - if (defsym == null) { - 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); - } - else if (defsym instanceof VarnodeListSymbol) { - built = new AssemblyStringMapTerminal(name, invVarnodeList((VarnodeListSymbol) defsym)); - } - else if (defsym instanceof VarnodeSymbol) { - built = new AssemblyStringTerminal(name); - // Does this need to consume an operand? It seems not. - } - else if (defsym instanceof ValueMapSymbol) { - built = new AssemblyNumericMapTerminal(name, invValueMap((ValueMapSymbol) defsym)); - } - else if (defsym instanceof NameSymbol) { - built = new AssemblyStringMapTerminal(name, invNameSymbol((NameSymbol) defsym)); - } - else { - throw new RuntimeException("Unknown symbol for " + name + ": " + defsym); - } - builtSymbols.put(name, built); - return built; - } - - /** - * Obtain the p-code result handle for the given operand - * - *

- * 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 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 HandleTpl getHandleTpl(Constructor cons, OperandSymbol opsym) { - ConstructTpl ctpl = cons.getTempl(); - if (null == ctpl) { - // No pcode, no size specification - return null; - } - HandleTpl htpl = ctpl.getResult(); - if (null == htpl) { - // If nothing is exported, the size is unspecified - return null; - } - if (opsym.getIndex() != htpl.getOffsetOperandIndex()) { - // If the export is not of the same operand, it does not specify its size - return null; - } - return htpl; - } - - /** - * Build a portion of the grammar representing a table of constructors - * - * @param subtable the table - * @return the partial grammar - */ - protected AssemblyGrammar buildSubGrammar(SubtableSymbol subtable) { - final AssemblyGrammar subgrammar = new AssemblyGrammar(); - final AssemblyNonTerminal lhs = new AssemblyNonTerminal(subtable.getName()); - SleighLanguages.traverseConstructors(subtable, new SubtableEntryVisitor() { - @Override - public int visit(DisjointPattern pattern, Constructor cons) { - AssemblySentential rhs = new AssemblySentential<>(); - List indices = new ArrayList<>(); - for (String str : cons.getPrintPieces()) { - if (str.length() != 0) { - if (str.charAt(0) == '\n') { - int index = str.charAt(1) - 'A'; - OperandSymbol opsym = cons.getOperand(index); - AssemblySymbol sym = getSymbolFor(cons, opsym); - if (sym.takesOperandIndex()) { - indices.add(index); - } - rhs.addSymbol(sym); - } - else { - rhs.addSeparators(str); - } - } - } - subgrammar.addProduction(lhs, rhs, pattern, cons, indices); - return CONTINUE; - } - }); - return subgrammar; - } - - /** - * Build the full grammar for the language - */ - protected void buildGrammar() { - try (DbgCtx dc = dbg.start("Building grammar")) { - grammar = new AssemblyGrammar(); - for (Symbol sym : lang.getSymbolTable().getSymbolList()) { - if (sym instanceof SubtableSymbol) { - SubtableSymbol subtable = (SubtableSymbol) sym; - grammar.combine(buildSubGrammar(subtable)); - } - else if (sym instanceof VarnodeSymbol) { - // Ignore. This just becomes a string terminal - } - else if (sym instanceof StartSymbol) { - // Ignore. We handle inst_start in semantic processing - } - else if (sym instanceof EndSymbol) { - // Ignore. We handle inst_next in semantic processing - } - - else if (sym instanceof Next2Symbol) { - // Ignore. We handle inst_next2 in semantic processing - } - else if (sym instanceof UseropSymbol) { - // Ignore. We don't do pcode. - } - else if (sym instanceof OperandSymbol) { - // Ignore. These are terminals, or will be produced by their defining symbols - } - else if (sym instanceof ValueSymbol) { - // Ignore. These are now terminals - } - else { - throw new RuntimeException("Unexpected type: " + sym.getClass()); - } - } - grammar.setStartName("instruction"); - } - } - - /** - * Build the default context for the language - */ - protected void buildContext() { - defaultContext = new AssemblyDefaultContext(lang); - } - - /** - * Build the context transition graph for the language - */ - protected void buildContextGraph() { - try (DbgCtx dc = dbg.start("Building context graph")) { - ctxGraph = new AssemblyContextGraph(lang, grammar); - } - } - - /** - * Build the parser for the language - */ - protected void buildParser() { - try (DbgCtx dc = dbg.start("Building parser")) { - parser = new AssemblyParser(grammar); - } - } - - /** - * Get the built grammar for the language - * - * @return the grammar - */ - protected AssemblyGrammar getGrammar() { - return grammar; - } - - /** - * Get the built parser for the language - * - * @return the parser - */ - protected AssemblyParser getParser() { - return parser; + return (SleighAssembler) super.getAssembler(selector, program); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractBinaryExpressionSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractBinaryExpressionSolver.java index 2a4222597c..18a9efc1b3 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractBinaryExpressionSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractBinaryExpressionSolver.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr; import java.util.Map; import java.util.Set; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; +import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.processors.sleigh.expression.BinaryExpression; import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; @@ -36,9 +35,9 @@ public abstract class AbstractBinaryExpressionSolver } @Override - public AssemblyResolution solve(T exp, MaskedLong goal, Map vals, - AssemblyResolvedPatterns cur, Set hints, String description) - throws NeedsBackfillException { + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + T exp, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set hints, String description) throws NeedsBackfillException { MaskedLong lval = solver.getValue(exp.getLeft(), vals, cur); MaskedLong rval = solver.getValue(exp.getRight(), vals, cur); @@ -58,26 +57,27 @@ public abstract class AbstractBinaryExpressionSolver try { if (lval != null && rval != null) { MaskedLong cval = compute(lval, rval); - return ConstantValueSolver.checkConstAgrees(cval, goal, description); + return ConstantValueSolver.checkConstAgrees(factory, cval, goal, description); } else if (lval != null) { - return solveRightSide(exp.getRight(), lval, goal, vals, cur, hints, + return solveRightSide(factory, exp.getRight(), lval, goal, vals, cur, hints, description); } else if (rval != null) { - return solveLeftSide(exp.getLeft(), rval, goal, vals, cur, hints, description); + return solveLeftSide(factory, 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, cur, hints, description); + return solveTwoSided(factory, exp, goal, vals, cur, hints, description); } } catch (NeedsBackfillException e) { throw e; } catch (SolverException e) { - return AssemblyResolution.error(e.getMessage(), description); + return factory.newErrorBuilder().error(e.getMessage()).description(description).build(); } catch (AssertionError e) { dbg.println("While solving: " + exp + " (" + description + ")"); @@ -85,23 +85,25 @@ public abstract class AbstractBinaryExpressionSolver } } - protected AssemblyResolution solveLeftSide(PatternExpression lexp, MaskedLong rval, - MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, - Set hints, String description) - throws NeedsBackfillException, SolverException { - return solver.solve(lexp, computeLeft(rval, goal), vals, cur, hints, description); - } - - protected AssemblyResolution solveRightSide(PatternExpression rexp, MaskedLong lval, - MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, - Set hints, String description) - throws NeedsBackfillException, SolverException { - return solver.solve(rexp, computeRight(lval, goal), vals, cur, hints, description); - } - - protected AssemblyResolution solveTwoSided(T exp, MaskedLong goal, Map vals, + protected AssemblyResolution solveLeftSide(AbstractAssemblyResolutionFactory factory, + PatternExpression lexp, MaskedLong rval, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, Set hints, String description) throws NeedsBackfillException, SolverException { + return solver.solve(factory, lexp, computeLeft(rval, goal), vals, cur, hints, description); + } + + protected AssemblyResolution solveRightSide(AbstractAssemblyResolutionFactory factory, + PatternExpression rexp, MaskedLong lval, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set hints, String description) + throws NeedsBackfillException, SolverException { + return solver.solve(factory, rexp, computeRight(lval, goal), vals, cur, hints, + description); + } + + protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory factory, + T exp, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set hints, String description) + throws NeedsBackfillException, SolverException { throw new NeedsBackfillException("_two_sided_"); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractExpressionSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractExpressionSolver.java index d2ba1886ab..a5cde4aab2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractExpressionSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractExpressionSolver.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr; import java.util.Map; import java.util.Set; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; +import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; @@ -54,9 +53,9 @@ public abstract class AbstractExpressionSolver { * @return the resolution * @throws NeedsBackfillException if the expression refers to an undefined symbol */ - public abstract AssemblyResolution solve(T exp, MaskedLong goal, Map vals, - AssemblyResolvedPatterns cur, Set hints, - String description) throws NeedsBackfillException; + public abstract AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + T exp, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set hints, String description) throws NeedsBackfillException; /** * Attempt to get a constant value for the expression diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractUnaryExpressionSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractUnaryExpressionSolver.java index 9d68e92bdf..20c2879771 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractUnaryExpressionSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/AbstractUnaryExpressionSolver.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr; import java.util.Map; import java.util.Set; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; +import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.processors.sleigh.expression.UnaryExpression; /** @@ -35,18 +34,18 @@ public abstract class AbstractUnaryExpressionSolver } @Override - public AssemblyResolution solve(T exp, MaskedLong goal, Map vals, - AssemblyResolvedPatterns cur, Set hints, String description) - throws NeedsBackfillException { + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, T exp, + MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set hints, String description) throws NeedsBackfillException { MaskedLong uval = solver.getValue(exp.getUnary(), vals, cur); try { if (uval != null && uval.isFullyDefined()) { MaskedLong cval = compute(uval); if (cval != null) { - return ConstantValueSolver.checkConstAgrees(cval, goal, description); + return ConstantValueSolver.checkConstAgrees(factory, cval, goal, description); } } - return solver.solve(exp.getUnary(), computeInverse(goal), vals, cur, hints, + return solver.solve(factory, exp.getUnary(), computeInverse(goal), vals, cur, hints, description); } /* diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/ConstantValueSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/ConstantValueSolver.java index 7bfff47f42..b8322b7e92 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/ConstantValueSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/ConstantValueSolver.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr; import java.util.Map; import java.util.Set; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; +import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.processors.sleigh.expression.ConstantValue; /** @@ -36,11 +35,11 @@ public class ConstantValueSolver extends AbstractExpressionSolver } @Override - public AssemblyResolution solve(ConstantValue cv, MaskedLong goal, Map vals, - AssemblyResolvedPatterns cur, Set hints, - String description) { + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + ConstantValue cv, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set hints, String description) { MaskedLong value = getValue(cv, vals, cur); - return checkConstAgrees(value, goal, description); + return checkConstAgrees(factory, value, goal, description); } @Override @@ -60,12 +59,15 @@ public class ConstantValueSolver extends AbstractExpressionSolver return MaskedLong.fromLong(cv.getValue()); } - static AssemblyResolution checkConstAgrees(MaskedLong value, MaskedLong goal, + static AssemblyResolution checkConstAgrees( + AbstractAssemblyResolutionFactory factory, MaskedLong value, MaskedLong goal, String description) { if (!value.agrees(goal)) { - return AssemblyResolution.error( - "Constant value " + value + " does not agree with child requirements", description); + return factory.newErrorBuilder() + .error("Constant value " + value + " does not agree with child requirements") + .description(description) + .build(); } - return AssemblyResolution.nop(description, null, null); + return factory.nop(description); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/ContextFieldSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/ContextFieldSolver.java index a232dd2677..3d02907658 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/ContextFieldSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/ContextFieldSolver.java @@ -35,15 +35,18 @@ public class ContextFieldSolver extends AbstractExpressionSolver { } @Override - public AssemblyResolution solve(ContextField cf, MaskedLong goal, Map vals, - AssemblyResolvedPatterns cur, Set hints, String description) { + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + ContextField cf, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set 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); + return factory.newErrorBuilder() + .error("Value " + goal + " is not valid for " + cf) + .description(description) + .build(); } AssemblyPatternBlock block = AssemblyPatternBlock.fromContextField(cf, goal); - return AssemblyResolution.contextOnly(block, description); + return factory.contextOnly(block, description); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/EndInstructionValueSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/EndInstructionValueSolver.java index f9211afa75..96e9a77837 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/EndInstructionValueSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/EndInstructionValueSolver.java @@ -39,12 +39,14 @@ public class EndInstructionValueSolver extends AbstractExpressionSolver vals, - AssemblyResolvedPatterns cur, Set hints, String description) { - throw new AssertionError( - "INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_NEXT); + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + EndInstructionValue exp, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set hints, String description) + throws NeedsBackfillException { + throw new AssertionError("INTERNAL: Should never be asked to solve for " + + AbstractAssemblyTreeResolver.INST_NEXT); /** - * I suppose we could instead throw NeedsBackfillExcpeiton(INST_NEXT) here, too, but this + * I suppose we could instead throw NeedsBackfillException(INST_NEXT) here, too, but this * serves as a sanity check on the SLEIGH spec. I can't think of a good reason to try to * solve INST_NEXT == const. */ @@ -53,9 +55,9 @@ public class EndInstructionValueSolver extends AbstractExpressionSolver vals, AssemblyResolvedPatterns cur) throws NeedsBackfillException { - Long instNext = vals.get(AssemblyTreeResolver.INST_NEXT); + Long instNext = vals.get(AbstractAssemblyTreeResolver.INST_NEXT); if (instNext == null) { - throw new NeedsBackfillException(AssemblyTreeResolver.INST_NEXT); + throw new NeedsBackfillException(AbstractAssemblyTreeResolver.INST_NEXT); } return MaskedLong.fromLong(instNext); } @@ -68,7 +70,7 @@ public class EndInstructionValueSolver extends AbstractExpressionSolver vals, AssemblyResolvedPatterns rc) { - Long instNext = vals.get(AssemblyTreeResolver.INST_NEXT); + Long instNext = vals.get(AbstractAssemblyTreeResolver.INST_NEXT); if (instNext == null) { /** * This method is used in forward state construction, so just leave unknown. This may diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/LeftShiftExpressionSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/LeftShiftExpressionSolver.java index 2092fed3c0..c0131bb63a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/LeftShiftExpressionSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/LeftShiftExpressionSolver.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr; import java.util.Map; import java.util.Set; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; +import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.processors.sleigh.expression.LeftShiftExpression; import ghidra.util.Msg; @@ -60,13 +59,14 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver vals, AssemblyResolvedPatterns cur, Set hints, - String description) throws NeedsBackfillException, SolverException { + protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory factory, + LeftShiftExpression exp, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set 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, cur, hints, description); + return super.solveTwoSided(factory, 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 @@ -79,8 +79,8 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver vals, AssemblyResolvedPatterns cur, - Set hints, String description) throws NeedsBackfillException { + protected AssemblyResolution tryRep(AbstractAssemblyResolutionFactory factory, + PatternExpression lexp, MaskedLong rval, MaskedLong repGoal, MaskedLong goal, + Map vals, AssemblyResolvedPatterns cur, Set hints, + String description) throws NeedsBackfillException { MaskedLong lval = repGoal.divideUnsigned(rval); if (lval.multiply(rval).agrees(goal)) { - return solver.solve(lexp, lval, vals, cur, hints, description); + return solver.solve(factory, lexp, lval, vals, cur, hints, description); } return null; } @Override - protected AssemblyResolution solveLeftSide(PatternExpression lexp, MaskedLong rval, - MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, - Set hints, String description) + protected AssemblyResolution solveLeftSide(AbstractAssemblyResolutionFactory factory, + PatternExpression lexp, MaskedLong rval, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set 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, cur, hints, description); + return super.solveLeftSide(factory, lexp, rval, goal, vals, cur, hints, description); }); if (sol != null) { return sol; @@ -150,7 +150,8 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver 0) { MaskedLong repRightGoal = MaskedLong.fromMaskAndValue(repMsk, repVal); sol = tracker.trySolverFunc(() -> { - return tryRep(lexp, rval, repRightGoal, goal, vals, cur, hintsWithRepetition, + return tryRep(factory, lexp, rval, repRightGoal, goal, vals, cur, + hintsWithRepetition, description); }); if (sol != null) { @@ -168,8 +169,8 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver>> i; MaskedLong repLeftGoal = MaskedLong.fromMaskAndValue(repMsk, repVal); sol = tracker.trySolverFunc(() -> { - return tryRep(lexp, rval, repLeftGoal, goal, vals, cur, hintsWithRepetition, - description); + return tryRep(factory, lexp, rval, repLeftGoal, goal, vals, cur, + hintsWithRepetition, description); }); if (sol != null) { return sol; @@ -180,11 +181,11 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver vals, AssemblyResolvedPatterns cur, - Set hints, String description) + protected AssemblyResolution solveRightSide(AbstractAssemblyResolutionFactory factory, + PatternExpression rexp, MaskedLong lval, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set hints, String description) throws NeedsBackfillException, SolverException { - return solveLeftSide(rexp, lval, goal, vals, cur, hints, description); + return solveLeftSide(factory, rexp, lval, goal, vals, cur, hints, description); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/Next2InstructionValueSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/Next2InstructionValueSolver.java index 1ada36e056..7219fe8c3e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/Next2InstructionValueSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/Next2InstructionValueSolver.java @@ -39,9 +39,10 @@ public class Next2InstructionValueSolver extends AbstractExpressionSolver vals, - AssemblyResolvedPatterns cur, Set hints, String description) { + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + Next2InstructionValue exp, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set hints, String description) + throws NeedsBackfillException { throw new AssertionError( "INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_NEXT2); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/OperandValueSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/OperandValueSolver.java index 6e6345a7f3..414224088c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/OperandValueSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/OperandValueSolver.java @@ -41,7 +41,7 @@ public class OperandValueSolver extends AbstractExpressionSolver { * Obtains the "defining expression" * *

- * This is either the symbols assigned defining expression, or the expression associated with + * This is either the symbol's assigned defining expression, or the expression associated with * its defining symbol. * * @return the defining expression, or null if neither is available @@ -60,25 +60,31 @@ public class OperandValueSolver extends AbstractExpressionSolver { } @Override - public AssemblyResolution solve(OperandValue ov, MaskedLong goal, Map vals, - AssemblyResolvedPatterns cur, Set hints, String description) - throws NeedsBackfillException { + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + OperandValue ov, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set 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, null); + return factory.nop(description); } - return AssemblyResolution.error("Operand " + sym.getName() + - " is undefined and does not agree with child requirements", description); + return factory.newErrorBuilder() + .error("Operand " + sym.getName() + + " is undefined and does not agree with child requirements") + .description(description) + .build(); } - AssemblyResolution result = solver.solve(patexp, goal, vals, cur, hints, description); + AssemblyResolution result = + solver.solve(factory, 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), null); + return factory.newErrorBuilder() + .error(err.getError()) + .description("Solution to " + sym.getName() + " := " + goal + " = " + patexp) + .children(List.of(result)) + .build(); } // TODO: Shifting here seems like a hack to me. // I assume this only comes at the top of an expression diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/OrExpressionSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/OrExpressionSolver.java index 84955d3596..3832a08eb9 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/OrExpressionSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/OrExpressionSolver.java @@ -53,7 +53,8 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver factory, OrExpression exp, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, Set hints, String description) throws SolverException { /* @@ -68,7 +69,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver factory, OrExpression exp, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, Set hints, String description) throws SolverException { // If OR is being used to accomplish a circular shift, then we can apply a clever solver. @@ -166,11 +168,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver factory, PatternExpression expValue, PatternExpression expShift, int size, int dir, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, Set hints, String description) throws NeedsBackfillException, SolverException { @@ -194,12 +197,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver vals, AssemblyResolvedPatterns cur, Set hints, - String description) throws NeedsBackfillException, SolverException { + protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory factory, + OrExpression exp, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set hints, String description) + throws NeedsBackfillException, SolverException { try { - return tryCatenationExpression(exp, goal, vals, cur, hints, description); + return tryCatenationExpression(factory, exp, goal, vals, cur, hints, description); } catch (Exception e) { dbg.println("while solving: " + goal + "=:" + exp); @@ -279,7 +283,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver vals, AssemblyResolvedPatterns cur, Set hints, - String description) throws NeedsBackfillException { + protected AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + PatternExpression exp, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set hints, String description) + throws NeedsBackfillException { try { - return getRegistered(exp.getClass()).solve(exp, goal, vals, cur, hints, description); + return getRegistered(exp.getClass()).solve(factory, exp, goal, vals, cur, hints, + description); } catch (UnsupportedOperationException e) { DBG.println("Error solving " + exp + " = " + goal); @@ -152,10 +153,10 @@ public class RecursiveDescentSolver { * @return the encoded solution * @throws NeedsBackfillException a solution may exist, but a required symbol is missing */ - public AssemblyResolution solve(PatternExpression exp, MaskedLong goal, Map vals, - AssemblyResolvedPatterns cur, String description) - throws NeedsBackfillException { - return solve(exp, goal, vals, cur, Set.of(), description); + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + PatternExpression exp, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, String description) throws NeedsBackfillException { + return solve(factory, exp, goal, vals, cur, Set.of(), description); } /** diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/RightShiftExpressionSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/RightShiftExpressionSolver.java index 0da326e314..11883fad0a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/RightShiftExpressionSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/RightShiftExpressionSolver.java @@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr; import java.util.Map; import java.util.Set; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns; +import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.processors.sleigh.expression.RightShiftExpression; import ghidra.util.Msg; @@ -61,15 +60,16 @@ public class RightShiftExpressionSolver } @Override - protected AssemblyResolution solveTwoSided(RightShiftExpression exp, MaskedLong goal, - Map vals, AssemblyResolvedPatterns cur, Set hints, - String description) throws NeedsBackfillException, SolverException { + protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory factory, + RightShiftExpression exp, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set 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, cur, hints, description); + return super.solveTwoSided(factory, exp, goal, vals, cur, hints, description); } int maxShift = Long.numberOfLeadingZeros(goal.val); @@ -80,13 +80,13 @@ public class RightShiftExpressionSolver MaskedLong reqr = MaskedLong.fromLong(shift); MaskedLong reql = computeLeft(reqr, goal); - AssemblyResolution lres = - solver.solve(exp.getLeft(), reql, vals, cur, hintsWithRShift, description); + AssemblyResolution lres = solver.solve(factory, exp.getLeft(), reql, vals, cur, + hintsWithRShift, description); if (lres.isError()) { throw new SolverException("Solving left failed"); } AssemblyResolution rres = - solver.solve(exp.getRight(), reqr, vals, cur, hints, description); + solver.solve(factory, exp.getRight(), reqr, vals, cur, hints, description); if (rres.isError()) { throw new SolverException("Solving right failed"); } @@ -104,6 +104,6 @@ public class RightShiftExpressionSolver // try the next } } - return super.solveTwoSided(exp, goal, vals, cur, hints, description); + return super.solveTwoSided(factory, exp, goal, vals, cur, hints, description); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/StartInstructionValueSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/StartInstructionValueSolver.java index b21e98952a..5c62ffe2eb 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/StartInstructionValueSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/StartInstructionValueSolver.java @@ -35,9 +35,9 @@ public class StartInstructionValueSolver extends AbstractExpressionSolver vals, AssemblyResolvedPatterns cur, Set hints, - String description) { + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + StartInstructionValue iv, MaskedLong goal, Map vals, + AssemblyResolvedPatterns cur, Set hints, String description) { throw new AssertionError( "INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_START); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/TokenFieldSolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/TokenFieldSolver.java index 667a98a05d..7e2b655b03 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/TokenFieldSolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/expr/TokenFieldSolver.java @@ -35,15 +35,18 @@ public class TokenFieldSolver extends AbstractExpressionSolver { } @Override - public AssemblyResolution solve(TokenField tf, MaskedLong goal, Map vals, - AssemblyResolvedPatterns cur, Set hints, String description) { + public AssemblyResolution solve(AbstractAssemblyResolutionFactory factory, + TokenField tf, MaskedLong goal, Map vals, AssemblyResolvedPatterns cur, + Set 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); + return factory.newErrorBuilder() + .error("Value " + goal + " is not valid for " + tf) + .description(description) + .build(); } AssemblyPatternBlock block = AssemblyPatternBlock.fromTokenField(tf, goal); - return AssemblyResolution.instrOnly(block, description); + return factory.instrOnly(block, description); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblyGrammar.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblyGrammar.java index 19bbe3b76f..df22448884 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblyGrammar.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblyGrammar.java @@ -17,6 +17,7 @@ package ghidra.app.plugin.assembler.sleigh.grammars; import java.util.*; +import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyConstructorSemantic; import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal; import ghidra.app.plugin.processors.sleigh.Constructor; @@ -33,14 +34,19 @@ import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern; */ public class AssemblyGrammar extends AbstractAssemblyGrammar { + protected final AbstractAssemblyResolutionFactory factory; // a nested map of semantics by production, by constructor - protected final Map> semanticsByProduction = - new TreeMap<>(); + protected final Map> semanticsByProduction = new TreeMap<>(); protected final Map semanticsByConstructor = new HashMap<>(); // a map of purely recursive, e.g., I => I, productions by name of LHS protected final Map pureRecursive = new TreeMap<>(); + public AssemblyGrammar(AbstractAssemblyResolutionFactory factory) { + this.factory = factory; + } + @Override protected AssemblyProduction newProduction(AssemblyNonTerminal lhs, AssemblySentential rhs) { @@ -73,7 +79,7 @@ public class AssemblyGrammar Map map = semanticsByProduction.computeIfAbsent(prod, p -> new TreeMap<>()); AssemblyConstructorSemantic sem = - map.computeIfAbsent(cons, c -> new AssemblyConstructorSemantic(cons, indices)); + map.computeIfAbsent(cons, c -> new AssemblyConstructorSemantic(factory, cons, indices)); if (!indices.equals(sem.getOperandIndices())) { throw new IllegalStateException( "Productions of the same constructor must have same operand indices"); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblyProduction.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblyProduction.java index 71b7e23892..071824fa8c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblyProduction.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblyProduction.java @@ -28,4 +28,8 @@ public class AssemblyProduction extends AbstractAssemblyProduction rhs) { super(lhs, rhs); } + + public boolean isConstructor() { + return true; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblySentential.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblySentential.java index 4a00aaef25..4acc5bf933 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblySentential.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/grammars/AssemblySentential.java @@ -127,7 +127,7 @@ public class AssemblySentential */ private static class WhiteSpace extends AssemblyStringTerminal { private WhiteSpace() { - super(" "); + super(" ", null); } @Override @@ -166,6 +166,11 @@ public class AssemblySentential public Collection getSuggestions(String got, AssemblyNumericSymbols symbols) { return Collections.singleton(" "); } + + @Override + public boolean isWhiteSpace() { + return true; + } } /** @@ -217,7 +222,7 @@ public class AssemblySentential * Add a comma followed by optional whitespace. */ public void addCommaWS() { - addSymbol(new AssemblyStringTerminal(",")); + addSymbol(new AssemblyStringTerminal(",", null)); addWS(); } @@ -240,7 +245,7 @@ public class AssemblySentential if (!Character.isLetterOrDigit(first)) { addWS(); } - addSymbol(new AssemblyStringTerminal(tstr)); + addSymbol(new AssemblyStringTerminal(tstr, null)); char last = tstr.charAt(tstr.length() - 1); if (!str.endsWith(tstr)) { addWS(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyResolution.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyResolution.java new file mode 100644 index 0000000000..c047e4dcb8 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyResolution.java @@ -0,0 +1,170 @@ +/* ### + * 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.sem; + +import java.util.*; + +/** + * The (often intermediate) result of assembly + * + *

+ * These may represent a successful construction ({@link AssemblyResolvedPatterns}, a future field + * ({@link AssemblyResolvedBackfill}), or an error ({@link AssemblyResolvedError}). + * + *

+ * This class also provides the static factory methods for constructing any of its subclasses. + */ +public abstract class AbstractAssemblyResolution implements AssemblyResolution { + protected final AbstractAssemblyResolutionFactory factory; + protected final String description; + protected final List children; + protected final AssemblyResolution right; + + private boolean hashed = false; + private int hash; + + /** + * Construct a resolution + * + * @param description a textual description used as part of {@link #toString()} + * @param children for record keeping, any children used in constructing this resolution + */ + protected AbstractAssemblyResolution(AbstractAssemblyResolutionFactory factory, + String description, List children, + AssemblyResolution right) { + this.factory = factory; + this.description = description; + this.children = children == null ? List.of() : Collections.unmodifiableList(children); + this.right = right; + } + + @Override + public int hashCode() { + if (!hashed) { + hash = computeHash(); + hashed = true; + } + return hash; + } + + protected abstract int computeHash(); + + /* ******************************************************************************************** + * Misc + */ + + protected List getAllRight() { + List result = new ArrayList<>(); + collectAllRight(result); + return result; + } + + @Override + public void collectAllRight(Collection into) { + into.add(this); + if (right == null) { + return; + } + right.collectAllRight(into); + } + + /** + * Get the child portion of {@link #toString()} + * + *

+ * If a subclass has another, possible additional, notion of children that it would like to + * include in {@link #toString()}, it must override this method. + * + * @see #hasChildren() + * @param indent the current indentation + * @return the indented description for each child on its own line + */ + protected String childrenToString(String indent) { + StringBuilder sb = new StringBuilder(); + for (AssemblyResolution child : children) { + sb.append(child.toString(indent) + "\n"); + } + return sb.substring(0, sb.length() - 1); + } + + @Override + public String toString(String indent) { + StringBuilder sb = new StringBuilder(); + sb.append(indent); + sb.append(lineToString()); + if (hasChildren()) { + sb.append(":\n"); + String newIndent = indent + " "; + sb.append(childrenToString(newIndent)); + } + return sb.toString(); + } + + @Override + public String toString() { + return toString(""); + } + + @Override + public int compareTo(AssemblyResolution that) { + return this.toString().compareTo(that.toString()); // LAZY + } + + @Override + public boolean hasChildren() { + if (children == null) { + return false; + } + if (children.size() == 0) { + return false; + } + return true; + } + + @Override + public abstract AssemblyResolution shift(int amt); + + /** + * Get this same resolution, but without any right siblings + * + * @return the resolution + */ + public AssemblyResolution withoutRight() { + return withRight(null); + } + + /** + * Get this same resolution, but with the given right sibling + * + * @return the resolution + */ + public abstract AssemblyResolution withRight(AssemblyResolution right); + + @Override + public String getDescription() { + return description; + } + + @Override + public List getChildren() { + return children; + } + + @Override + public AssemblyResolution getRight() { + return right; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyResolutionFactory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyResolutionFactory.java new file mode 100644 index 0000000000..48fdeffc94 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyResolutionFactory.java @@ -0,0 +1,442 @@ +/* ### + * 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.sem; + +import java.util.*; +import java.util.concurrent.atomic.AtomicLong; + +import ghidra.app.plugin.assembler.sleigh.expr.*; +import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolvedBackfillBuilder; +import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolvedPatternsBuilder; +import ghidra.app.plugin.processors.sleigh.Constructor; +import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; +import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern; + +public abstract class AbstractAssemblyResolutionFactory< // + RP extends AssemblyResolvedPatterns, // + BF extends AssemblyResolvedBackfill> { + protected static final RecursiveDescentSolver SOLVER = RecursiveDescentSolver.getSolver(); + protected static final String INS = "ins:"; + protected static final String CTX = "ctx:"; + protected static final String SEP = ","; + + public abstract static class AbstractAssemblyResolutionBuilder< // + B extends AbstractAssemblyResolutionBuilder, // + T extends AssemblyResolution> { + protected String description; + protected List children; + protected AssemblyResolution right; + + public void copyFromDefault(AbstractAssemblyResolution ar) { + this.description = ar.description; + this.children = ar.children; + this.right = ar.right; + } + + @SuppressWarnings("unchecked") + protected B self() { + return (B) this; + } + + public B description(String description) { + this.description = description; + return self(); + } + + public B children(List children) { + this.children = children; + return self(); + } + + public B right(AssemblyResolution right) { + this.right = right; + return self(); + } + + protected abstract T build(); + } + + public abstract static class AbstractAssemblyResolvedPatternsBuilder< // + RP extends AssemblyResolvedPatterns> extends + AbstractAssemblyResolutionBuilder, RP> { + protected Constructor cons; + protected AssemblyPatternBlock ins; + protected AssemblyPatternBlock ctx; + protected Set backfills; + protected Set forbids; + + public void copyFromDefault(DefaultAssemblyResolvedPatterns rp) { + super.copyFromDefault(rp); + this.cons = rp.cons; + this.ins = rp.ins; + this.ctx = rp.ctx; + this.backfills = rp.backfills; + this.forbids = rp.forbids; + } + } + + public abstract static class AbstractAssemblyResolvedBackfillBuilder< // + BF extends AssemblyResolvedBackfill> extends + AbstractAssemblyResolutionBuilder, BF> { + protected PatternExpression exp; + protected MaskedLong goal; + protected int inslen; + protected int offset; + } + + public class DefaultAssemblyResolvedPatternBuilder + extends AbstractAssemblyResolvedPatternsBuilder { + @Override + protected AssemblyResolvedPatterns build() { + return new DefaultAssemblyResolvedPatterns(AbstractAssemblyResolutionFactory.this, + description, cons, children, right, ins, ctx, backfills, forbids); + } + } + + public class DefaultAssemblyResolvedBackfillBuilder + extends AbstractAssemblyResolvedBackfillBuilder { + @Override + protected AssemblyResolvedBackfill build() { + return new DefaultAssemblyResolvedBackfill(AbstractAssemblyResolutionFactory.this, + description, exp, goal, inslen, offset); + } + } + + public class AssemblyResolvedErrorBuilder extends + AbstractAssemblyResolutionBuilder { + protected String error; + + public AssemblyResolvedErrorBuilder error(String error) { + this.error = error; + return self(); + } + + @Override + public AssemblyResolvedError build() { + return new DefaultAssemblyResolvedError(AbstractAssemblyResolutionFactory.this, + description, children, right, error); + } + } + + public abstract AbstractAssemblyResolvedPatternsBuilder newPatternsBuilder(); + + public abstract AbstractAssemblyResolvedBackfillBuilder newBackfillBuilder(); + + public AssemblyResolvedErrorBuilder newErrorBuilder() { + return new AssemblyResolvedErrorBuilder(); + } + + /** + * Construct an immutable single-entry result set consisting of the one given resolution + * + * @param rp the single resolution entry + * @return the new resolution set + */ + protected AssemblyResolutionResults singleton(AssemblyResolution one) { + return results(Set.of(one)); + } + + public AssemblyResolutionResults newAssemblyResolutionResults() { + return new AssemblyResolutionResults(); + } + + protected AssemblyResolutionResults results(Set col) { + return new AssemblyResolutionResults(col); + } + + /** + * Attempt to solve an expression + * + * @param exp the expression to solve + * @param goal the desired value of the expression + * @param cur the resolved constructor so far + * @param description a description of the result + * @return the encoded solution, or a backfill record + */ + protected AssemblyResolution solveOrBackfill(PatternExpression exp, MaskedLong goal, + Map vals, AssemblyResolvedPatterns cur, String description) { + try { + return SOLVER.solve(this, exp, goal, vals, cur, description); + } + catch (NeedsBackfillException bf) { + int fieldLength = SOLVER.getInstructionLength(exp); + return backfill(exp, goal, fieldLength, description); + } + } + + /** + * Attempt to solve an expression + * + *

+ * Converts the given goal and bits count to a {@link MaskedLong} and then solves as before. As + * a special case, if {@code bits == 0}, the goal is considered fully-defined (as if + * {@code bits == 64}). + * + * @see #solveOrBackfill(PatternExpression, MaskedLong, AssemblyResolvedPatterns, String) + */ + protected AssemblyResolution solveOrBackfill(PatternExpression exp, long goal, int bits, + Map vals, AssemblyResolvedPatterns cur, String description) { + long msk; + if (bits == 0 || bits >= 64) { + msk = -1L; + } + else { + msk = ~(-1L << bits); + } + return solveOrBackfill(exp, MaskedLong.fromMaskAndValue(msk, goal), vals, cur, description); + } + + /** + * Attempt to solve an expression + * + *

+ * Converts the given goal to a fully-defined {@link MaskedLong} and then solves. + * + * @see #solveOrBackfill(PatternExpression, MaskedLong, Map, AssemblyResolvedPatterns, String) + */ + protected AssemblyResolution solveOrBackfill(PatternExpression exp, long goal, + Map vals, AssemblyResolvedPatterns cur, String description) { + return solveOrBackfill(exp, MaskedLong.fromLong(goal), vals, cur, description); + } + + public AssemblyResolvedErrorBuilder errorBuilder(String error, AssemblyResolution res) { + var builder = newErrorBuilder(); + builder.error = error; + builder.description = res.getDescription(); + builder.children = res.getChildren(); + builder.right = res.getRight(); + return builder; + } + + /** + * Build an error resolution record, based on an intermediate SLEIGH constructor record + * + * @param error a description of the error + * @param res the constructor record that was being populated when the error occurred + * @return the new error resolution + */ + public AssemblyResolution error(String error, AssemblyResolution res) { + return errorBuilder(error, res).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder nopBuilder(String description) { + var builder = newPatternsBuilder(); + builder.ins = AssemblyPatternBlock.nop(); + builder.ctx = AssemblyPatternBlock.nop(); + builder.description = description; + return builder; + } + + /** + * Obtain a new "blank" resolved SLEIGH constructor record + * + * @param description a description of the resolution + * @return the new resolution + */ + public RP nop(String description) { + return nopBuilder(description).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder nopBuilder(String description, + List children, AssemblyResolution right) { + var builder = newPatternsBuilder(); + builder.ins = AssemblyPatternBlock.nop(); + builder.ctx = AssemblyPatternBlock.nop(); + builder.description = description; + builder.children = children; + builder.right = right; + return builder; + } + + /** + * Obtain a new "blank" resolved SLEIGH constructor record + * + * @param description a description of the resolution + * @param children any children that will be involved in populating this record + * @return the new resolution + */ + public RP nop(String description, List children, AssemblyResolution right) { + return nopBuilder(description, children, right).build(); + } + + public AbstractAssemblyResolvedBackfillBuilder backfillBuilder(PatternExpression exp, + MaskedLong goal, int inslen, String description) { + var builder = newBackfillBuilder(); + builder.exp = exp; + builder.goal = goal; + builder.inslen = inslen; + builder.description = description; + return builder; + } + + /** + * Build a backfill record to attach to a successful resolution result + * + * @param exp the expression depending on a missing symbol + * @param goal the desired value of the expression + * @param inslen the length of instruction portion expected in the future solution + * @param description a description of the backfill record + * @return the new record + */ + public AssemblyResolution backfill(PatternExpression exp, MaskedLong goal, int inslen, + String description) { + return backfillBuilder(exp, goal, inslen, description).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder resolvedBuilder(AssemblyPatternBlock ins, + AssemblyPatternBlock ctx, String description, Constructor cons, + List children, AssemblyResolution right) { + var builder = newPatternsBuilder(); + builder.ins = ins; + builder.ctx = ctx; + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + return builder; + } + + /** + * Build the result of successfully resolving a SLEIGH constructor + * + *

+ * NOTE: This is not used strictly for resolved SLEIGH constructors. It may also be used + * to store intermediates, e.g., encoded operands, during constructor resolution. + * + * @param ins the instruction pattern block + * @param ctx the context pattern block + * @param description a description of the resolution + * @param cons the constructor, or null + * @param children the children of this constructor, or null + * @return the new resolution + */ + public RP resolved(AssemblyPatternBlock ins, AssemblyPatternBlock ctx, String description, + Constructor cons, List children, AssemblyResolution right) { + return resolvedBuilder(ins, ctx, description, cons, children, right).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder instrOnlyBuilder(AssemblyPatternBlock ins, + String description) { + var builder = newPatternsBuilder(); + builder.ins = ins; + builder.ctx = AssemblyPatternBlock.nop(); + builder.description = description; + return builder; + } + + /** + * Build an instruction-only successful resolution result + * + * @param ins the instruction pattern block + * @param description a description of the resolution + * @return the new resolution + * @see #resolved(AssemblyPatternBlock, AssemblyPatternBlock, String, Constructor, List, + * AssemblyResolution) + */ + public RP instrOnly(AssemblyPatternBlock ins, String description) { + return instrOnlyBuilder(ins, description).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder contextOnlyBuilder( + AssemblyPatternBlock ctx, String description) { + var builder = newPatternsBuilder(); + builder.ins = AssemblyPatternBlock.nop(); + builder.ctx = ctx; + builder.description = description; + return builder; + } + + /** + * Build a context-only successful resolution result + * + * @param ctx the context pattern block + * @param description a description of the resolution + * @return the new resolution + * @see #resolved(AssemblyPatternBlock, AssemblyPatternBlock, String, Constructor, List, + * AssemblyResolution) + */ + public RP contextOnly(AssemblyPatternBlock ctx, String description) { + return contextOnlyBuilder(ctx, description).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder fromPatternBuilder(DisjointPattern pat, + int minLen, String description, Constructor cons) { + var builder = newPatternsBuilder(); + builder.ins = AssemblyPatternBlock.fromPattern(pat, minLen, false); + builder.ctx = AssemblyPatternBlock.fromPattern(pat, 0, true); + builder.description = description; + builder.cons = cons; + return builder; + } + + /** + * Build a successful resolution result from a SLEIGH constructor's patterns + * + * @param pat the constructor's pattern + * @param description a description of the resolution + * @return the new resolution + */ + public RP fromPattern(DisjointPattern pat, int minLen, + String description, Constructor cons) { + return fromPatternBuilder(pat, minLen, description, cons).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder fromStringBuilder(String str, + String description, List children) { + var builder = newPatternsBuilder(); + builder.description = description; + builder.children = children; + if (str.startsWith(INS)) { + int end = str.indexOf(SEP); + if (end == -1) { + end = str.length(); + } + builder.ins = AssemblyPatternBlock.fromString(str.substring(INS.length(), end)); + str = str.substring(end); + if (str.startsWith(SEP)) { + str = str.substring(1); + } + } + if (str.startsWith(CTX)) { + int end = str.length(); + builder.ctx = AssemblyPatternBlock.fromString(str.substring(CTX.length(), end)); + str = str.substring(end); + } + if (str.length() != 0) { + throw new IllegalArgumentException(str); + } + return builder; + } + + /** + * Build a new successful SLEIGH constructor resolution from a string representation + * + *

+ * This was used primarily in testing, to specify expected results. + * + * @param str the string representation: "{@code ins:[pattern],ctx:[pattern]}" + * @see ghidra.util.NumericUtilities#convertHexStringToMaskedValue(AtomicLong, AtomicLong, + * String, int, int, String) NumericUtilities.convertHexStringToMaskedValue(AtomicLong, + * AtomicLong, String, int, int, String) + * @param description a description of the resolution + * @param children any children involved in the resolution + * @return the decoded resolution + */ + public AssemblyResolvedPatterns fromString(String str, String description, + List children) { + return fromStringBuilder(str, description, children).build(); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyState.java index 249786dfe4..896e55cd72 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyState.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyState.java @@ -25,14 +25,16 @@ import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; * Base for a node in an assembly prototype */ public abstract class AbstractAssemblyState { - protected static final DbgTimer DBG = AssemblyTreeResolver.DBG; + protected static final DbgTimer DBG = AbstractAssemblyTreeResolver.DBG; - protected final AssemblyTreeResolver resolver; + protected final AbstractAssemblyTreeResolver resolver; + protected final AbstractAssemblyResolutionFactory factory; protected final List path; protected final int shift; protected final int length; - protected final int hash; + protected volatile boolean hasHash = false; + protected volatile int hash; /** * Construct a node @@ -42,18 +44,21 @@ public abstract class AbstractAssemblyState { * @param shift the (right) shift in bytes for this operand * @param length the length of this operand */ - protected AbstractAssemblyState(AssemblyTreeResolver resolver, + protected AbstractAssemblyState(AbstractAssemblyTreeResolver resolver, List path, int shift, int length) { this.resolver = resolver; + this.factory = resolver.factory; this.path = path; this.shift = shift; this.length = length; - - this.hash = computeHash(); } @Override public int hashCode() { + if (!hasHash) { + this.hash = computeHash(); + this.hasHash = true; + } return hash; } @@ -77,6 +82,18 @@ public abstract class AbstractAssemblyState { protected abstract Stream resolve(AssemblyResolvedPatterns fromRight, Collection errors); + public AbstractAssemblyTreeResolver getResolver() { + return resolver; + } + + public List getPath() { + return path; + } + + public int getShift() { + return shift; + } + /** * Get the length in bytes of the operand represented by this node * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyStateGenerator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyStateGenerator.java index 6fc28bace6..740fec4655 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyStateGenerator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyStateGenerator.java @@ -29,7 +29,7 @@ import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; * @param the type of parse tree node to process */ public abstract class AbstractAssemblyStateGenerator { - protected static final DbgTimer DBG = AssemblyTreeResolver.DBG; + protected static final DbgTimer DBG = AbstractAssemblyTreeResolver.DBG; /** * Context to pass along as states are generated @@ -47,8 +47,8 @@ public abstract class AbstractAssemblyStateGenerator sem.getLocation()).collect(Collectors.joining(",")) + "]"; } - final List path; - final int shift; + public final List path; + public final int shift; /** * Construct a context @@ -84,7 +84,7 @@ public abstract class AbstractAssemblyStateGenerator resolver; protected final N node; protected final AssemblyResolvedPatterns fromLeft; @@ -95,7 +95,7 @@ public abstract class AbstractAssemblyStateGenerator resolver, N node, AssemblyResolvedPatterns fromLeft) { this.resolver = resolver; this.node = node; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyTreeResolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyTreeResolver.java new file mode 100644 index 0000000000..9893ff4aa1 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AbstractAssemblyTreeResolver.java @@ -0,0 +1,527 @@ +/* ### + * 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.sem; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import ghidra.app.plugin.assembler.sleigh.SleighAssemblerBuilder; +import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver; +import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar; +import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyProduction; +import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyStateGenerator.GeneratorContext; +import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolutionResults.Applicator; +import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal; +import ghidra.app.plugin.assembler.sleigh.tree.*; +import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; +import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx; +import ghidra.app.plugin.processors.sleigh.*; +import ghidra.app.plugin.processors.sleigh.symbol.*; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.InsufficientBytesException; +import ghidra.program.model.lang.UnknownInstructionException; +import ghidra.program.model.mem.ByteMemBufferImpl; +import ghidra.program.model.mem.MemBuffer; + +/** + * The workhorse of semantic resolution for the assembler + * + *

+ * This class takes a parse tree and some additional information (start address, context, etc.) and + * attempts to determine possible encodings using the semantics associated with each branch of the + * given parse tree. Details of this process are described in {@link SleighAssemblerBuilder}. + * + * @see SleighAssemblerBuilder + */ +public abstract class AbstractAssemblyTreeResolver { + protected static final RecursiveDescentSolver SOLVER = RecursiveDescentSolver.getSolver(); + protected static final DbgTimer DBG = DbgTimer.INACTIVE; + + public static final String INST_START = "inst_start"; + public static final String INST_NEXT = "inst_next"; + public static final String INST_NEXT2 = "inst_next2"; + + protected final AbstractAssemblyResolutionFactory factory; + protected final SleighLanguage lang; + protected final Address at; + protected final Map vals = new HashMap<>(); + protected final AssemblyParseBranch tree; + protected final AssemblyGrammar grammar; + protected final AssemblyPatternBlock context; + protected final AssemblyContextGraph ctxGraph; + + /** + * Construct a resolver for the given parse tree + * + * @param lang + * @param at the address where the instruction will start + * @param tree the parse tree + * @param context the context expected at {@code instStart} + * @param ctxGraph the context transition graph used to resolve purely-recursive productions + */ + public AbstractAssemblyTreeResolver(AbstractAssemblyResolutionFactory factory, + SleighLanguage lang, Address at, AssemblyParseBranch tree, AssemblyPatternBlock context, + AssemblyContextGraph ctxGraph) { + this.factory = factory; + this.lang = lang; + this.at = at; + this.vals.put(INST_START, at.getAddressableWordOffset()); + this.tree = tree; + this.grammar = tree.getGrammar(); + this.context = context.fillMask(); + this.ctxGraph = ctxGraph; + } + + public AbstractAssemblyResolutionFactory getFactory() { + return factory; + } + + /** + * Resolve the tree for the given parameters + * + * @return a set of resolutions (encodings and errors) + */ + public AssemblyResolutionResults resolve() { + RP empty = factory.nop("Empty"); + AssemblyConstructStateGenerator rootGen = + new AssemblyConstructStateGenerator(this, tree, empty); + + Collection errors = new ArrayList<>(); + Stream protStream = + rootGen.generate(new GeneratorContext(List.of(), 0)); + + if (DBG == DbgTimer.ACTIVE) { + try (DbgCtx dc = DBG.start("Prototypes:")) { + protStream = protStream.map(prot -> { + DBG.println(prot); + return prot; + }).collect(Collectors.toList()).stream(); + } + } + + Stream patStream = + protStream.map(p -> p.state).distinct().flatMap(s -> s.resolve(empty, errors)); + + AssemblyResolutionResults results = + patStream.collect(Collectors.toCollection(factory::newAssemblyResolutionResults)); + results = resolveRootRecursion(results); + results = selectContext(results); + results = resolvePendingBackfills(results); + // TODO: Remove this? It's subsumed by filterByDisassembly, and more accurately.... + results = filterForbidden(results); + results = filterByDisassembly(results); + for (AssemblyResolvedError err : errors) { + results.add(err); + } + return results; + } + + /** + * If applicable, get the {@code I => I} production of the grammar + * + * @return the production + */ + protected AssemblyProduction getRootRecursion() { + assert tree.getParent() == null; + AssemblyProduction rootProd = tree.getProduction(); + AssemblyNonTerminal start = rootProd.getLHS(); + AssemblyProduction rec = grammar.getPureRecursion(start); + return rec; + } + + /** + * If necessary, resolve recursive constructors at the root, usually for prefixes + * + *

+ * If there are no pure recursive constructors at the root, then this simply returns + * {@code temp} unmodified. + * + * @param temp the resolved root results + * @return the results with pure recursive constructors applied to obtain a compatible context + */ + // Ugh, public so I can refer to it in javadocs... + public AssemblyResolutionResults resolveRootRecursion(AssemblyResolutionResults temp) { + AssemblyProduction rootRec = getRootRecursion(); + if (rootRec == null) { + return temp; + } + try (DbgCtx dc = DBG.start("Resolving root recursion:")) { + AssemblyResolutionResults result = factory.newAssemblyResolutionResults(); + + for (AssemblyResolution ar : temp) { + if (ar.isError()) { + result.add(ar); + continue; + } + @SuppressWarnings("unchecked") + RP rp = (RP) ar; + AssemblyPatternBlock dst = rp.getContext(); + // TODO: The desired context may need to be passed in. For now, just take start. + AssemblyPatternBlock src = context; // NOTE: This is only correct for "instruction" + String table = "instruction"; + + DBG.println("Finding paths from " + src + " to " + ar.lineToString()); + Collection> paths = + ctxGraph.computeOptimalApplications(src, table, dst, table); + DBG.println("Found " + paths.size()); + for (Deque path : paths) { + DBG.println(" " + path); + result.absorb(applyRecursionPath(path, tree, rootRec, ar)); + } + } + + return result; + } + } + + /** + * Attempt a second time to solve operands and context changes + * + *

+ * Backfills that depended on {@code inst_next} should now easily be solved, since the + * instruction length is now known. + * + * @param temp the resolved results, with backfill pending + * @return the results without backfill, possible with new errors + */ + protected AssemblyResolutionResults resolvePendingBackfills(AssemblyResolutionResults temp) { + return temp.apply(factory, rp -> { + if (!rp.hasBackfills()) { + return rp; + } + vals.put(INST_NEXT, at.add(rp.getInstructionLength()).getAddressableWordOffset()); + // inst_next2 use not really supported + vals.put(INST_NEXT2, at.add(rp.getInstructionLength()).getAddressableWordOffset()); + DBG.println("Backfilling: " + rp); + AssemblyResolution ar = rp.backfill(SOLVER, vals); + DBG.println("Backfilled final: " + ar); + return ar; + }).apply(factory, rp -> { + if (rp.hasBackfills()) { + return factory.newErrorBuilder() + .error("Solution is incomplete") + .description("failed backfill") + .children(List.of(rp)) + .build(); + } + return rp; + }); + } + + /** + * Filter out results whose context do not match that requested + * + * @param temp the results whose contexts have not yet been checked + * @return the results that pass. Those that do not are replaced with errors. + */ + protected AssemblyResolutionResults selectContext(AssemblyResolutionResults temp) { + RP ctx = factory.contextOnly(context, "Selecting context"); + return temp.apply(factory, rp -> { + AssemblyResolution check = rp.combine(ctx); + if (null == check) { + return factory.newErrorBuilder() + .error("Incompatible context") + .description("resolving") + .children(List.of(rp)) + .build(); + } + return check; + }); + } + + /** + * Filter out results that would certainly be disassembled differently than assembled + * + *

+ * Because of constructor precedence rules, it is possible to assemble a pattern from a + * prototype that would not result in equivalent disassembly. This can be detected in some cases + * via the "forbids" mechanism, where more specific constructors are recorded with the result. + * If the generated pattern matches on of those more-specific constructors, it is forbidden. + * + * @param temp the results whose forbids have not yet been checked + * @return the results that pass. Those that do not are replaced with errors. + */ + protected AssemblyResolutionResults filterForbidden(AssemblyResolutionResults temp) { + return temp.apply(factory, rp -> rp.checkNotForbidden()); + } + + /** + * Filter out results that get disassembled differently than assembled + * + *

+ * The forbids mechanism is not perfect, so as a final fail safe, we disassemble the result and + * compare the prototypes. + * + * @param temp the results whose disassemblies have not yet been checked + * @return the results that pass. Those that do not are replaced with errors. + */ + protected AssemblyResolutionResults filterByDisassembly(AssemblyResolutionResults temp) { + AssemblyDefaultContext asmCtx = new AssemblyDefaultContext(lang); + asmCtx.setContextRegister(context); + return temp.apply(factory, rp -> { + MemBuffer buf = + new ByteMemBufferImpl(at, rp.getInstruction().getVals(), lang.isBigEndian()); + try { + SleighInstructionPrototype ip = + (SleighInstructionPrototype) lang.parse(buf, asmCtx, false); + if (!rp.equivalentConstructState(ip.getRootState())) { + return factory.error("Disassembly prototype mismatch", rp); + } + return rp; + } + catch (InsufficientBytesException | UnknownInstructionException e) { + return factory.error("Disassembly failed: " + e.getMessage(), rp); + } + }); + } + + /** + * Get the state generator for a given operand and parse tree node + * + * @param opSym the operand symbol + * @param node the corresponding parse tree node, possibly null indicating a hidden operand + * @param fromLeft the accumulated patterns from the left sibling or parent + * @return the generator + */ + protected AbstractAssemblyStateGenerator getStateGenerator(OperandSymbol opSym, + AssemblyParseTreeNode node, AssemblyResolvedPatterns fromLeft) { + if (node instanceof AssemblyParseHiddenNode) { + return getHiddenStateGenerator(opSym, fromLeft); + } + if (node instanceof AssemblyParseNumericToken token) { + return new AssemblyOperandStateGenerator(this, token, opSym, fromLeft); + } + if (node instanceof AssemblyParseBranch branch) { + return new AssemblyConstructStateGenerator(this, branch, fromLeft); + } + if (node instanceof AssemblyParseToken token && node.getSym().takesOperandIndex()) { + return new AssemblyStringStateGenerator(this, token, opSym, fromLeft); + } + throw new AssertionError(); + } + + /** + * Get the state generator for a hidden operand + * + * @param opSym the operand symbol + * @param fromLeft the accumulated patterns from the left sibling or parent + * @return the generator + */ + protected AbstractAssemblyStateGenerator getHiddenStateGenerator(OperandSymbol opSym, + AssemblyResolvedPatterns fromLeft) { + TripleSymbol defSym = opSym.getDefiningSymbol(); + if (defSym instanceof SubtableSymbol subtable) { + return new AssemblyHiddenConstructStateGenerator(this, subtable, fromLeft); + } + return new AssemblyNopStateGenerator(this, opSym, fromLeft); + } + + /** + * Apply a constructor pattern + * + *

+ * TODO: This is currently used only for resolving recursion. Could this be factored with + * {@link AssemblyConstructState#resolve(AssemblyResolvedPatterns, Collection)}? + * + * @param sem the SLEIGH constructor + * @param shift the shift + * @param fromChildren the results from the single resolved child + * @return the results + */ + protected AssemblyResolutionResults resolvePatterns(AssemblyConstructorSemantic sem, int shift, + AssemblyResolutionResults fromChildren) { + AssemblyResolutionResults results = fromChildren; + results = applyMutations(sem, results); + results = applyPatterns(sem, shift, results); + results = tryResolveBackfills(results); + return results; + } + + protected AssemblyResolutionResults parent(String description, AssemblyResolutionResults temp, + int opCount) { + return temp.stream() + .map(r -> r.parent(description, opCount)) + .collect(Collectors.toCollection(factory::newAssemblyResolutionResults)); + } + + /** + * TODO: This is currently used only for resolving recursion. Could this be factored with + * {@link AssemblyConstructState#resolveMutations(AssemblyResolvedPatterns, Collection)}? + */ + protected AssemblyResolutionResults applyMutations(AssemblyConstructorSemantic sem, + AssemblyResolutionResults temp) { + DBG.println("Applying context mutations:"); + return temp.apply(factory, rp -> { + DBG.println("Current: " + rp.lineToString()); + AssemblyResolution backctx = sem.solveContextChanges(rp, vals); + DBG.println("Mutated: " + backctx.lineToString()); + return backctx; + }).apply(factory, rp -> { + return rp.solveContextChangesForForbids(sem, vals); + }); + } + + /** + * TODO: This is currently used only for resolving recursion. Could this be factored with + * {@link AssemblyConstructState#resolvePatterns(AssemblyResolvedPatterns, Collection)}? + */ + protected AssemblyResolutionResults applyPatterns(AssemblyConstructorSemantic sem, int shift, + AssemblyResolutionResults temp) { + DBG.println("Applying patterns:"); + Collection patterns = + sem.getPatterns() + .stream() + .map(p -> p.shift(shift)) + .collect(Collectors.toList()); + return temp.apply(factory, new Applicator() { + @Override + public Iterable getPatterns( + AssemblyResolvedPatterns cur) { + return patterns; + } + + @Override + public AssemblyResolvedPatterns setRight(AssemblyResolvedPatterns res, + AssemblyResolvedPatterns cur) { + // This is typically applied by parent, so don't insert sibling + return res; + } + + @Override + public String describeError(AssemblyResolvedPatterns rp, AssemblyResolution pat) { + return "The patterns conflict " + pat.lineToString(); + } + + @Override + public AssemblyResolvedPatterns combineBackfill(AssemblyResolvedPatterns cur, + AssemblyResolvedBackfill bf) { + throw new AssertionError(); + } + + @Override + public AssemblyResolution finish(AssemblyResolvedPatterns resolved) { + return resolved.checkNotForbidden(); + } + }); + } + + /** + * Apply constructors as indicated by a path returned by the context resolution graph + * + *

+ * NOTE: The given path will be emptied during processing. + * + * @param path the path to apply + * @param branch the branch corresponding to the production whose LHS has a purely-recursive + * definition. + * @param rec the purely-recursive production + * @param child the intermediate result to apply the constructors to + * @return the results + */ + protected AssemblyResolutionResults applyRecursionPath(Deque path, + AssemblyParseBranch branch, AssemblyProduction rec, AssemblyResolution child) { + /* + * A constructor may have multiple patterns, so I cannot assume I will get at most one + * output at each constructor in the path. Start (1) collecting all the results, then (2) + * filter out and report the errors, then (3) feed successful resolutions into the next + * constructor in the path (or finish). + */ + AssemblyResolutionResults results = factory.newAssemblyResolutionResults(); + results.add(child); + while (!path.isEmpty()) { + AssemblyConstructorSemantic sem = path.pollLast(); + + int opIdx = sem.getOperandIndex(0); + Constructor cons = sem.getConstructor(); + OperandSymbol opSym = cons.getOperand(opIdx); + if (-1 != opSym.getOffsetBase()) { + throw new AssertionError("TODO"); + } + int offset = opSym.getRelativeOffset(); + results = parent("Resolving recursive constructor: " + cons.getSourceFile() + ":" + + cons.getLineno(), results, 1); + results = results.apply(factory, rp -> rp.shift(offset)); + results = + resolvePatterns(sem, 0, results).apply(factory, rp -> rp.withConstructor(cons)); + } + return results; + } + + /** + * TODO: This is currently used only for resolving recursion. It seems it's missing from the + * refactor? + */ + protected AssemblyResolutionResults tryResolveBackfills(AssemblyResolutionResults results) { + AssemblyResolutionResults res = factory.newAssemblyResolutionResults(); + next_ar: for (AssemblyResolution ar : results) { + if (ar.isError()) { + res.add(ar); + continue; + } + while (true) { + AssemblyResolvedPatterns rp = (AssemblyResolvedPatterns) ar; + if (!rp.hasBackfills()) { + // finish: The complete solution is known + res.add(ar); + continue next_ar; + } + ar = rp.backfill(SOLVER, vals); + if (ar.isError() || ar.isBackfill()) { + // fail: It is now known that the solution doesn't exist + res.add(ar); + continue next_ar; + } + if (ar.equals(rp)) { + // fail: The solution is /still/ not known, and we made no progress + res.add(ar); + continue next_ar; + } + // Some progress was made, continue trying until we finish or fail + } + } + return res; + } + + /** + * Compute the offset of an operand encoded in the instruction block + * + *

+ * TODO: Currently, there are duplicate mechanisms for resolving a constructor: 1) The newer + * mechanism implemented in {@link AssemblyConstructState}, and 2) the older one implemented in + * {@link #applyPatterns(AssemblyConstructorSemantic, int, AssemblyResolutionResults)}. The + * latter seems to require this method, since it does not have pre-computed shifts as in the + * former. We should probably remove the latter in favor of the former.... + * + * @param opsym the operand symbol + * @param cons the constructor containing the operand + * @return the offset (right shift) to apply to the encoded operand + */ + public static int computeOffset(OperandSymbol opsym, Constructor cons) { + int offset = opsym.getRelativeOffset(); + int baseidx = opsym.getOffsetBase(); + if (baseidx != -1) { + OperandSymbol baseop = cons.getOperand(baseidx); + offset += baseop.getMinimumLength(); + offset += computeOffset(baseop, cons); + } + return offset; + } + + public AssemblyGrammar getGrammar() { + return grammar; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructState.java index 157985208c..a91fbedc71 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructState.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructState.java @@ -39,7 +39,7 @@ public class AssemblyConstructState extends AbstractAssemblyState { * @param operands the operands * @return the farthest end byte */ - protected static int computeEnd(List operands) { + protected static int computeEnd(List operands) { return operands.stream() .map(s -> s.shift + s.length) .reduce(0, Integer::max); @@ -61,7 +61,7 @@ public class AssemblyConstructState extends AbstractAssemblyState { * @param sem the selected SLEIGH constructor * @param children the child state for each operand in the constructor */ - public AssemblyConstructState(AssemblyTreeResolver resolver, + public AssemblyConstructState(AbstractAssemblyTreeResolver resolver, List path, int shift, AssemblyConstructorSemantic sem, List children) { super(resolver, path, shift, @@ -150,8 +150,10 @@ public class AssemblyConstructState extends AbstractAssemblyState { }) .filter(ar -> { if (ar == null) { - errors.add(AssemblyResolution.error("Pattern conflict", - "Resolving " + sem.getLocation() + " in " + path)); + errors.add(factory.newErrorBuilder() + .error("Pattern conflict") + .description("Resolving " + sem.getLocation() + " in " + path) + .build()); return false; } return true; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructStateGenerator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructStateGenerator.java index 879c363f8a..7dca302e57 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructStateGenerator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructStateGenerator.java @@ -20,8 +20,7 @@ import java.util.stream.Stream; import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyProduction; import ghidra.app.plugin.assembler.sleigh.symbol.AssemblySymbol; -import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch; -import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseTreeNode; +import ghidra.app.plugin.assembler.sleigh.tree.*; import ghidra.app.plugin.assembler.sleigh.util.AsmUtil; import ghidra.app.plugin.processors.sleigh.Constructor; import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol; @@ -43,8 +42,8 @@ public class AssemblyConstructStateGenerator * @param node the node from which to generate states * @param fromLeft the accumulated patterns from the left sibling or the parent */ - public AssemblyConstructStateGenerator(AssemblyTreeResolver resolver, AssemblyParseBranch node, - AssemblyResolvedPatterns fromLeft) { + public AssemblyConstructStateGenerator(AbstractAssemblyTreeResolver resolver, + AssemblyParseBranch node, AssemblyResolvedPatterns fromLeft) { super(resolver, node, fromLeft); } @@ -68,8 +67,9 @@ public class AssemblyConstructStateGenerator */ protected List orderOpNodes(AssemblyConstructorSemantic sem) { Constructor cons = sem.getConstructor(); - List result = - Arrays.asList(new AssemblyParseTreeNode[cons.getNumOperands()]); + AssemblyParseTreeNode[] arr = new AssemblyParseTreeNode[cons.getNumOperands()]; + List result = Arrays.asList(arr); + Arrays.fill(arr, new AssemblyParseHiddenNode(resolver.grammar)); int index = 0; AssemblyProduction production = node.getProduction(); List substitutions = node.getSubstitutions(); @@ -167,13 +167,13 @@ public class AssemblyConstructStateGenerator * child operand boundary is numbered. The offset base must always refer to an operand to the * left. * - * @param parentGc the generator context for othis node + * @param parentGc the generator context for this node * @param childGcs a list to collect the generator context for each child operand. The root * invocation should pass a fixed-length mutable list of nulls, one for each child. * @param fromLeft the accumulated patterns from the left sibling. The root invocation should * pass the patterns accumulated after context changes. * @param sem the selected SLEIGH constructor, whose operands to generate - * @param opOrdered the paresd children ordered as the constructor's operands + * @param opOrdered the parsed children ordered as the constructor's operands * @param children the list of children generated so far. The root invocation should pass the * empty list. * @return the stream of generated (sub) prototypes diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructorSemantic.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructorSemantic.java index bba2c30e3e..5be9e3d122 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructorSemantic.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyConstructorSemantic.java @@ -36,9 +36,10 @@ import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol; */ public class AssemblyConstructorSemantic implements Comparable { protected static final RecursiveDescentSolver SOLVER = RecursiveDescentSolver.getSolver(); - protected static final DbgTimer DBG = AssemblyTreeResolver.DBG; + protected static final DbgTimer DBG = AbstractAssemblyTreeResolver.DBG; protected final Set patterns = new HashSet<>(); + protected final AbstractAssemblyResolutionFactory factory; protected final Constructor cons; protected final List indices; protected final List contextChanges; @@ -54,7 +55,9 @@ public class AssemblyConstructorSemantic implements Comparable indices) { + public AssemblyConstructorSemantic(AbstractAssemblyResolutionFactory factory, + Constructor cons, List indices) { + this.factory = factory; this.cons = cons; this.indices = Collections.unmodifiableList(indices); List changes = new ArrayList<>(cons.getContextChanges()); @@ -69,7 +72,7 @@ public class AssemblyConstructorSemantic implements Comparable vals) { @@ -320,12 +323,12 @@ public class AssemblyConstructorSemantic implements Comparable { + protected final AbstractAssemblyResolutionFactory factory; protected final Map> semantics = LazyMap.lazyMap(new HashMap<>(), () -> new HashSet<>()); protected final AssemblyGrammar grammar; @@ -72,7 +73,9 @@ public class AssemblyContextGraph implements GImplicitDirectedGraph factory, + SleighLanguage lang, AssemblyGrammar grammar) { + this.factory = factory; this.grammar = grammar; this.lang = lang; @@ -343,7 +346,7 @@ public class AssemblyContextGraph implements GImplicitDirectedGraph result = new HashSet<>(); for (AssemblyConstructorSemantic sem : semantics.get(from.subtable)) { for (AssemblyResolvedPatterns rc : sem.patterns) { - AssemblyPatternBlock pattern = rc.ctx; + AssemblyPatternBlock pattern = rc.getContext(); AssemblyPatternBlock outer = from.context.combine(pattern); if (outer == null) { continue; @@ -352,8 +355,7 @@ public class AssemblyContextGraph implements GImplicitDirectedGraph resolver, SubtableSymbol subtableSym, AssemblyResolvedPatterns fromLeft) { super(resolver, null, fromLeft); this.subtableSym = subtableSym; @@ -56,10 +55,17 @@ public class AssemblyHiddenConstructStateGenerator extends AssemblyConstructStat .flatMap(sem -> applyConstructor(gc, sem)); } + protected AssemblyParseTreeNode getFiller() { + return new AssemblyParseHiddenNode(resolver.grammar); + } + @Override protected List orderOpNodes(AssemblyConstructorSemantic sem) { - // Just provide null operands, since they're hidden, too. + // Just provide hidden operands Constructor cons = sem.getConstructor(); - return Arrays.asList(new AssemblyParseTreeNode[cons.getNumOperands()]); + AssemblyParseTreeNode hidden = getFiller(); + return IntStream.range(0, cons.getNumOperands()) + .mapToObj(i -> hidden) + .collect(Collectors.toList()); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyNopState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyNopState.java index 32806079ca..6c12b0d71a 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyNopState.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyNopState.java @@ -22,25 +22,21 @@ import java.util.stream.Stream; import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol; public class AssemblyNopState extends AbstractAssemblyState { - public AssemblyNopState(AssemblyTreeResolver resolver, List path, - int shift, OperandSymbol opSym) { + + public AssemblyNopState(AbstractAssemblyTreeResolver resolver, + List path, int shift, OperandSymbol opSym) { super(resolver, path, shift, opSym.getMinimumLength()); } @Override public int computeHash() { - return "NOP".hashCode(); + int result = getClass().hashCode(); + result *= 31; + result += Integer.hashCode(shift); + return result; } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof AssemblyNopState)) { - return false; - } - AssemblyNopState that = (AssemblyNopState) obj; + protected boolean partsEqual(AssemblyNopState that) { if (this.resolver != that.resolver) { return false; } @@ -50,6 +46,18 @@ public class AssemblyNopState extends AbstractAssemblyState { return true; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (this.getClass() != obj.getClass()) { + return false; + } + AssemblyNopState that = (AssemblyNopState) obj; + return partsEqual(that); + } + @Override public String toString() { return "NOP"; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyNopStateGenerator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyNopStateGenerator.java index 59a3a34d4c..57fd24bdc7 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyNopStateGenerator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyNopStateGenerator.java @@ -39,8 +39,8 @@ public class AssemblyNopStateGenerator * @param opSym the operand symbol * @param fromLeft the accumulated patterns from the left sibling or parent */ - public AssemblyNopStateGenerator(AssemblyTreeResolver resolver, OperandSymbol opSym, - AssemblyResolvedPatterns fromLeft) { + public AssemblyNopStateGenerator(AbstractAssemblyTreeResolver resolver, + OperandSymbol opSym, AssemblyResolvedPatterns fromLeft) { super(resolver, null, fromLeft); this.opSym = opSym; } @@ -48,8 +48,7 @@ public class AssemblyNopStateGenerator @Override public Stream generate(GeneratorContext gc) { gc.dbg("Generating NOP for " + opSym); - return Stream.of( - new AssemblyGeneratedPrototype(new AssemblyNopState(resolver, gc.path, gc.shift, opSym), - fromLeft)); + return Stream.of(new AssemblyGeneratedPrototype( + new AssemblyNopState(resolver, gc.path, gc.shift, opSym), fromLeft)); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyOperandState.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyOperandState.java index c96ff4cacc..36e82087f1 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyOperandState.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyOperandState.java @@ -47,7 +47,7 @@ public class AssemblyOperandState extends AbstractAssemblyState { * @param value the value of the operand * @param opSym the operand symbol */ - public AssemblyOperandState(AssemblyTreeResolver resolver, + public AssemblyOperandState(AbstractAssemblyTreeResolver resolver, List path, int shift, AssemblyTerminal terminal, long value, OperandSymbol opSym) { super(resolver, path, shift, opSym.getMinimumLength()); @@ -58,18 +58,17 @@ public class AssemblyOperandState extends AbstractAssemblyState { @Override public int computeHash() { - return Objects.hash(getClass(), shift, value, opSym); + int result = getClass().hashCode(); + result *= 31; + result += Integer.hashCode(shift); + result *= 31; + result += Long.hashCode(value); + result *= 31; + result += opSym.hashCode(); + return result; } - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof AssemblyOperandState)) { - return false; - } - AssemblyOperandState that = (AssemblyOperandState) obj; + protected boolean partsEqual(AssemblyOperandState that) { if (this.resolver != that.resolver) { return false; } @@ -85,6 +84,18 @@ public class AssemblyOperandState extends AbstractAssemblyState { return true; } + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (this.getClass() != obj.getClass()) { + return false; + } + AssemblyOperandState that = (AssemblyOperandState) obj; + return partsEqual(that); + } + @Override public String toString() { return terminal + "=" + value + "(0x" + Long.toHexString(value) + ")"; @@ -120,7 +131,7 @@ public class AssemblyOperandState extends AbstractAssemblyState { DBG.println("Equation: " + symExp + " = " + Long.toHexString(value)); String desc = "Solution to " + opSym + " in " + Long.toHexString(value) + " = " + symExp; AssemblyResolution sol = - AssemblyTreeResolver.solveOrBackfill(symExp, value, bitsize, resolver.vals, null, desc); + factory.solveOrBackfill(symExp, value, bitsize, resolver.vals, null, desc); DBG.println("Solution: " + sol); AssemblyResolution shifted = sol.shift(shift); DBG.println("Shifted: " + shifted); @@ -143,8 +154,10 @@ public class AssemblyOperandState extends AbstractAssemblyState { } AssemblyResolution combined = fromRight.combine((AssemblyResolvedPatterns) sol); if (combined == null) { - errors.add( - AssemblyResolution.error("Pattern/operand conflict", "Resolving " + terminal)); + errors.add(factory.newErrorBuilder() + .error("Pattern/operand conflict") + .description("Resolving " + terminal) + .build()); return Stream.of(); } AssemblyResolvedPatterns pats = (AssemblyResolvedPatterns) combined; @@ -152,4 +165,16 @@ public class AssemblyOperandState extends AbstractAssemblyState { return Stream.of(pats.withRight(fromRight).withConstructor(null)); } } + + public AssemblyTerminal getTerminal() { + return terminal; + } + + public long getValue() { + return value; + } + + public OperandSymbol getOperandSymbol() { + return opSym; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyOperandStateGenerator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyOperandStateGenerator.java index 477e4f8cca..83f40d532e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyOperandStateGenerator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyOperandStateGenerator.java @@ -35,11 +35,11 @@ public class AssemblyOperandStateGenerator * Construct the operand state generator * * @param resolver the resolver - * @param node the ndoe from which to generate the state + * @param node the node from which to generate the state * @param fromLeft the accumulated patterns from the left sibling or parent * @param opSym the operand symbol */ - public AssemblyOperandStateGenerator(AssemblyTreeResolver resolver, + public AssemblyOperandStateGenerator(AbstractAssemblyTreeResolver resolver, AssemblyParseNumericToken node, OperandSymbol opSym, AssemblyResolvedPatterns fromLeft) { super(resolver, node, fromLeft); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyPatternBlock.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyPatternBlock.java index 7a4e82bf1f..c1b71284f2 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyPatternBlock.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyPatternBlock.java @@ -17,8 +17,7 @@ package ghidra.app.plugin.assembler.sleigh.sem; import java.math.BigInteger; import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Iterator; +import java.util.*; import java.util.concurrent.atomic.AtomicLong; import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong; @@ -144,7 +143,7 @@ public class AssemblyPatternBlock implements Comparable { AtomicLong msk = new AtomicLong(); AtomicLong val = new AtomicLong(); int i = 0; - for (String hex : str.split(":")) { + for (String hex : str.substring(pos).split(":")) { NumericUtilities.convertHexStringToMaskedValue(msk, val, hex, 2, 0, null); mask[i] = (byte) msk.get(); vals[i] = (byte) val.get(); @@ -265,6 +264,7 @@ public class AssemblyPatternBlock implements Comparable { /** * Convert a register value into a pattern block * + *

* This is used primarily to compute default context register values, and pass them into an * assembler. * @@ -604,9 +604,132 @@ public class AssemblyPatternBlock implements Comparable { return new AssemblyPatternBlock(offset, newMask, newVals); } + /** + * Set all bits that are known (1 in mask) in {@code other} to unknown. + * + *

+ * Other must have the same or shorter length than this. + * + * @param other the other pattern block whose mask bits are examined + * @return a copy of this pattern with mask bits set to unknown + */ + public AssemblyPatternBlock maskOut(AssemblyPatternBlock other) { + assert this.length() >= other.length(); + + byte[] newMask = Arrays.copyOf(this.mask, this.mask.length); + byte[] newVals = Arrays.copyOf(this.vals, this.vals.length); + + for (int i = this.offset; i < Math.min(this.length(), other.length()); i++) { + if (i < this.offset || i < other.offset) { + continue; + } + newMask[i - this.offset] &= (~other.mask[i - other.offset] & 0xff); + newVals[i - this.offset] &= (~other.mask[i - other.offset] & 0xff); + } + return new AssemblyPatternBlock(offset, newMask, newVals); + } + + /*test access*/ + static byte[] bitShiftRightByteArray(byte[] input, int amount) { + byte[] newMask = new byte[input.length]; + for (int i = input.length - 1; i >= 0; i--) { + // Add the lower bits of the next byte to the previously processed byte + if (i < input.length - 1) { + newMask[i + 1] = (byte) (newMask[i + 1] | ((input[i] << (8 - amount) & 0xff))); + } + + // Shift down the bits of this byte + newMask[i] = (byte) (((input[i]) & 0xff) >> amount); + } + return newMask; + } + + /** + * Remove all unknown bits from both left and right + * + * @return new value without any left or right unknown bits (but may have unknown bits in the + * middle) + */ + public AssemblyPatternBlock trim() { + + var minNonZeroMask = Integer.MAX_VALUE; + var maxNonZeroMask = -1; + for (int i = 0; i < this.mask.length; i++) { + if (mask[i] != 0) { + minNonZeroMask = Math.min(minNonZeroMask, i); + maxNonZeroMask = i; + } + } + if (maxNonZeroMask == -1) { + return AssemblyPatternBlock.nop(); + } + + var bitShiftAmount = Integer.numberOfTrailingZeros(mask[maxNonZeroMask]); + + var newMask = bitShiftRightByteArray( + Arrays.copyOfRange(mask, minNonZeroMask, maxNonZeroMask + 1), bitShiftAmount); + var newVals = bitShiftRightByteArray( + Arrays.copyOfRange(vals, minNonZeroMask, maxNonZeroMask + 1), bitShiftAmount); + + if (newMask[0] == 0) { + newMask = Arrays.copyOfRange(newMask, 1, newMask.length); + newVals = Arrays.copyOfRange(newVals, 1, newVals.length); + } + return new AssemblyPatternBlock(0, newMask, newVals); + } + + /** + * Get an array representing the full value of the pattern + * + *

+ * This is a copy of the {@link #getVals()} array, but with 0s prepended to apply the offset. + * See {@link #getOffset()}. + * + * @return the array + */ + public byte[] getValsAll() { + byte[] out = new byte[offset + vals.length]; + + for (int i = 0; i < offset; i++) { + out[i] = 0; + } + + for (int i = 0; i < vals.length; i++) { + out[offset + i] = vals[i]; + } + return out; + } + + /** + * Get an array representing the full mask of the pattern + * + *

+ * This is a copy of the {@link #getMask()} array, but with 0s prepended to apply the offset. + * See {@link #getOffset()}. + * + * @return the array + */ + public byte[] getMaskAll() { + byte[] out = new byte[offset + mask.length]; + + for (int i = 0; i < offset; i++) { + out[i] = 0; + } + + for (int i = 0; i < mask.length; i++) { + out[offset + i] = mask[i]; + } + return out; + } + /** * Get the values array * + *

+ * Modifications to the returned array will affect the pattern block. It is not a copy. + * Furthermore, the offset is not incorporated. See {@link #getOffset()}. For a copy of the + * array with offset applied, use {@link #getValsAll()}. + * * @return the array */ public byte[] getVals() { @@ -616,12 +739,39 @@ public class AssemblyPatternBlock implements Comparable { /** * Get the mask array * + *

+ * Modifications to the returned array will affect the pattern block. It is not a copy. + * Furthermore, the offset is not incorporated. See {@link #getOffset()}. For a copy of the + * array with offset applied, use {@link #getMaskAll()}. + * + * * @return the array */ public byte[] getMask() { return mask; } + /** + * Mask the given {@code unmasked} value with the mask contained in this pattern block. + * + *

+ * The returned {@link AssemblyPatternBlock} has an identical mask as {@code this} but with a + * value taken from the given {@code unmasked}. + * + * @param unmasked the value to be masked into the result + * @return a combination of the given unmasked value and this mask + */ + public AssemblyPatternBlock getMaskedValue(byte[] unmasked) { + assert offset + mask.length <= unmasked.length; + var newVals = Arrays.copyOfRange(unmasked, offset, offset + mask.length); + + for (int i = 0; i < newVals.length; i++) { + newVals[i] = (byte) ((newVals[i] & mask[i]) & 0xff); + } + + return new AssemblyPatternBlock(offset, mask, newVals); + } + /** * Get the number of undefined bytes preceding the mask and values arrays * diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolution.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolution.java index c7d9baabf5..68f7f263fe 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolution.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolution.java @@ -15,285 +15,23 @@ */ package ghidra.app.plugin.assembler.sleigh.sem; -import java.util.*; +import java.util.Collection; +import java.util.List; -import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong; -import ghidra.app.plugin.processors.sleigh.Constructor; -import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; -import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern; - -/** - * The (often intermediate) result of assembly - * - *

- * These may represent a successful construction ({@link AssemblyResolvedPatterns}, a future field - * ({@link AssemblyResolvedBackfill}), or an error ({@link AssemblyResolvedError}). - * - *

- * This class also provides the static factory methods for constructing any of its subclasses. - */ -public abstract class AssemblyResolution implements Comparable { - protected final String description; - protected final List children; - protected final AssemblyResolution right; - - private boolean hashed = false; - private int hash; - - @Override - public int hashCode() { - if (!hashed) { - hash = computeHash(); - hashed = true; - } - return hash; - } - - protected abstract int computeHash(); +public interface AssemblyResolution extends Comparable { /** - * Construct a resolution - * - * @param description a textual description used as part of {@link #toString()} - * @param children for record keeping, any children used in constructing this resolution - */ - AssemblyResolution(String description, List children, - AssemblyResolution right) { - this.description = description; - this.children = children == null ? List.of() : Collections.unmodifiableList(children); - this.right = right; - } - - /* ******************************************************************************************** - * Static factory methods - */ - - /** - * Build the result of successfully resolving a SLEIGH constructor + * {@inheritDoc} * *

- * NOTE: This is not used strictly for resolved SLEIGH constructors. It may also be used - * to store intermediates, e.g., encoded operands, during constructor resolution. - * - * @param ins the instruction pattern block - * @param ctx the context pattern block - * @param description a description of the resolution - * @param cons the constructor, or null - * @param children the children of this constructor, or null - * @return the new resolution - */ - public static AssemblyResolvedPatterns resolved(AssemblyPatternBlock ins, - AssemblyPatternBlock ctx, String description, Constructor cons, - List children, AssemblyResolution right) { - return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx, null, - null); - } - - /** - * Build an instruction-only successful resolution result - * - * @param ins the instruction pattern block - * @param description a description of the resolution - * @return the new resolution - * @see #resolved(AssemblyPatternBlock, AssemblyPatternBlock, String, Constructor, List, AssemblyResolution) - */ - public static AssemblyResolvedPatterns instrOnly(AssemblyPatternBlock ins, - String description) { - return resolved(ins, AssemblyPatternBlock.nop(), description, null, null, null); - } - - /** - * Build a context-only successful resolution result - * - * @param ctx the context pattern block - * @param description a description of the resolution - * @return the new resolution - * @see #resolved(AssemblyPatternBlock, AssemblyPatternBlock, String, Constructor, List, AssemblyResolution) - */ - public static AssemblyResolvedPatterns contextOnly(AssemblyPatternBlock ctx, - String description) { - return resolved(AssemblyPatternBlock.nop(), ctx, description, null, null, null); - } - - /** - * Build a successful resolution result from a SLEIGH constructor's patterns - * - * @param pat the constructor's pattern - * @param description a description of the resolution - * @return the new resolution - */ - public static AssemblyResolvedPatterns fromPattern(DisjointPattern pat, int minLen, - String description, Constructor cons) { - AssemblyPatternBlock ins = AssemblyPatternBlock.fromPattern(pat, minLen, false); - AssemblyPatternBlock ctx = AssemblyPatternBlock.fromPattern(pat, 0, true); - return resolved(ins, ctx, description, cons, null, null); - } - - /** - * Build a backfill record to attach to a successful resolution result - * - * @param exp the expression depending on a missing symbol - * @param goal the desired value of the expression - * @param inslen the length of instruction portion expected in the future solution - * @param description a description of the backfill record - * @return the new record - */ - public static AssemblyResolvedBackfill backfill(PatternExpression exp, MaskedLong goal, - int inslen, String description) { - return new AssemblyResolvedBackfill(description, exp, goal, inslen, 0); - } - - /** - * Obtain a new "blank" resolved SLEIGH constructor record - * - * @param description a description of the resolution - * @param children any children that will be involved in populating this record - * @return the new resolution - */ - public static AssemblyResolvedPatterns nop(String description, - List children, AssemblyResolution right) { - return resolved(AssemblyPatternBlock.nop(), AssemblyPatternBlock.nop(), description, null, - children, right); - } - - /** - * Obtain a new "blank" resolved SLEIGH constructor record - * - * @param description a description of the resolution - * @return the new resolution - */ - public static AssemblyResolvedPatterns nop(String description) { - return resolved(AssemblyPatternBlock.nop(), AssemblyPatternBlock.nop(), description, null, - null, null); - } - - /** - * Build an error resolution record - * - * @param error a description of the error - * @param description a description of what the resolver was doing when the error ocurred - * @param children any children involved in generating the error - * @return the new resolution - */ - public static AssemblyResolvedError error(String error, String description, - List children, AssemblyResolution right) { - return new AssemblyResolvedError(description, children, right, error); - } - - /** - * Build an error resolution record - * - * @param error a description of the error - * @param description a description of what the resolver was doing when the error occurred - * @return the new resolution - */ - public static AssemblyResolvedError error(String error, String description) { - return new AssemblyResolvedError(description, null, null, error); - } - - /** - * Build an error resolution record, based on an intermediate SLEIGH constructor record - * - * @param error a description of the error - * @param res the constructor record that was being populated when the error ocurred - * @return the new error resolution - */ - public static AssemblyResolution error(String error, AssemblyResolvedPatterns res) { - return error(error, res.description, res.children, res.right); - } - - /* ******************************************************************************************** - * Abstract methods - */ - - /** - * Check if this record describes an error - * - * @return true if the record is an error - */ - public abstract boolean isError(); - - /** - * Check if this record describes a backfill - * - * @return true if the record is a backfill - */ - public abstract boolean isBackfill(); - - /** - * Display the resolution result in one line (omitting child details) - * - * @return the display description - */ - protected abstract String lineToString(); - - /* ******************************************************************************************** - * Misc - */ - - protected List getAllRight() { - List result = new ArrayList<>(); - collectAllRight(result); - return result; - } - - protected void collectAllRight(Collection into) { - into.add(this); - if (right == null) { - return; - } - right.collectAllRight(into); - } - - /** - * Get the child portion of {@link #toString()} - * - *

- * If a subclass has another, possible additional, notion of children that it would like to - * include in {@link #toString()}, it must override this method. - * - * @see #hasChildren() - * @param indent the current indentation - * @return the indented description for each child on its own line - */ - protected String childrenToString(String indent) { - StringBuilder sb = new StringBuilder(); - for (AssemblyResolution child : children) { - sb.append(child.toString(indent) + "\n"); - } - return sb.substring(0, sb.length() - 1); - } - - /** - * Used only by parents: get a multi-line description of this record, indented - * - * @param indent the current indentation - * @return the indented description - */ - public String toString(String indent) { - StringBuilder sb = new StringBuilder(); - sb.append(indent); - sb.append(lineToString()); - if (hasChildren()) { - sb.append(":\n"); - String newIndent = indent + " "; - sb.append(childrenToString(newIndent)); - } - return sb.toString(); - } - - /** - * Describe this record including indented children, grandchildren, etc., each on its own line + * Describe this record including indented children, grandchildren, etc., each on its own line. */ @Override - public String toString() { - return toString(""); - } + String toString(); - @Override - public int compareTo(AssemblyResolution that) { - return this.toString().compareTo(that.toString()); // LAZY - } + String getDescription(); + + List getChildren(); /** * Check if this record has children @@ -306,15 +44,30 @@ public abstract class AssemblyResolution implements Comparable into); + + /** + * Used only by parents: get a multi-line description of this record, indented + * + * @param indent the current indentation + * @return the indented description + */ + String toString(String indent); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolutionResults.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolutionResults.java index c366ffab84..efb14d25fe 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolutionResults.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolutionResults.java @@ -27,21 +27,21 @@ import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; * A set of possible assembly resolutions for a single SLEIGH constructor * *

- * Since the assembler works from the leaves up, it unclear in what context a given token appears. + * Since the assembler works from the leaves up, it's unclear in what context a given token appears. * Thus, every possible encoding is collected and passed upward. As resolution continues, many of * the possible encodings are pruned out. When the resolver reaches the root, we end up with every * possible encoding (less some prefixes) of an instruction. This object stores the possible * encodings, including error records describing the pruned intermediate results. */ public class AssemblyResolutionResults extends AbstractSetDecorator { - protected static final DbgTimer DBG = AssemblyTreeResolver.DBG; + protected static final DbgTimer DBG = AbstractAssemblyTreeResolver.DBG; public interface Applicator { Iterable getPatterns(AssemblyResolvedPatterns cur); default AssemblyResolvedPatterns setDescription( AssemblyResolvedPatterns res, AssemblyResolution from) { - AssemblyResolvedPatterns temp = res.withDescription(from.description); + AssemblyResolvedPatterns temp = res.withDescription(from.getDescription()); return temp; } @@ -92,20 +92,10 @@ public class AssemblyResolutionResults extends AbstractSetDecorator(); } - private AssemblyResolutionResults(Set resolutions) { + protected AssemblyResolutionResults(Set resolutions) { this.resolutions = resolutions; } - /** - * Construct an immutable single-entry set consisting of the one given resolution - * - * @param rc the single resolution entry - * @return the new resolution set - */ - public static AssemblyResolutionResults singleton(AssemblyResolvedPatterns rc) { - return new AssemblyResolutionResults(Collections.singleton(rc)); - } - @Override public boolean add(AssemblyResolution ar) { return resolutions.add(ar); @@ -143,21 +133,22 @@ public class AssemblyResolutionResults extends AbstractSetDecorator factory, + Applicator applicator) { + AssemblyResolutionResults results = factory.newAssemblyResolutionResults(); for (AssemblyResolution res : this) { if (res.isError()) { results.add(res); continue; } - AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) res; - DBG.println("Current: " + rc.lineToString()); - for (AssemblyResolution pat : applicator.getPatterns(rc)) { - DBG.println("Pattern: " + pat.lineToString()); - AssemblyResolvedPatterns combined = applicator.combine(rc, pat); + AssemblyResolvedPatterns rp = (AssemblyResolvedPatterns) res; + DBG.println("Current: " + rp.lineToString()); + for (AssemblyResolution ar : applicator.getPatterns(rp)) { + DBG.println("Pattern: " + ar.lineToString()); + AssemblyResolvedPatterns combined = applicator.combine(rp, ar); DBG.println("Combined: " + (combined == null ? "(null)" : combined.lineToString())); if (combined == null) { - results.add(AssemblyResolution.error(applicator.describeError(rc, pat), rc)); + results.add(factory.error(applicator.describeError(rp, ar), ar)); continue; } results.add(applicator.finish(combined)); @@ -166,14 +157,19 @@ public class AssemblyResolutionResults extends AbstractSetDecorator factory, Function function) { return stream().map(res -> { - assert !(res instanceof AssemblyResolvedBackfill); - if (res.isError()) { - return res; + if (res instanceof AssemblyResolvedBackfill) { + throw new AssertionError(); } - return function.apply((AssemblyResolvedPatterns) res); - }).collect(Collectors.toCollection(AssemblyResolutionResults::new)); + if (res instanceof AssemblyResolvedError err) { + return err; + } + if (res instanceof AssemblyResolvedPatterns rp) { + return function.apply(rp); + } + throw new AssertionError(); + }).collect(Collectors.toCollection(factory::newAssemblyResolutionResults)); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedBackfill.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedBackfill.java index 47fe59b02d..b45246fa8e 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedBackfill.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedBackfill.java @@ -17,66 +17,9 @@ package ghidra.app.plugin.assembler.sleigh.sem; import java.util.Map; -import ghidra.app.plugin.assembler.sleigh.expr.*; -import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; +import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver; -/** - * A {@link AssemblyResolution} indicating the need to solve an expression in the future - * - *

- * Such records are collected within a {@link AssemblyResolvedPatterns} and then solved just before - * the final result(s) are assembled. This is typically required by instructions that refer to the - * {@code inst_next} symbol. - * - *

- * NOTE: These are used internally. The user ought never to see these from the assembly API. - */ -public class AssemblyResolvedBackfill extends AssemblyResolution { - protected final PatternExpression exp; - protected final MaskedLong goal; - protected final int inslen; - protected final int offset; - - @Override - protected int computeHash() { - int result = 0; - result += exp.hashCode(); - result *= 31; - result += goal.hashCode(); - result *= 31; - result += inslen; - result *= 31; - result += offset; - return result; - } - - /** - * @see {@link AssemblyResolution#backfill(PatternExpression, MaskedLong, Map, int, String)} - */ - AssemblyResolvedBackfill(String description, PatternExpression exp, MaskedLong goal, int inslen, - int offset) { - super(description, null, null); - this.exp = exp; - this.goal = goal; - this.inslen = inslen; - this.offset = offset; - } - - /** - * Duplicate this record - * - * @return the duplicate - */ - AssemblyResolvedBackfill copy() { - AssemblyResolvedBackfill cp = - new AssemblyResolvedBackfill(description, exp, goal, inslen, offset); - return cp; - } - - @Override - public AssemblyResolvedBackfill withRight(AssemblyResolution right) { - throw new AssertionError(); - } +public interface AssemblyResolvedBackfill extends AssemblyResolution { /** * Get the expected length of the instruction portion of the future encoding @@ -86,39 +29,15 @@ public class AssemblyResolvedBackfill extends AssemblyResolution { * * @return the total expected length (including the offset) */ - public int getInstructionLength() { - return offset + inslen; - } + int getInstructionLength(); @Override - public boolean isError() { - return false; - } - - @Override - public boolean isBackfill() { - return true; - } - - @Override - protected String lineToString() { - return "Backfill (len:" + inslen + ",off:" + offset + ") " + goal + " := " + exp + " (" + - description + ")"; - } - - @Override - public AssemblyResolvedBackfill shift(int amt) { - return new AssemblyResolvedBackfill(description, exp, goal, inslen, offset + amt); - } - - @Override - public AssemblyResolution parent(String description, int opCount) { - throw new AssertionError(); - } + AssemblyResolvedBackfill shift(int amt); /** * Attempt (again) to solve the expression that generated this backfill record * + *

* This will attempt to solve the same expression and goal again, using the same parameters as * were given to the original attempt, except with additional defined symbols. Typically, the * symbol that required backfill is {@code inst_next}. This method will not throw @@ -130,22 +49,6 @@ public class AssemblyResolvedBackfill extends AssemblyResolution { * @param vals the defined symbols, usually the same, but with the missing symbol(s). * @return the solution result */ - public AssemblyResolution solve(RecursiveDescentSolver solver, Map vals, - AssemblyResolvedPatterns cur) { - try { - AssemblyResolution ar = - solver.solve(exp, goal, vals, cur.truncate(offset), description); - if (ar.isError()) { - return ar; - } - AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; - return rc.shift(offset); - } - catch (NeedsBackfillException e) { - return AssemblyResolution.error("Solution still requires backfill", description); - } - catch (UnsupportedOperationException e) { - return AssemblyResolution.error("Unsupported: " + e.getMessage(), description); - } - } + AssemblyResolution solve(RecursiveDescentSolver solver, Map vals, + AssemblyResolvedPatterns cur); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedError.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedError.java index 4bee9da6e4..3e73da5c17 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedError.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedError.java @@ -15,83 +15,8 @@ */ package ghidra.app.plugin.assembler.sleigh.sem; -import java.util.List; +public interface AssemblyResolvedError extends AssemblyResolution { -/** - * A {@link AssemblyResolution} indicating the occurrence of a (usually semantic) error - * - *

- * The description should indicate where the error occurred. The error message should explain the - * actual error. To help the user diagnose the nature of the error, errors in sub-constructors - * should be placed as children of an error given by the parent constructor. - */ -public class AssemblyResolvedError extends AssemblyResolution { - protected final String error; + String getError(); - @Override - protected int computeHash() { - return error.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof AssemblyResolvedError)) { - return false; - } - AssemblyResolvedError that = (AssemblyResolvedError) obj; - if (!this.error.equals(that.error)) { - return false; - } - return true; - } - - /** - * @see AssemblyResolution#error(String, String, List) - */ - AssemblyResolvedError(String description, List children, - AssemblyResolution right, String error) { - super(description, children, right); - AssemblyTreeResolver.DBG.println(error); - this.error = error; - } - - @Override - public boolean isError() { - return true; - } - - @Override - public boolean isBackfill() { - return false; - } - - /** - * Get a description of the error - * - * @return the description - */ - public String getError() { - return error; - } - - @Override - public String lineToString() { - return error + " (" + description + ")"; - } - - @Override - public AssemblyResolution shift(int amt) { - return this; - } - - @Override - public AssemblyResolution withRight(AssemblyResolution right) { - return new AssemblyResolvedError(description, null, right, error); - } - - @Override - public AssemblyResolution parent(String description, int opCount) { - List allRight = getAllRight(); - return new AssemblyResolvedError(description, allRight, null, error); - } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedPatterns.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedPatterns.java index 84f47c5c67..0f54c2e6e6 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedPatterns.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyResolvedPatterns.java @@ -16,334 +16,96 @@ package ghidra.app.plugin.assembler.sleigh.sem; import java.util.*; -import java.util.concurrent.atomic.AtomicLong; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import org.apache.commons.collections4.IteratorUtils; -import org.apache.commons.collections4.Predicate; -import org.apache.commons.lang3.StringUtils; - -import ghidra.app.plugin.assembler.AssemblySelector; import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong; import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver; import ghidra.app.plugin.processors.sleigh.*; -import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol; -import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol; -/** - * A {@link AssemblyResolution} indicating successful application of a constructor - * - *

- * This is almost analogous to {@link ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern - * DisjointPattern}, in that is joins an instruction {@link AssemblyPatternBlock} with a - * corresponding context {@link AssemblyPatternBlock}. However, this object is mutable, and it - * collects backfill records, as well as forbidden patterns. - * - *

- * When the applied constructor is from the "instruction" subtable, this represents a fully- - * constructed instruction with required context. All backfill records ought to be resolved and - * applied before the final result is given to the user, i.e., passed into the - * {@link AssemblySelector}. If at any time during the resolution or backfill process, the result - * becomes confined to one of the forbidden patterns, it must be dropped, since the encoding will - * actually invoke a more specific SLEIGH constructor. - */ -public class AssemblyResolvedPatterns extends AssemblyResolution { - protected static final String INS = "ins:"; - protected static final String CTX = "ctx:"; - protected static final String SEP = ","; - - protected final Constructor cons; - protected final AssemblyPatternBlock ins; - protected final AssemblyPatternBlock ctx; - - protected final Set backfills; - protected final Set forbids; - - @Override - protected int computeHash() { - int result = 0; - result += ins.hashCode(); - result *= 31; - result += ctx.hashCode(); - result *= 31; - result += backfills.hashCode(); - result *= 31; - result += forbids.hashCode(); - return result; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof AssemblyResolvedPatterns)) { - return false; - } - AssemblyResolvedPatterns that = (AssemblyResolvedPatterns) obj; - if (!this.ins.equals(that.ins)) { - return false; - } - if (!this.ctx.equals(that.ctx)) { - return false; - } - if (!this.backfills.equals(that.backfills)) { - return false; - } - if (!this.forbids.equals(that.forbids)) { - return false; - } - return true; - } +public interface AssemblyResolvedPatterns extends AssemblyResolution { /** - * @see AssemblyResolution#resolved(AssemblyPatternBlock, AssemblyPatternBlock, String, Constructor, List, AssemblyResolution) + * Get the instruction block + * + * @return the instruction block */ - AssemblyResolvedPatterns(String description, Constructor cons, - List children, AssemblyResolution right, - AssemblyPatternBlock ins, AssemblyPatternBlock ctx, - Set backfills, Set forbids) { - super(description, children, right); - this.cons = cons; - this.ins = ins; - this.ctx = ctx; - this.backfills = backfills == null ? Set.of() : backfills; - this.forbids = forbids == null ? Set.of() : forbids; - } + AssemblyPatternBlock getInstruction(); /** - * Build a new successful SLEIGH constructor resolution from a string representation + * Get the context block + * + * @return the context block + */ + AssemblyPatternBlock getContext(); + + /** + * Get the length of the instruction encoding * *

- * This was used primarily in testing, to specify expected results. - * - * @param str the string representation: "{@code ins:[pattern],ctx:[pattern]}" - * @see ghidra.util.NumericUtilities#convertHexStringToMaskedValue(AtomicLong, AtomicLong, - * String, int, int, String) NumericUtilities.convertHexStringToMaskedValue(AtomicLong, - * AtomicLong, String, int, int, String) - * @param description a description of the resolution - * @param children any children involved in the resolution - * @return the decoded resolution - */ - public static AssemblyResolvedPatterns fromString(String str, String description, - List children) { - AssemblyPatternBlock ins = null; - if (str.startsWith(INS)) { - int end = str.indexOf(SEP); - if (end == -1) { - end = str.length(); - } - ins = AssemblyPatternBlock.fromString(str.substring(INS.length(), end)); - str = str.substring(end); - if (str.startsWith(SEP)) { - str = str.substring(1); - } - } - AssemblyPatternBlock ctx = null; - if (str.startsWith(CTX)) { - int end = str.length(); - ctx = AssemblyPatternBlock.fromString(str.substring(CTX.length(), end)); - str = str.substring(end); - } - if (str.length() != 0) { - throw new IllegalArgumentException(str); - } - return AssemblyResolution.resolved(// - ins == null ? AssemblyPatternBlock.nop() : ins,// - ctx == null ? AssemblyPatternBlock.nop() : ctx,// - description, null, children, null); - } - - @Override - public AssemblyResolvedPatterns shift(int amt) { - if (amt == 0) { - return this; - } - AssemblyPatternBlock newIns = this.ins.shift(amt); - - // Also shift the attached backfills and forbidden patterns - Set newBackfills = new HashSet<>(); - for (AssemblyResolvedBackfill bf : this.backfills) { - newBackfills.add(bf.shift(amt)); - } - - Set newForbids = new HashSet<>(); - for (AssemblyResolvedPatterns f : this.forbids) { - newForbids.add(f.shift(amt)); - } - return new AssemblyResolvedPatterns(description, cons, children, right, newIns, ctx, - Collections.unmodifiableSet(newBackfills), Collections.unmodifiableSet(newForbids)); - } - - /** - * Truncate (unshift) the resolved instruction pattern from the left - * - * NOTE: This drops all backfill and forbidden pattern records, since this method is - * typically used to read token fields rather than passed around for resolution. - * - * @param amt the number of bytes to remove from the left - * @return the result - */ - public AssemblyResolvedPatterns truncate(int amt) { - if (amt == 0) { - return this; - } - AssemblyPatternBlock newIns = this.ins.truncate(amt); - - return new AssemblyResolvedPatterns("Truncated: " + description, cons, null, right, - newIns, ctx, - null, null); - } - - /** - * Check if the current encoding is forbidden by one of the attached patterns + * This is used to ensure each operand is encoded at the correct offset * *

- * The pattern becomes forbidden if this encoding's known bits are an overset of any forbidden - * pattern's known bits. + * NOTE: this DOES include the offset
+ * NOTE: this DOES include pending backfills * - * @return false if the pattern is forbidden (and thus in error), true if permitted + * @return the length of the instruction block */ - public AssemblyResolution checkNotForbidden() { - Set newForbids = new HashSet<>(); - for (AssemblyResolvedPatterns f : this.forbids) { - AssemblyResolvedPatterns check = this.combine(f); - if (null == check) { - continue; - } - newForbids.add(f); - if (check.bitsEqual(this)) { - // The result would be disassembled by a more-specific constructor. - return AssemblyResolution.error("The result is forbidden by " + f, this); - } - } - return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx, - backfills, Collections.unmodifiableSet(newForbids)); - } + int getInstructionLength(); /** - * Check if this and another resolution have equal encodings + * Get the length of the instruction encoding, excluding trailing undefined bytes * *

- * This is like {@link #equals(Object)}, but it ignores backfill records and forbidden patterns. + * NOTE: this DOES include the offset
+ * NOTE: this DOES NOT include pending backfills * - * @param that the other resolution - * @return true if both have equal encodings + * @return the length of the defined bytes in the instruction block */ - protected boolean bitsEqual(AssemblyResolvedPatterns that) { - return this.ins.equals(that.ins) && this.ctx.equals(that.ctx); - } + int getDefinedInstructionLength(); /** - * Combine the encodings and backfills of the given resolution into this one + * Get the backfill records for this resolution, if any + * + * @return the backfills + */ + Collection getBackfills(); + + /** + * Check if this resolution has pending backfills to apply + * + * @return true if there are backfills + */ + boolean hasBackfills(); + + /** + * Get the forbidden patterns for this resolution * *

- * This combines corresponding pattern blocks (assuming they agree), collects backfill records, - * and collects forbidden patterns. + * These represent patterns included in the current resolution that would actually get matched + * by a more specific constructor somewhere in the resolved tree, and thus are subtracted. * - * @param that the other resolution - * @return the result if successful, or null + * @return the forbidden patterns */ - public AssemblyResolvedPatterns combine(AssemblyResolvedPatterns that) { - // Not really a backfill, but I would like to re-use code - return combineLessBackfill(that, null); - } + Collection getForbids(); /** - * Combine a backfill result + * Decode a portion of the instruction block * - *

- * When a backfill is successful, the result should be combined with the owning resolution. In - * addition, for bookkeeping's sake, the resolved record should be removed from the list of - * backfills. - * - * @param that the result from backfilling - * @param bf the resolved backfilled record - * @return the result if successful, or null + * @param start the first byte to decode + * @param len the number of bytes to decode + * @return the read masked value + * @see AssemblyPatternBlock#readBytes(int, int) */ - protected AssemblyResolvedPatterns combineLessBackfill(AssemblyResolvedPatterns that, - AssemblyResolvedBackfill bf) { - AssemblyPatternBlock newIns = this.ins.combine(that.ins); - if (newIns == null) { - return null; - } - AssemblyPatternBlock newCtx = this.ctx.combine(that.ctx); - if (newCtx == null) { - return null; - } - Set newBackfills = new HashSet<>(this.backfills); - newBackfills.addAll(that.backfills); - if (bf != null) { - newBackfills.remove(bf); - } - Set newForbids = new HashSet<>(this.forbids); - newForbids.addAll(that.forbids); - return new AssemblyResolvedPatterns(description, cons, children, right, newIns, newCtx, - Collections.unmodifiableSet(newBackfills), Collections.unmodifiableSet(newForbids)); - } + MaskedLong readInstruction(int byteStart, int size); /** - * Combine the given backfill record into this resolution + * Decode a portion of the context block * - * @param bf the backfill record - * @return the result + * @param start the first byte to decode + * @param len the number of bytes to decode + * @return the read masked value + * @see AssemblyPatternBlock#readBytes(int, int) */ - public AssemblyResolvedPatterns combine(AssemblyResolvedBackfill bf) { - Set newBackfills = new HashSet<>(this.backfills); - newBackfills.add(bf); - return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx, - Collections.unmodifiableSet(newBackfills), forbids); - } - - /** - * Create a new resolution from this one with the given forbidden patterns recorded - * - * @param more the additional forbidden patterns to record - * @return the new resolution - */ - public AssemblyResolvedPatterns withForbids(Set more) { - Set combForbids = new HashSet<>(this.forbids); - combForbids.addAll(more); - return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx, - backfills, Collections.unmodifiableSet(more)); - } - - /** - * Create a copy of this resolution with a new description - * - * @param desc the new description - * @return the copy - */ - public AssemblyResolvedPatterns withDescription(String desc) { - return new AssemblyResolvedPatterns(desc, cons, children, right, ins, ctx, backfills, - forbids); - } - - /** - * Create a copy of this resolution with a replaced constructor - * - * @param cons the new constructor - * @return the copy - */ - public AssemblyResolvedPatterns withConstructor(Constructor cons) { - return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx, - backfills, - forbids); - } - - /** - * Encode the given value into the context block as specified by an operation - * - * @param cop the context operation specifying the location of the value to encode - * @param val the masked value to encode - * @return the result - * - * This is the forward (as in disassembly) direction of applying context operations. The - * pattern expression is evaluated, and the result is written as specified. - */ - public AssemblyResolvedPatterns writeContextOp(ContextOp cop, MaskedLong val) { - AssemblyPatternBlock newCtx = this.ctx.writeContextOp(cop, val); - return new AssemblyResolvedPatterns(description, cons, children, right, ins, newCtx, - backfills, forbids); - } + MaskedLong readContext(int start, int len); /** * Decode the value from the context located where the given context operation would write @@ -358,55 +120,93 @@ public class AssemblyResolvedPatterns extends AssemblyResolution { * @param cop the context operation whose "variable" to read. * @return the masked result. */ - public MaskedLong readContextOp(ContextOp cop) { - return ctx.readContextOp(cop); - } + MaskedLong readContextOp(ContextOp cop); /** - * Duplicate this resolution, with additional description text appended + * Check if this and another resolution have equal encodings * - * @param append the text to append - * @return the duplicate NOTE: An additional separator {@code ": "} is inserted + *

+ * This is like {@link #equals(Object)}, but it ignores backfill records and forbidden patterns. + * + * @param that the other resolution + * @return true if both have equal encodings */ - public AssemblyResolvedPatterns copyAppendDescription(String append) { - AssemblyResolvedPatterns cp = new AssemblyResolvedPatterns( - description + ": " + append, cons, children, right, ins.copy(), ctx.copy(), backfills, - forbids); - return cp; - } - - @Override - public AssemblyResolvedPatterns withRight(AssemblyResolution right) { - AssemblyResolvedPatterns cp = new AssemblyResolvedPatterns(description, cons, - children, right, ins.copy(), ctx.copy(), backfills, forbids); - return cp; - } - - public AssemblyResolvedPatterns nopLeftSibling() { - return new AssemblyResolvedPatterns("nop-left", null, null, this, ins.copy(), - ctx.copy(), backfills, forbids); - } - - @Override - public AssemblyResolvedPatterns parent(String description, int opCount) { - List allRight = getAllRight(); - AssemblyResolvedPatterns cp = new AssemblyResolvedPatterns(description, cons, - allRight.subList(0, opCount), allRight.get(opCount), ins, ctx, backfills, forbids); - return cp; - } + boolean bitsEqual(AssemblyResolvedPatterns that); /** - * Set all bits read by a given context operation to unknown + * Check if this assembled construct state is the same as the given dis-assembled construct + * state. + */ + boolean equivalentConstructState(ConstructState state); + + @Override + AssemblyResolvedPatterns shift(int shamt); + + /** + * Create a copy of this resolution with a new description * - * @param cop the context operation + * @param desc the new description + * @return the copy + */ + AssemblyResolvedPatterns withDescription(String description); + + /** + * Create a copy of this resolution with a sibling to the right + * + *

+ * The right sibling is a mechanism for collecting children of a parent yet to be created. See + * {@link #parent(String, int)}. + * + * @param right the right sibling + * @return the new resolution + */ + AssemblyResolvedPatterns withRight(AssemblyResolution right); + + /** + * Create a copy of this resolution with a replaced constructor + * + * @param cons the new constructor + * @return the copy + */ + AssemblyResolvedPatterns withConstructor(Constructor cons); + + /** + * Combine the encodings and backfills of the given resolution into this one + * + *

+ * This combines corresponding pattern blocks (assuming they agree), collects backfill records, + * and collects forbidden patterns. + * + * @param that the other resolution + * @return the result if successful, or null + */ + AssemblyResolvedPatterns combine(AssemblyResolvedPatterns pat); + + /** + * Combine the given backfill record into this resolution + * + * @param bf the backfill record * @return the result - * @see AssemblyPatternBlock#maskOut(ContextOp) */ - public AssemblyResolvedPatterns maskOut(ContextOp cop) { - AssemblyPatternBlock newCtx = this.ctx.maskOut(cop); - return new AssemblyResolvedPatterns(description, cons, children, right, ins, newCtx, - backfills, forbids); - } + AssemblyResolvedPatterns combine(AssemblyResolvedBackfill bf); + + /** + * Combine a backfill result + * + *

+ * When a backfill is successful, the result should be combined with the owning resolution. In + * addition, for bookkeeping's sake, the resolved record should be removed from the list of + * backfills. + * + * @param that the result from backfilling + * @param bf the resolved backfilled record + * @return the result if successful, or null + */ + AssemblyResolvedPatterns combineLessBackfill(AssemblyResolvedPatterns that, + AssemblyResolvedBackfill bf); + + @Override + AssemblyResolvedPatterns parent(String description, int opCount); /** * Apply as many backfill records as possible @@ -422,52 +222,29 @@ public class AssemblyResolvedPatterns extends AssemblyResolution { * @param vals the values. * @return the result, or an error. */ - public AssemblyResolution backfill(RecursiveDescentSolver solver, Map vals) { - if (!hasBackfills()) { - return this; - } - - AssemblyResolvedPatterns res = this; - loop: while (true) { - for (AssemblyResolvedBackfill bf : res.backfills) { - AssemblyResolution ar = bf.solve(solver, vals, this); - if (ar.isError()) { - continue; - } - AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; - AssemblyResolvedPatterns check = res.combineLessBackfill(rc, bf); - if (check == null) { - return AssemblyResolution.error("Conflict: Backfill " + bf.description, res); - } - res = check; - continue loop; - } - return res; - } - } - - @Override - public String lineToString() { - return dumpConstructorTree() + ":" + INS + ins + SEP + CTX + ctx + " (" + description + ")"; - } + AssemblyResolution backfill(RecursiveDescentSolver solver, Map vals); /** - * Check if this resolution has pending backfills to apply + * Check if the current encoding is forbidden by one of the attached patterns * - * @return true if there are backfills + *

+ * The pattern becomes forbidden if this encoding's known bits are an overset of any forbidden + * pattern's known bits. + * + * @return false if the pattern is forbidden (and thus in error), true if permitted */ - public boolean hasBackfills() { - return !backfills.isEmpty(); - } + AssemblyResolution checkNotForbidden(); /** - * Check if this resolution includes forbidden patterns + * Generate a new nop right this resolution to its right. * - * @return true if there are forbidden patterns + *

+ * Alternatively phrased: append a nop to the left of this list of siblings, returning the new + * head. + * + * @return the nop resolution */ - private boolean hasForbids() { - return !forbids.isEmpty(); - } + AssemblyResolvedPatterns nopLeftSibling(); /** * Solve and apply context changes in reverse to forbidden patterns @@ -484,193 +261,8 @@ public class AssemblyResolvedPatterns extends AssemblyResolution { * @return the result * @see AssemblyConstructorSemantic#solveContextChanges(AssemblyResolvedPatterns, Map) */ - public AssemblyResolvedPatterns solveContextChangesForForbids( - AssemblyConstructorSemantic sem, Map vals) { - if (!hasForbids()) { - return this; - } - Set newForbids = new HashSet<>(); - for (AssemblyResolvedPatterns f : this.forbids) { - AssemblyResolution t = sem.solveContextChanges(f, vals); - if (!(t instanceof AssemblyResolvedPatterns)) { - // Can't be solved, so it can be dropped - continue; - } - newForbids.add((AssemblyResolvedPatterns) t); - } - return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx, - backfills, Collections.unmodifiableSet(newForbids)); - } - - /** - * Get the length of the instruction encoding - * - *

- * This is used to ensure each operand is encoded at the correct offset - * - *

- * NOTE: this DOES include the offset
- * NOTE: this DOES include pending backfills - * - * @return the length of the instruction block - */ - public int getInstructionLength() { - int inslen = ins.length(); - for (AssemblyResolvedBackfill bf : backfills) { - inslen = Math.max(inslen, bf.getInstructionLength()); - } - return inslen; - } - - /** - * Get the length of the instruction encoding, excluding trailing undefined bytes - * - *

- * NOTE: this DOES include the offset
- * NOTE: this DOES NOT include pending backfills - * - * @return the length of the defined bytes in the instruction block - */ - public int getDefinedInstructionLength() { - byte[] imsk = ins.getMask(); - int i; - for (i = imsk.length - 1; i >= 0; i--) { - if (imsk[i] != 0) { - break; - } - } - return ins.getOffset() + i + 1; - } - - /** - * Get the instruction block - * - * @return the instruction block - */ - public AssemblyPatternBlock getInstruction() { - return ins; - } - - /** - * Get the context block - * - * @return the context block - */ - public AssemblyPatternBlock getContext() { - return ctx; - } - - /** - * Decode a portion of the instruction block - * - * @param start the first byte to decode - * @param len the number of bytes to decode - * @return the read masked value - * @see AssemblyPatternBlock#readBytes(int, int) - */ - public MaskedLong readInstruction(int start, int len) { - return ins.readBytes(start, len); - } - - /** - * Decode a portion of the context block - * - * @param start the first byte to decode - * @param len the number of bytes to decode - * @return the read masked value - * @see AssemblyPatternBlock#readBytes(int, int) - */ - public MaskedLong readContext(int start, int len) { - return ctx.readBytes(start, len); - } - - @Override - public boolean isError() { - return false; - } - - @Override - public boolean isBackfill() { - return false; - } - - @Override - public boolean hasChildren() { - return super.hasChildren() || hasBackfills() || hasForbids(); - } - - @Override - protected String childrenToString(String indent) { - StringBuilder sb = new StringBuilder(); - if (super.hasChildren()) { - sb.append(super.childrenToString(indent) + "\n"); - } - for (AssemblyResolvedBackfill bf : backfills) { - sb.append(indent); - sb.append("backfill: " + bf + "\n"); - } - for (AssemblyResolvedPatterns f : forbids) { - sb.append(indent); - sb.append("forbidden: " + f + "\n"); - } - return sb.substring(0, sb.length() - 1); - } - - protected static final Pattern pat = Pattern.compile("line(\\d*)"); - - /** - * Used for testing and diagnostics: list the constructor line numbers used to resolve this - * encoding - * - *

- * This includes braces to describe the tree structure - * - * @see ConstructState#dumpConstructorTree() - * @return the constructor tree - */ - public String dumpConstructorTree() { - StringBuilder sb = new StringBuilder(); - if (cons == null) { - return null; - } - sb.append(cons.getSourceFile() + ":" + cons.getLineno()); - - if (children == null) { - return sb.toString(); - } - - List subs = new ArrayList<>(); - for (AssemblyResolution c : children) { - if (c instanceof AssemblyResolvedPatterns) { - AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) c; - String s = rc.dumpConstructorTree(); - if (s != null) { - subs.add(s); - } - } - } - - if (subs.isEmpty()) { - return sb.toString(); - } - sb.append('['); - sb.append(StringUtils.join(subs, ",")); - sb.append(']'); - return sb.toString(); - } - - /** - * Count the number of bits specified in the resolution patterns - * - *

- * Totals the specificity of the instruction and context pattern blocks. - * - * @return the number of bits in the resulting patterns - * @see AssemblyPatternBlock#getSpecificity() - */ - public int getSpecificity() { - return ins.getSpecificity() + ctx.getSpecificity(); - } + AssemblyResolvedPatterns solveContextChangesForForbids(AssemblyConstructorSemantic sem, + Map vals); /** * Get an iterable over all the possible fillings of the instruction pattern given a context @@ -700,107 +292,58 @@ public class AssemblyResolvedPatterns extends AssemblyResolution { * @param forCtx the context at the assembly address * @return the iterable */ - public Iterable possibleInsVals(AssemblyPatternBlock forCtx) { - AssemblyPatternBlock ctxCompat = ctx.combine(forCtx); - if (ctxCompat == null) { - return List.of(); - } - Predicate removeForbidden = (byte[] val) -> { - for (AssemblyResolvedPatterns f : forbids) { - // If the forbidden length is larger than us, we can ignore it - if (f.getDefinedInstructionLength() > val.length) { - continue; - } - // Check if the context matches, if not, we can let it pass - if (null == f.getContext().combine(forCtx)) { - continue; - } - // If the context matches, now check the instruction - AssemblyPatternBlock i = f.getInstruction(); - AssemblyPatternBlock vi = - AssemblyPatternBlock.fromBytes(ins.length() - val.length, val); - if (null == i.combine(vi)) { - continue; - } - return false; - } - return true; - }; - return new Iterable() { - @Override - public Iterator iterator() { - return IteratorUtils.filteredIterator(ins.possibleVals().iterator(), - removeForbidden); - } - }; - } - - protected static int getOpIndex(String piece) { - if (piece.charAt(0) != '\n') { - return -1; - } - return piece.charAt(1) - 'A'; - } + Iterable possibleInsVals(AssemblyPatternBlock forCtx); /** - * If the construct state is a {@code ^instruction} or other purely-recursive constructor, get - * its single child. + * Used for testing and diagnostics: list the constructor line numbers used to resolve this + * encoding * - * @param state the parent state - * @return the child state if recursive, or null + *

+ * This includes braces to describe the tree structure + * + * @see ConstructState#dumpConstructorTree() + * @return the constructor tree */ - protected static ConstructState getPureRecursion(ConstructState state) { - // NB. There can be other operands, but only one can be printed - // Furthermore, nothing else can be printed, whether an operand or not - List pieces = state.getConstructor().getPrintPieces(); - if (pieces.size() != 1) { - return null; - } - int opIdx = getOpIndex(pieces.get(0)); - if (opIdx < 0) { - return null; - } - ConstructState sub = state.getSubState(opIdx); - if (sub == null || sub.getConstructor() == null || - sub.getConstructor().getParent() != state.getConstructor().getParent()) { - // not recursive - return null; - } - return sub; - } + String dumpConstructorTree(); - public boolean equivalentConstructState(ConstructState state) { - ConstructState rec = getPureRecursion(state); - if (rec != null) { - if (state.getConstructor() == cons) { - assert children.size() == 1; - AssemblyResolvedPatterns recRes = (AssemblyResolvedPatterns) children.get(0); - return recRes.equivalentConstructState(rec); - } - return equivalentConstructState(rec); - } - if (state.getConstructor() != cons) { - return false; - } - int opCount = cons.getNumOperands(); - for (int opIdx = 0; opIdx < opCount; opIdx++) { - OperandSymbol opSym = cons.getOperand(opIdx); - Set printed = - Arrays.stream(cons.getOpsPrintOrder()).boxed().collect(Collectors.toSet()); - if (!(opSym.getDefiningSymbol() instanceof SubtableSymbol)) { - AssemblyTreeResolver.DBG.println("Operand " + opSym + " is not a sub-table"); - continue; - } - if (!printed.contains(opIdx)) { - AssemblyTreeResolver.DBG.println("Operand " + opSym + " is hidden"); - continue; - } - AssemblyResolvedPatterns child = (AssemblyResolvedPatterns) children.get(opIdx); - ConstructState subState = state.getSubState(opIdx); - if (!child.equivalentConstructState(subState)) { - return false; - } - } - return true; - } + /** + * Truncate (unshift) the resolved instruction pattern from the left + * + * NOTE: This drops all backfill and forbidden pattern records, since this method is + * typically used to read token fields rather than passed around for resolution. + * + * @param amt the number of bytes to remove from the left + * @return the result + */ + AssemblyResolvedPatterns truncate(int shamt); + + /** + * Create a new resolution from this one with the given forbidden patterns recorded + * + * @param more the additional forbidden patterns to record + * @return the new resolution + */ + AssemblyResolvedPatterns withForbids(Set more); + + /** + * Set all bits read by a given context operation to unknown + * + * @param cop the context operation + * @return the result + * @see AssemblyPatternBlock#maskOut(ContextOp) + */ + AssemblyResolvedPatterns maskOut(ContextOp cop); + + /** + * Encode the given value into the context block as specified by an operation + * + *

+ * This is the forward (as in disassembly) direction of applying context operations. The pattern + * expression is evaluated, and the result is written as specified. + * + * @param cop the context operation specifying the location of the value to encode + * @param val the masked value to encode + * @return the result + */ + AssemblyResolvedPatterns writeContextOp(ContextOp cop, MaskedLong val); } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyStringStateGenerator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyStringStateGenerator.java new file mode 100644 index 0000000000..9dcb85897d --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyStringStateGenerator.java @@ -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.sem; + +import java.util.stream.Stream; + +import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseToken; +import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol; + +public class AssemblyStringStateGenerator + extends AbstractAssemblyStateGenerator { + protected final OperandSymbol opSym; + + public AssemblyStringStateGenerator(AbstractAssemblyTreeResolver resolver, + AssemblyParseToken node, OperandSymbol opSym, AssemblyResolvedPatterns fromLeft) { + super(resolver, node, fromLeft); + this.opSym = opSym; + } + + @Override + public Stream generate(GeneratorContext gc) { + return Stream.of(new AssemblyGeneratedPrototype( + new AssemblyOperandState(resolver, gc.path, gc.shift, node.getSym(), 0, opSym), + fromLeft)); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyTreeResolver.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyTreeResolver.java index e66b275be6..a5ea642f8c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyTreeResolver.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyTreeResolver.java @@ -15,550 +15,16 @@ */ package ghidra.app.plugin.assembler.sleigh.sem; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import ghidra.app.plugin.assembler.sleigh.SleighAssemblerBuilder; -import ghidra.app.plugin.assembler.sleigh.expr.*; -import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar; -import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyProduction; -import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyStateGenerator.GeneratorContext; -import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolutionResults.Applicator; -import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal; -import ghidra.app.plugin.assembler.sleigh.tree.*; -import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; -import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx; -import ghidra.app.plugin.processors.sleigh.*; -import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; -import ghidra.app.plugin.processors.sleigh.symbol.*; +import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch; +import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.program.model.address.Address; -import ghidra.program.model.lang.InsufficientBytesException; -import ghidra.program.model.lang.UnknownInstructionException; -import ghidra.program.model.mem.ByteMemBufferImpl; -import ghidra.program.model.mem.MemBuffer; -/** - * The workhorse of semantic resolution for the assembler - * - *

- * This class takes a parse tree and some additional information (start address, context, etc.) and - * attempts to determine possible encodings using the semantics associated with each branch of the - * given parse tree. Details of this process are described in {@link SleighAssemblerBuilder}. - * - * @see SleighAssemblerBuilder - */ -public class AssemblyTreeResolver { - protected static final RecursiveDescentSolver SOLVER = RecursiveDescentSolver.getSolver(); - protected static final DbgTimer DBG = DbgTimer.INACTIVE; +public class AssemblyTreeResolver extends AbstractAssemblyTreeResolver { - public static final String INST_START = "inst_start"; - public static final String INST_NEXT = "inst_next"; - public static final String INST_NEXT2 = "inst_next2"; - - protected final SleighLanguage lang; - protected final Address at; - protected final Map vals = new HashMap<>(); - protected final AssemblyParseBranch tree; - protected final AssemblyGrammar grammar; - protected final AssemblyPatternBlock context; - protected final AssemblyContextGraph ctxGraph; - - /** - * Construct a resolver for the given parse tree - * - * @param lang - * @param at the address where the instruction will start - * @param tree the parse tree - * @param context the context expected at {@code instStart} - * @param ctxGraph the context transition graph used to resolve purely-recursive productions - */ - public AssemblyTreeResolver(SleighLanguage lang, Address at, AssemblyParseBranch tree, - AssemblyPatternBlock context, AssemblyContextGraph ctxGraph) { - this.lang = lang; - this.at = at; - this.vals.put(INST_START, at.getAddressableWordOffset()); - this.tree = tree; - this.grammar = tree.getGrammar(); - this.context = context.fillMask(); - this.ctxGraph = ctxGraph; - } - - /** - * Resolve the tree for the given parameters - * - * @return a set of resolutions (encodings and errors) - */ - public AssemblyResolutionResults resolve() { - AssemblyResolvedPatterns empty = AssemblyResolution.nop("Empty"); - AssemblyConstructStateGenerator rootGen = - new AssemblyConstructStateGenerator(this, tree, empty); - - Collection errors = new ArrayList<>(); - Stream protStream = - rootGen.generate(new GeneratorContext(List.of(), 0)); - - if (DBG == DbgTimer.ACTIVE) { - try (DbgCtx dc = DBG.start("Prototypes:")) { - protStream = protStream.map(prot -> { - DBG.println(prot); - return prot; - }).collect(Collectors.toList()).stream(); - } - } - - Stream patStream = - protStream.map(p -> p.state).distinct().flatMap(s -> s.resolve(empty, errors)); - - AssemblyResolutionResults results = new AssemblyResolutionResults(); - patStream.forEach(results::add); - - results = resolveRootRecursion(results); - results = selectContext(results); - results = resolvePendingBackfills(results); - // TODO: Remove this? It's subsumed by filterByDisassembly, and more accurately.... - results = filterForbidden(results); - results = filterByDisassembly(results); - results.addAll(errors); - return results; - } - - /** - * If applicable, get the {@code I => I} production of the grammar - * - * @return the production - */ - protected AssemblyProduction getRootRecursion() { - assert tree.getParent() == null; - AssemblyProduction rootProd = tree.getProduction(); - AssemblyNonTerminal start = rootProd.getLHS(); - AssemblyProduction rec = grammar.getPureRecursion(start); - return rec; - } - - /** - * If necessary, resolve recursive constructors at the root, usually for prefixes - * - *

- * If there are no pure recursive constructors at the root, then this simply returns - * {@code temp} unmodified. - * - * @param temp the resolved root results - * @return the results with pure recursive constructors applied to obtain a compatible context - */ - // Ugh, public so I can refer to it in javadocs... - public AssemblyResolutionResults resolveRootRecursion(AssemblyResolutionResults temp) { - AssemblyProduction rootRec = getRootRecursion(); - if (rootRec == null) { - return temp; - } - try (DbgCtx dc = DBG.start("Resolving root recursion:")) { - AssemblyResolutionResults result = new AssemblyResolutionResults(); - - for (AssemblyResolution ar : temp) { - if (ar.isError()) { - result.add(ar); - continue; - } - AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; - AssemblyPatternBlock dst = rc.getContext(); - // TODO: The desired context may need to be passed in. For now, just take start. - AssemblyPatternBlock src = context; // NOTE: This is only correct for "instruction" - String table = "instruction"; - - DBG.println("Finding paths from " + src + " to " + ar.lineToString()); - Collection> paths = - ctxGraph.computeOptimalApplications(src, table, dst, table); - DBG.println("Found " + paths.size()); - for (Deque path : paths) { - DBG.println(" " + path); - result.absorb(applyRecursionPath(path, tree, rootRec, rc)); - } - } - - return result; - } - } - - /** - * Attempt a second time to solve operands and context changes - * - *

- * Backfills that depended on {@code inst_next} should now easily be solved, since the - * instruction length is now known. - * - * @param temp the resolved results, with backfill pending - * @return the results without backfill, possible with new errors - */ - protected AssemblyResolutionResults resolvePendingBackfills(AssemblyResolutionResults temp) { - return temp.apply(rc -> { - if (!rc.hasBackfills()) { - return rc; - } - vals.put(INST_NEXT, at.add(rc.getInstructionLength()).getAddressableWordOffset()); - // inst_next2 use not really supported - vals.put(INST_NEXT2, at.add(rc.getInstructionLength()).getAddressableWordOffset()); - DBG.println("Backfilling: " + rc); - AssemblyResolution ar = rc.backfill(SOLVER, vals); - DBG.println("Backfilled final: " + ar); - return ar; - }).apply(rc -> { - if (rc.hasBackfills()) { - return AssemblyResolution.error("Solution is incomplete", "failed backfill", - List.of(rc), null); - } - return rc; - }); - } - - /** - * Filter out results whose context do not match that requested - * - * @param temp the results whose contexts have not yet been checked - * @return the results that pass. Those that do not are replaced with errors. - */ - protected AssemblyResolutionResults selectContext(AssemblyResolutionResults temp) { - AssemblyResolvedPatterns ctx = - AssemblyResolution.contextOnly(context, "Selecting context"); - return temp.apply(rc -> { - AssemblyResolvedPatterns check = rc.combine(ctx); - if (null == check) { - return AssemblyResolution.error("Incompatible context", "resolving", List.of(rc), - null); - } - return check; - }); - } - - /** - * Filter out results that would certainly be disassembled differently than assembled - * - *

- * Because of constructor precedence rules, it is possible to assemble a pattern from a - * prototype that would not result in equivalent disassembly. This can be detected in some cases - * via the "forbids" mechanism, where more specific constructors are recorded with the result. - * If the generated pattern matches on of those more-specific constructors, it is forbidden. - * - * @param temp the results whose forbids have not yet been checked - * @return the results that pass. Those that do not are replaced with errors. - */ - protected AssemblyResolutionResults filterForbidden(AssemblyResolutionResults temp) { - return temp.apply(rc -> rc.checkNotForbidden()); - } - - /** - * Filter out results that get disassembled differently than assembled - * - *

- * The forbids mechanism is not perfect, so as a final fail safe, we disassemble the result and - * compare the prototypes. - * - * @param temp the results whose disassemblies have not yet been checked - * @return the results that pass. Those that do not are replaced with errors. - */ - protected AssemblyResolutionResults filterByDisassembly(AssemblyResolutionResults temp) { - AssemblyDefaultContext asmCtx = new AssemblyDefaultContext(lang); - asmCtx.setContextRegister(context); - return temp.apply(rc -> { - MemBuffer buf = - new ByteMemBufferImpl(at, rc.getInstruction().getVals(), lang.isBigEndian()); - try { - SleighInstructionPrototype ip = - (SleighInstructionPrototype) lang.parse(buf, asmCtx, false); - if (!rc.equivalentConstructState(ip.getRootState())) { - return AssemblyResolution.error("Disassembly prototype mismatch", rc); - } - return rc; - } - catch (InsufficientBytesException | UnknownInstructionException e) { - return AssemblyResolution.error("Disassembly failed: " + e.getMessage(), rc); - } - }); - } - - /** - * Get the state generator for a given operand and parse tree node - * - * @param opSym the operand symbol - * @param node the corresponding parse tree node, possibly null indicating a hidden operand - * @param fromLeft the accumulated patterns from the left sibling or parent - * @return the generator - */ - protected AbstractAssemblyStateGenerator getStateGenerator(OperandSymbol opSym, - AssemblyParseTreeNode node, AssemblyResolvedPatterns fromLeft) { - if (node == null) { - return getHiddenStateGenerator(opSym, fromLeft); - } - if (node.isNumeric()) { - return new AssemblyOperandStateGenerator(this, (AssemblyParseNumericToken) node, opSym, - fromLeft); - } - if (node.isConstructor()) { - return new AssemblyConstructStateGenerator(this, (AssemblyParseBranch) node, fromLeft); - } - throw new AssertionError(); - } - - /** - * Get the state generator for a hidden operand - * - * @param opSym the operand symbol - * @param fromLeft the accumulated patterns from the left sibling or parent - * @return the generator - */ - protected AbstractAssemblyStateGenerator getHiddenStateGenerator(OperandSymbol opSym, - AssemblyResolvedPatterns fromLeft) { - TripleSymbol defSym = opSym.getDefiningSymbol(); - if (defSym instanceof SubtableSymbol) { - return new AssemblyHiddenConstructStateGenerator(this, (SubtableSymbol) defSym, - fromLeft); - } - return new AssemblyNopStateGenerator(this, opSym, fromLeft); - } - - /** - * Apply a constructor pattern - * - *

- * TODO: This is currently used only for resolving recursion. Could this be factored with - * {@link AssemblyConstructState#resolve(AssemblyResolvedPatterns, Collection)}? - * - * @param sem the SLEIGH constructor - * @param shift the shift - * @param fromChildren the results from the single resolved child - * @return the results - */ - protected AssemblyResolutionResults resolvePatterns(AssemblyConstructorSemantic sem, int shift, - AssemblyResolutionResults fromChildren) { - AssemblyResolutionResults results = fromChildren; - results = applyMutations(sem, results); - results = applyPatterns(sem, shift, results); - results = tryResolveBackfills(results); - return results; - } - - /** - * TODO: Can this be factored? - */ - protected AssemblyResolutionResults parent(String description, AssemblyResolutionResults temp, - int opCount) { - return temp.stream() - .map(r -> r.parent(description, opCount)) - .collect(Collectors.toCollection(AssemblyResolutionResults::new)); - } - - /** - * TODO: This is currently used only for resolving recursion. Could this be factored with - * {@link AssemblyConstructState#resolveMutations(AssemblyResolvedPatterns, Collection)}? - */ - protected AssemblyResolutionResults applyMutations(AssemblyConstructorSemantic sem, - AssemblyResolutionResults temp) { - DBG.println("Applying context mutations:"); - return temp.apply(rc -> { - DBG.println("Current: " + rc.lineToString()); - AssemblyResolution backctx = sem.solveContextChanges(rc, vals); - DBG.println("Mutated: " + backctx.lineToString()); - return backctx; - }).apply(rc -> { - return rc.solveContextChangesForForbids(sem, vals); - }); - } - - /** - * TODO: This is currently used only for resolving recursion. Could this be factored with - * {@link AssemblyConstructState#resolvePatterns(AssemblyResolvedPatterns, Collection)}? - */ - protected AssemblyResolutionResults applyPatterns(AssemblyConstructorSemantic sem, int shift, - AssemblyResolutionResults temp) { - DBG.println("Applying patterns:"); - Collection patterns = - sem.getPatterns().stream().map(p -> p.shift(shift)).collect(Collectors.toList()); - return temp.apply(new Applicator() { - @Override - public Iterable getPatterns( - AssemblyResolvedPatterns cur) { - return patterns; - } - - @Override - public AssemblyResolvedPatterns setRight(AssemblyResolvedPatterns res, - AssemblyResolvedPatterns cur) { - // This is typically applied by parent, so don't insert sibling - return res; - } - - @Override - public String describeError(AssemblyResolvedPatterns rc, AssemblyResolution pat) { - return "The patterns conflict " + pat.lineToString(); - } - - @Override - public AssemblyResolvedPatterns combineBackfill(AssemblyResolvedPatterns cur, - AssemblyResolvedBackfill bf) { - throw new AssertionError(); - } - - @Override - public AssemblyResolution finish(AssemblyResolvedPatterns resolved) { - return resolved.checkNotForbidden(); - } - }); - } - - /** - * Apply constructors as indicated by a path returned by the context resolution graph - * - *

- * NOTE: The given path will be emptied during processing. - * - * @param path the path to apply - * @param branch the branch corresponding to the production whose LHS has a purely-recursive - * definition. - * @param rec the purely-recursive production - * @param child the intermediate result to apply the constructors to - * @return the results - */ - protected AssemblyResolutionResults applyRecursionPath(Deque path, - AssemblyParseBranch branch, AssemblyProduction rec, AssemblyResolvedPatterns child) { - /* - * A constructor may have multiple patterns, so I cannot assume I will get at most one - * output at each constructor in the path. Start (1) collecting all the results, then (2) - * filter out and report the errors, then (3) feed successful resolutions into the next - * constructor in the path (or finish). - */ - AssemblyResolutionResults results = new AssemblyResolutionResults(); - results.add(child); - while (!path.isEmpty()) { - AssemblyConstructorSemantic sem = path.pollLast(); - - int opIdx = sem.getOperandIndex(0); - Constructor cons = sem.getConstructor(); - OperandSymbol opSym = cons.getOperand(opIdx); - if (-1 != opSym.getOffsetBase()) { - throw new AssertionError("TODO"); - } - int offset = opSym.getRelativeOffset(); - results = parent("Resolving recursive constructor: " + cons.getSourceFile() + ":" + - cons.getLineno(), results, 1); - results = results.apply(rc -> rc.shift(offset)); - results = resolvePatterns(sem, 0, results).apply(rc -> rc.withConstructor(cons)); - } - return results; - } - - /** - * TODO: This is currently used only for resolving recursion. It seems its missing from the - * refactor? - */ - protected AssemblyResolutionResults tryResolveBackfills(AssemblyResolutionResults results) { - AssemblyResolutionResults res = new AssemblyResolutionResults(); - next_ar: for (AssemblyResolution ar : results) { - if (ar.isError()) { - res.add(ar); - continue; - } - while (true) { - AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; - if (!rc.hasBackfills()) { - // finish: The complete solution is known - res.add(rc); - continue next_ar; - } - ar = rc.backfill(SOLVER, vals); - if (ar.isError() || ar.isBackfill()) { - // fail: It is now known that the solution doesn't exist - res.add(ar); - continue next_ar; - } - if (ar.equals(rc)) { - // fail: The solution is /still/ not known, and we made no progress - res.add(ar); - continue next_ar; - } - // Some progress was made, continue trying until we finish or fail - } - } - return res; - } - - /** - * Compute the offset of an operand encoded in the instruction block - * - *

- * TODO: Currently, there are duplicate mechanisms for resolving a constructor: 1) The newer - * mechanism implemented in {@link AssemblyConstructState}, and 2) the older one implemented in - * {@link #applyPatterns(AssemblyConstructorSemantic, int, AssemblyResolutionResults)}. The - * latter seems to require this method, since it does not have pre-computed shifts as in the - * former. We should probably remove the latter in favor of the former.... - * - * @param opsym the operand symbol - * @param cons the constructor containing the operand - * @return the offset (right shift) to apply to the encoded operand - */ - public static int computeOffset(OperandSymbol opsym, Constructor cons) { - int offset = opsym.getRelativeOffset(); - int baseidx = opsym.getOffsetBase(); - if (baseidx != -1) { - OperandSymbol baseop = cons.getOperand(baseidx); - offset += baseop.getMinimumLength(); - offset += computeOffset(baseop, cons); - } - return offset; - } - - /** - * Attempt to solve an expression - * - * @param exp the expression to solve - * @param goal the desired value of the expression - * @param vals any defined symbols - * @param cur the resolved constructor so far - * @param description a description of the result - * @return the encoded solution, or a backfill record - */ - protected static AssemblyResolution solveOrBackfill(PatternExpression exp, MaskedLong goal, - Map vals, AssemblyResolvedPatterns cur, String description) { - try { - return SOLVER.solve(exp, goal, vals, cur, description); - } - catch (NeedsBackfillException bf) { - int fieldLength = SOLVER.getInstructionLength(exp); - return AssemblyResolution.backfill(exp, goal, fieldLength, description); - } - } - - /** - * Attempt to solve an expression - * - *

- * Converts the given goal to a fully-defined {@link MaskedLong} and then solves as before. - * - * @see #solveOrBackfill(PatternExpression, MaskedLong, Map, AssemblyResolvedPatterns, String) - */ - protected static AssemblyResolution solveOrBackfill(PatternExpression exp, long goal, - Map vals, AssemblyResolvedPatterns cur, String description) { - return solveOrBackfill(exp, MaskedLong.fromLong(goal), vals, cur, description); - } - - /** - * Attempt to solve an expression - * - *

- * Converts the given goal and bits count to a {@link MaskedLong} and then solves as before. As - * a special case, if {@code bits == 0}, the goal is considered fully-defined (as if - * {@code bits == 64}). - * - * @see #solveOrBackfill(PatternExpression, MaskedLong, Map, AssemblyResolvedPatterns, String) - */ - protected static AssemblyResolution solveOrBackfill(PatternExpression exp, long goal, int bits, - Map vals, AssemblyResolvedPatterns cur, String description) { - long msk; - if (bits == 0 || bits >= 64) { - msk = -1L; - } - else { - msk = ~(-1L << bits); - } - return solveOrBackfill(exp, MaskedLong.fromMaskAndValue(msk, goal), vals, cur, description); + public AssemblyTreeResolver( + AbstractAssemblyResolutionFactory factory, + SleighLanguage lang, Address at, AssemblyParseBranch tree, AssemblyPatternBlock context, + AssemblyContextGraph ctxGraph) { + super(factory, lang, at, tree, context, ctxGraph); } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolutionFactory.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolutionFactory.java new file mode 100644 index 0000000000..20311d5f83 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolutionFactory.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.assembler.sleigh.sem; + +public class DefaultAssemblyResolutionFactory extends + AbstractAssemblyResolutionFactory { + + @Override + public DefaultAssemblyResolvedPatternBuilder newPatternsBuilder() { + return new DefaultAssemblyResolvedPatternBuilder(); + } + + @Override + public DefaultAssemblyResolvedBackfillBuilder newBackfillBuilder() { + return new DefaultAssemblyResolvedBackfillBuilder(); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedBackfill.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedBackfill.java new file mode 100644 index 0000000000..a296dc6a6a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedBackfill.java @@ -0,0 +1,158 @@ +/* ### + * 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.sem; + +import java.util.Map; +import java.util.Objects; + +import ghidra.app.plugin.assembler.sleigh.expr.*; +import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolvedBackfillBuilder; +import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; + +/** + * A {@link AssemblyResolution} indicating the need to solve an expression in the future + * + *

+ * Such records are collected within a {@link AssemblyResolvedPatterns} and then solved just before + * the final result(s) are assembled. This is typically required by instructions that refer to the + * {@code inst_next} symbol. + * + *

+ * NOTE: These are used internally. The user ought never to see these from the assembly API. + */ +public class DefaultAssemblyResolvedBackfill extends AbstractAssemblyResolution + implements AssemblyResolvedBackfill { + protected final PatternExpression exp; + protected final MaskedLong goal; + protected final int inslen; + protected final int offset; + + /** + * @see {@link AssemblyResolution#backfill(PatternExpression, MaskedLong, Map, int, String)} + */ + protected DefaultAssemblyResolvedBackfill(AbstractAssemblyResolutionFactory factory, + String description, PatternExpression exp, MaskedLong goal, int inslen, int offset) { + super(factory, description, null, null); + this.exp = Objects.requireNonNull(exp); + this.goal = Objects.requireNonNull(goal); + this.inslen = inslen; + this.offset = offset; + } + + @Override + protected int computeHash() { + int result = 0; + result += exp.hashCode(); + result *= 31; + result += goal.hashCode(); + result *= 31; + result += inslen; + result *= 31; + result += offset; + return result; + } + + protected AbstractAssemblyResolvedBackfillBuilder copyBuilder() { + var builder = factory.newBackfillBuilder(); + builder.description = description; + builder.exp = exp; + builder.goal = goal; + builder.inslen = inslen; + builder.offset = offset; + return builder; + } + + /** + * Duplicate this record + * + * @return the duplicate + */ + protected AssemblyResolvedBackfill copy() { + return copyBuilder().build(); + } + + @Override + public AssemblyResolvedBackfill withRight(AssemblyResolution right) { + throw new AssertionError(); + } + + @Override + public int getInstructionLength() { + return offset + inslen; + } + + @Override + public boolean isError() { + return false; + } + + @Override + public boolean isBackfill() { + return true; + } + + @Override + public String lineToString() { + return "Backfill (len:" + inslen + ",off:" + offset + ") " + goal + " := " + exp + " (" + + description + ")"; + } + + protected AbstractAssemblyResolvedBackfillBuilder shiftBuilder(int amt) { + var builder = factory.newBackfillBuilder(); + builder.description = description; + builder.exp = exp; + builder.goal = goal; + builder.inslen = inslen; + builder.offset = offset + amt; + return builder; + } + + @Override + public AssemblyResolvedBackfill shift(int amt) { + return shiftBuilder(amt).build(); + } + + @Override + public AssemblyResolution parent(String description, int opCount) { + throw new AssertionError(); + } + + @Override + public AssemblyResolution solve(RecursiveDescentSolver solver, Map vals, + AssemblyResolvedPatterns cur) { + try { + AssemblyResolution ar = + solver.solve(factory, exp, goal, vals, cur.truncate(offset), description); + if (ar.isError()) { + return ar; + } + AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; + return rc.shift(offset); + } + catch (NeedsBackfillException e) { + return factory.newErrorBuilder() + .error("Solution still requires backfill") + .description(description) + .build(); + } + catch (UnsupportedOperationException e) { + return factory.newErrorBuilder() + .error("Unsupported: " + e.getMessage()) + .description(description) + .build(); + } + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedError.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedError.java new file mode 100644 index 0000000000..af7b55604c --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedError.java @@ -0,0 +1,111 @@ +/* ### + * 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.sem; + +import java.util.List; + +/** + * A {@link AssemblyResolution} indicating the occurrence of a (usually semantic) error + * + *

+ * The description should indicate where the error occurred. The error message should explain the + * actual error. To help the user diagnose the nature of the error, errors in sub-constructors + * should be placed as children of an error given by the parent constructor. + */ +public class DefaultAssemblyResolvedError extends AbstractAssemblyResolution + implements AssemblyResolvedError { + protected final String error; + + @Override + protected int computeHash() { + return error.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (this.getClass() != obj.getClass()) { + return false; + } + DefaultAssemblyResolvedError that = (DefaultAssemblyResolvedError) obj; + if (!this.error.equals(that.error)) { + return false; + } + return true; + } + + /** + * @see AssemblyResolution#error(String, String, List) + */ + protected DefaultAssemblyResolvedError(AbstractAssemblyResolutionFactory factory, + String description, List children, + AssemblyResolution right, String error) { + super(factory, description, children, right); + AbstractAssemblyTreeResolver.DBG.println(error); + this.error = error; + } + + @Override + public boolean isError() { + return true; + } + + @Override + public boolean isBackfill() { + return false; + } + + /** + * Get a description of the error + * + * @return the description + */ + @Override + public String getError() { + return error; + } + + @Override + public String lineToString() { + return error + " (" + description + ")"; + } + + @Override + public AssemblyResolution shift(int amt) { + return this; + } + + @Override + public AssemblyResolution withRight(AssemblyResolution right) { + return factory.newErrorBuilder() + .error(error) + .description(description) + .children(children) + .right(right) + .build(); + } + + @Override + public AssemblyResolution parent(String description, int opCount) { + return factory.newErrorBuilder() + .error(error) + .description(description) + .children(getAllRight()) + .build(); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedPatterns.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedPatterns.java new file mode 100644 index 0000000000..eec7bdcebb --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/sem/DefaultAssemblyResolvedPatterns.java @@ -0,0 +1,775 @@ +/* ### + * 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.sem; + +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.collections4.Predicate; +import org.apache.commons.lang3.StringUtils; + +import ghidra.app.plugin.assembler.AssemblySelector; +import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong; +import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver; +import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolutionBuilder; +import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolvedPatternsBuilder; +import ghidra.app.plugin.processors.sleigh.*; +import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol; +import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol; + +/** + * A {@link AssemblyResolution} indicating successful application of a constructor + * + *

+ * This is almost analogous to {@link ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern + * DisjointPattern}, in that is joins an instruction {@link AssemblyPatternBlock} with a + * corresponding context {@link AssemblyPatternBlock}. However, this object is mutable, and it + * collects backfill records, as well as forbidden patterns. + * + *

+ * When the applied constructor is from the "instruction" subtable, this represents a fully- + * constructed instruction with required context. All backfill records ought to be resolved and + * applied before the final result is given to the user, i.e., passed into the + * {@link AssemblySelector}. If at any time during the resolution or backfill process, the result + * becomes confined to one of the forbidden patterns, it must be dropped, since the encoding will + * actually invoke a more specific SLEIGH constructor. + */ +public class DefaultAssemblyResolvedPatterns extends AbstractAssemblyResolution + implements AssemblyResolvedPatterns { + protected static final String INS = AbstractAssemblyResolutionFactory.INS; + protected static final String CTX = AbstractAssemblyResolutionFactory.CTX; + protected static final String SEP = AbstractAssemblyResolutionFactory.SEP; + + protected final Constructor cons; + protected final AssemblyPatternBlock ins; + protected final AssemblyPatternBlock ctx; + + protected final Set backfills; + protected final Set forbids; + + /** + * @see AssemblyResolution#resolved(AssemblyPatternBlock, AssemblyPatternBlock, String, + * Constructor, List, AssemblyResolution) + */ + protected DefaultAssemblyResolvedPatterns(AbstractAssemblyResolutionFactory factory, + String description, Constructor cons, List children, + AssemblyResolution right, AssemblyPatternBlock ins, AssemblyPatternBlock ctx, + Set backfills, Set forbids) { + super(factory, description, children, right); + this.cons = cons; + this.ins = ins == null ? AssemblyPatternBlock.nop() : ins; + this.ctx = ctx == null ? AssemblyPatternBlock.nop() : ctx; + this.backfills = backfills == null ? Set.of() : Collections.unmodifiableSet(backfills); + this.forbids = forbids == null ? Set.of() : Collections.unmodifiableSet(forbids); + } + + @Override + protected int computeHash() { + int result = 0; + result += ins.hashCode(); + result *= 31; + result += ctx.hashCode(); + result *= 31; + result += backfills.hashCode(); + result *= 31; + result += forbids.hashCode(); + return result; + } + + protected boolean partsEqual(DefaultAssemblyResolvedPatterns that) { + if (!this.ins.equals(that.ins)) { + return false; + } + if (!this.ctx.equals(that.ctx)) { + return false; + } + if (!this.backfills.equals(that.backfills)) { + return false; + } + if (!this.forbids.equals(that.forbids)) { + return false; + } + return true; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (this.getClass() != obj.getClass()) { + return false; + } + DefaultAssemblyResolvedPatterns that = (DefaultAssemblyResolvedPatterns) obj; + return partsEqual(that); + } + + protected AbstractAssemblyResolvedPatternsBuilder shiftBuilder(int amt) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins.shift(amt); + builder.ctx = ctx; + + // Also shift the attached backfills and forbidden patterns + builder.backfills = new HashSet<>(); + for (AssemblyResolvedBackfill bf : this.backfills) { + builder.backfills.add(bf.shift(amt)); + } + + builder.forbids = new HashSet<>(); + for (AssemblyResolvedPatterns f : this.forbids) { + builder.forbids.add(f.shift(amt)); + } + + return builder; + } + + @Override + public AssemblyResolvedPatterns shift(int amt) { + if (amt == 0) { + return this; + } + return shiftBuilder(amt).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder truncateBuilder(int amt) { + var builder = factory.newPatternsBuilder(); + builder.description = "Truncated: " + description; + builder.cons = cons; + builder.right = right; + builder.ins = ins.truncate(amt); + builder.ctx = ctx; + return builder; + } + + @Override + public AssemblyResolvedPatterns truncate(int amt) { + if (amt == 0) { + return this; + } + return truncateBuilder(amt).build(); + } + + protected AbstractAssemblyResolutionBuilder checkNotForbiddenBuilder() { + var builder = factory.newPatternsBuilder(); + + builder.forbids = new HashSet<>(); + for (AssemblyResolvedPatterns f : forbids) { + AssemblyResolvedPatterns check = this.combine(f); + if (null == check) { + continue; + } + builder.forbids.add(f); + if (check.bitsEqual(this)) { + // The result would be disassembled by a more-specific constructor. + return factory.errorBuilder("The result is forbidden by " + f, this); + } + } + + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins; + builder.ctx = ctx; + builder.backfills = backfills; + return builder; + } + + @Override + public AssemblyResolution checkNotForbidden() { + return checkNotForbiddenBuilder().build(); + } + + @Override + public boolean bitsEqual(AssemblyResolvedPatterns that) { + return this.ins.equals(that.getInstruction()) && this.ctx.equals(that.getContext()); + } + + protected AbstractAssemblyResolvedPatternsBuilder combineBuilder( + AssemblyResolvedPatterns that) { + var builder = factory.newPatternsBuilder(); + builder.ins = ins.combine(that.getInstruction()); + if (builder.ins == null) { + return null; + } + builder.ctx = ctx.combine(that.getContext()); + if (builder.ctx == null) { + return null; + } + builder.backfills = new HashSet<>(this.backfills); + builder.backfills.addAll(that.getBackfills()); + builder.forbids = new HashSet<>(this.forbids); + builder.forbids.addAll(that.getForbids()); + + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + + return builder; + } + + protected AbstractAssemblyResolvedPatternsBuilder combineLessBackfillBuilder( + AssemblyResolvedPatterns that, AssemblyResolvedBackfill bf) { + var builder = combineBuilder(that); + builder.backfills.remove(bf); + return builder; + } + + @Override + public AssemblyResolvedPatterns combine(AssemblyResolvedPatterns that) { + var builder = combineBuilder(that); + return builder == null ? null : builder.build(); + } + + /** + * Combine a backfill result + * + *

+ * When a backfill is successful, the result should be combined with the owning resolution. In + * addition, for bookkeeping's sake, the resolved record should be removed from the list of + * backfills. + * + * @param that the result from backfilling + * @param bf the resolved backfilled record + * @return the result if successful, or null + */ + @Override + public AssemblyResolvedPatterns combineLessBackfill(AssemblyResolvedPatterns that, + AssemblyResolvedBackfill bf) { + var builder = combineLessBackfillBuilder(that, bf); + return builder == null ? null : builder.build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder combineBuilder( + AssemblyResolvedBackfill bf) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins; + builder.ctx = ctx; + builder.backfills = new HashSet<>(backfills); + builder.backfills.add(bf); + builder.forbids = forbids; + return builder; + } + + @Override + public AssemblyResolvedPatterns combine(AssemblyResolvedBackfill bf) { + return combineBuilder(bf).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder withForbidsBuilder( + Set more) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins; + builder.ctx = ctx; + builder.backfills = backfills; + builder.forbids = new HashSet<>(forbids); + builder.forbids.addAll(more); + return builder; + } + + @Override + public AssemblyResolvedPatterns withForbids(Set more) { + return withForbidsBuilder(more).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder withDescriptionBuilder( + String description) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins; + builder.ctx = ctx; + builder.backfills = backfills; + builder.forbids = forbids; + return builder; + } + + @Override + public AssemblyResolvedPatterns withDescription(String description) { + return withDescriptionBuilder(description).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder withConstructorBuilder(Constructor cons) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins; + builder.ctx = ctx; + builder.backfills = backfills; + builder.forbids = forbids; + return builder; + } + + @Override + public AssemblyResolvedPatterns withConstructor(Constructor cons) { + return withConstructorBuilder(cons).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder writeContextOpBuilder(ContextOp cop, + MaskedLong val) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins; + builder.ctx = ctx.writeContextOp(cop, val); + builder.backfills = backfills; + builder.forbids = forbids; + return builder; + } + + @Override + public AssemblyResolvedPatterns writeContextOp(ContextOp cop, MaskedLong val) { + return writeContextOpBuilder(cop, val).build(); + } + + @Override + public MaskedLong readContextOp(ContextOp cop) { + return ctx.readContextOp(cop); + } + + protected AbstractAssemblyResolvedPatternsBuilder copyAppendDescriptionBuilder( + String append) { + var builder = factory.newPatternsBuilder(); + builder.description = description + ": " + append; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins.copy(); + builder.ctx = ctx.copy(); + builder.backfills = backfills; + builder.forbids = forbids; + return builder; + } + + /** + * Duplicate this resolution, with additional description text appended + * + * @param append the text to append + * @return the duplicate NOTE: An additional separator {@code ": "} is inserted + */ + public AssemblyResolvedPatterns copyAppendDescription(String append) { + return copyAppendDescriptionBuilder(append).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder withRightBuilder( + AssemblyResolution right) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins.copy(); + builder.ctx = ctx.copy(); + builder.backfills = backfills; + builder.forbids = forbids; + return builder; + } + + @Override + public AssemblyResolvedPatterns withRight(AssemblyResolution right) { + return withRightBuilder(right).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder nopLeftSiblingBuilder() { + var builder = factory.newPatternsBuilder(); + builder.description = "nop-left"; + builder.right = this; + builder.ins = ins.copy(); + builder.ctx = ctx.copy(); + builder.backfills = backfills; + builder.forbids = forbids; + return builder; + } + + @Override + public AssemblyResolvedPatterns nopLeftSibling() { + return nopLeftSiblingBuilder().build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder parentBuilder(String description, + int opCount) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + List allRight = getAllRight(); + builder.children = allRight.subList(0, opCount); + builder.right = allRight.get(opCount); + builder.ins = ins; + builder.ctx = ctx; + builder.backfills = backfills; + builder.forbids = forbids; + return builder; + } + + @Override + public AssemblyResolvedPatterns parent(String description, int opCount) { + return parentBuilder(description, opCount).build(); + } + + protected AbstractAssemblyResolvedPatternsBuilder maskOutBuilder(ContextOp cop) { + var builder = factory.newPatternsBuilder(); + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins; + builder.ctx = ctx.maskOut(cop); + builder.backfills = backfills; + builder.forbids = forbids; + return builder; + } + + @Override + public AssemblyResolvedPatterns maskOut(ContextOp cop) { + return maskOutBuilder(cop).build(); + } + + @Override + public AssemblyResolution backfill(RecursiveDescentSolver solver, Map vals) { + if (!hasBackfills()) { + return this; + } + + AssemblyResolvedPatterns res = this; + loop: while (true) { + for (AssemblyResolvedBackfill bf : res.getBackfills()) { + AssemblyResolution ar = bf.solve(solver, vals, this); + if (ar.isError()) { + continue; + } + AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar; + AssemblyResolvedPatterns check = res.combineLessBackfill(rc, bf); + if (check == null) { + return factory.error("Conflict: Backfill " + bf.getDescription(), res); + } + res = check; + continue loop; + } + return res; + } + } + + @Override + public String lineToString() { + return dumpConstructorTree() + ":" + INS + ins + SEP + CTX + ctx + " (" + description + ")"; + } + + @Override + public boolean hasBackfills() { + return !backfills.isEmpty(); + } + + /** + * Check if this resolution includes forbidden patterns + * + * @return true if there are forbidden patterns + */ + private boolean hasForbids() { + return !forbids.isEmpty(); + } + + protected AbstractAssemblyResolvedPatternsBuilder solveContextChangesForForbidsBuilder( + AssemblyConstructorSemantic sem, Map vals) { + var builder = factory.newPatternsBuilder(); + builder.forbids = new HashSet<>(); + for (AssemblyResolvedPatterns f : forbids) { + AssemblyResolution t = sem.solveContextChanges(f, vals); + if (!(t instanceof AssemblyResolvedPatterns rp)) { + // Can't be solved, so it can be dropped + continue; + } + builder.forbids.add(rp); + } + builder.description = description; + builder.cons = cons; + builder.children = children; + builder.right = right; + builder.ins = ins; + builder.ctx = ctx; + builder.backfills = backfills; + return builder; + } + + @Override + public AssemblyResolvedPatterns solveContextChangesForForbids( + AssemblyConstructorSemantic sem, Map vals) { + if (!hasForbids()) { + return this; + } + return solveContextChangesForForbidsBuilder(sem, vals).build(); + } + + @Override + public int getInstructionLength() { + int inslen = ins.length(); + for (AssemblyResolvedBackfill bf : backfills) { + inslen = Math.max(inslen, bf.getInstructionLength()); + } + return inslen; + } + + @Override + public int getDefinedInstructionLength() { + byte[] imsk = ins.getMask(); + int i; + for (i = imsk.length - 1; i >= 0; i--) { + if (imsk[i] != 0) { + break; + } + } + return ins.getOffset() + i + 1; + } + + @Override + public AssemblyPatternBlock getInstruction() { + return ins; + } + + @Override + public AssemblyPatternBlock getContext() { + return ctx; + } + + @Override + public MaskedLong readInstruction(int start, int len) { + return ins.readBytes(start, len); + } + + @Override + public MaskedLong readContext(int start, int len) { + return ctx.readBytes(start, len); + } + + @Override + public boolean isError() { + return false; + } + + @Override + public boolean isBackfill() { + return false; + } + + @Override + public boolean hasChildren() { + return super.hasChildren() || hasBackfills() || hasForbids(); + } + + @Override + protected String childrenToString(String indent) { + StringBuilder sb = new StringBuilder(); + if (super.hasChildren()) { + sb.append(super.childrenToString(indent) + "\n"); + } + for (AssemblyResolvedBackfill bf : backfills) { + sb.append(indent); + sb.append("backfill: " + bf + "\n"); + } + for (AssemblyResolvedPatterns f : forbids) { + sb.append(indent); + sb.append("forbidden: " + f + "\n"); + } + return sb.substring(0, sb.length() - 1); + } + + protected static final Pattern pat = Pattern.compile("line(\\d*)"); + + @Override + public String dumpConstructorTree() { + StringBuilder sb = new StringBuilder(); + if (cons == null) { + return null; + } + sb.append(cons.getSourceFile() + ":" + cons.getLineno()); + + if (children == null) { + return sb.toString(); + } + + List subs = new ArrayList<>(); + for (AssemblyResolution c : children) { + if (c instanceof AssemblyResolvedPatterns) { + AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) c; + String s = rc.dumpConstructorTree(); + if (s != null) { + subs.add(s); + } + } + } + + if (subs.isEmpty()) { + return sb.toString(); + } + sb.append('['); + sb.append(StringUtils.join(subs, ",")); + sb.append(']'); + return sb.toString(); + } + + /** + * Count the number of bits specified in the resolution patterns + * + *

+ * Totals the specificity of the instruction and context pattern blocks. + * + * @return the number of bits in the resulting patterns + * @see AssemblyPatternBlock#getSpecificity() + */ + public int getSpecificity() { + return ins.getSpecificity() + ctx.getSpecificity(); + } + + @Override + public Iterable possibleInsVals(AssemblyPatternBlock forCtx) { + AssemblyPatternBlock ctxCompat = ctx.combine(forCtx); + if (ctxCompat == null) { + return List.of(); + } + Predicate removeForbidden = (byte[] val) -> { + for (AssemblyResolvedPatterns f : forbids) { + // If the forbidden length is larger than us, we can ignore it + if (f.getDefinedInstructionLength() > val.length) { + continue; + } + // Check if the context matches, if not, we can let it pass + if (null == f.getContext().combine(forCtx)) { + continue; + } + // If the context matches, now check the instruction + AssemblyPatternBlock i = f.getInstruction(); + AssemblyPatternBlock vi = + AssemblyPatternBlock.fromBytes(ins.length() - val.length, val); + if (null == i.combine(vi)) { + continue; + } + return false; + } + return true; + }; + return new Iterable() { + @Override + public Iterator iterator() { + return IteratorUtils.filteredIterator(ins.possibleVals().iterator(), + removeForbidden); + } + }; + } + + protected static int getOpIndex(String piece) { + if (piece.charAt(0) != '\n') { + return -1; + } + return piece.charAt(1) - 'A'; + } + + /** + * If the construct state is a {@code ^instruction} or other purely-recursive constructor, get + * its single child. + * + * @param state the parent state + * @return the child state if recursive, or null + */ + protected static ConstructState getPureRecursion(ConstructState state) { + // NB. There can be other operands, but only one can be printed + // Furthermore, nothing else can be printed, whether an operand or not + List pieces = state.getConstructor().getPrintPieces(); + if (pieces.size() != 1) { + return null; + } + int opIdx = getOpIndex(pieces.get(0)); + if (opIdx < 0) { + return null; + } + ConstructState sub = state.getSubState(opIdx); + if (sub == null || sub.getConstructor() == null || + sub.getConstructor().getParent() != state.getConstructor().getParent()) { + // not recursive + return null; + } + return sub; + } + + @Override + public boolean equivalentConstructState(ConstructState state) { + ConstructState rec = getPureRecursion(state); + if (rec != null) { + if (state.getConstructor() == cons) { + assert children.size() == 1; + AssemblyResolvedPatterns recRes = (AssemblyResolvedPatterns) children.get(0); + return recRes.equivalentConstructState(rec); + } + return equivalentConstructState(rec); + } + if (state.getConstructor() != cons) { + return false; + } + int opCount = cons.getNumOperands(); + for (int opIdx = 0; opIdx < opCount; opIdx++) { + OperandSymbol opSym = cons.getOperand(opIdx); + Set printed = + Arrays.stream(cons.getOpsPrintOrder()).boxed().collect(Collectors.toSet()); + if (!(opSym.getDefiningSymbol() instanceof SubtableSymbol)) { + AssemblyTreeResolver.DBG.println("Operand " + opSym + " is not a sub-table"); + continue; + } + if (!printed.contains(opIdx)) { + AssemblyTreeResolver.DBG.println("Operand " + opSym + " is hidden"); + continue; + } + AssemblyResolvedPatterns child = (AssemblyResolvedPatterns) children.get(opIdx); + ConstructState subState = state.getSubState(opIdx); + if (!child.equivalentConstructState(subState)) { + return false; + } + } + return true; + } + + public Constructor getConstructor() { + return cons; + } + + @Override + public Set getBackfills() { + return backfills; + } + + @Override + public Set getForbids() { + return forbids; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyFixedNumericTerminal.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyFixedNumericTerminal.java index 113e29bbf9..b7df7bc905 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyFixedNumericTerminal.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyFixedNumericTerminal.java @@ -28,7 +28,7 @@ import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseNumericToken; * value. */ public class AssemblyFixedNumericTerminal extends AssemblyNumericTerminal { - private final long val; + protected final long val; /** * Construct a terminal that accepts only the given numeric value @@ -65,4 +65,8 @@ public class AssemblyFixedNumericTerminal extends AssemblyNumericTerminal { } return toks; } + + public long getVal() { + return val; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyNumericMapTerminal.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyNumericMapTerminal.java index b0469bc373..6a9eda2fb8 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyNumericMapTerminal.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyNumericMapTerminal.java @@ -69,4 +69,8 @@ public class AssemblyNumericMapTerminal extends AssemblyNumericTerminal { } return result; } + + public Map getMap() { + return map; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyNumericTerminal.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyNumericTerminal.java index 962f37ab2a..8b19b5f70d 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyNumericTerminal.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyNumericTerminal.java @@ -289,4 +289,8 @@ public class AssemblyNumericTerminal extends AssemblyTerminal { public int getBitSize() { return bitsize; } + + public AddressSpace getSpace() { + return space; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyStringMapTerminal.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyStringMapTerminal.java index a1f3a89bc8..b15f1fe211 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyStringMapTerminal.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyStringMapTerminal.java @@ -15,7 +15,8 @@ */ package ghidra.app.plugin.assembler.sleigh.symbol; -import java.util.*; +import java.util.Collection; +import java.util.LinkedHashSet; import java.util.Map.Entry; import org.apache.commons.collections4.MultiValuedMap; @@ -66,4 +67,8 @@ public class AssemblyStringMapTerminal extends AssemblyTerminal { public String toString() { return "[list:" + name + "]"; } + + public MultiValuedMap getMap() { + return map; + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyStringTerminal.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyStringTerminal.java index 25fdaf825c..4876e31682 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyStringTerminal.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/symbol/AssemblyStringTerminal.java @@ -15,25 +15,29 @@ */ package ghidra.app.plugin.assembler.sleigh.symbol; -import java.util.*; +import java.util.Collection; +import java.util.Collections; import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar; import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseToken; +import ghidra.app.plugin.processors.sleigh.symbol.VarnodeSymbol; /** * A terminal that accepts only a particular string */ public class AssemblyStringTerminal extends AssemblyTerminal { protected final String str; + protected final VarnodeSymbol defsym; /** * Construct a terminal that accepts only the given string * * @param str the string to accept */ - public AssemblyStringTerminal(String str) { + public AssemblyStringTerminal(String str, VarnodeSymbol defsym) { super("\"" + str + "\""); this.str = str; + this.defsym = defsym; } @Override @@ -57,6 +61,18 @@ public class AssemblyStringTerminal extends AssemblyTerminal { @Override public boolean takesOperandIndex() { + return defsym != null; + } + + public VarnodeSymbol getDefiningSymbol() { + return defsym; + } + + public String getString() { + return str; + } + + public boolean isWhiteSpace() { return false; } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseBranch.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseBranch.java index 1459123b01..48d6f066bd 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseBranch.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseBranch.java @@ -166,9 +166,8 @@ public class AssemblyParseBranch extends AssemblyParseTreeNode return substs.get(i); } - @Override public boolean isConstructor() { - return true; + return prod.isConstructor(); } @Override diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseHiddenNode.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseHiddenNode.java new file mode 100644 index 0000000000..ef40467a14 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseHiddenNode.java @@ -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.tree; + +import java.io.PrintStream; + +import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar; +import ghidra.app.plugin.assembler.sleigh.symbol.AssemblySymbol; + +public class AssemblyParseHiddenNode extends AssemblyParseTreeNode { + public AssemblyParseHiddenNode(AssemblyGrammar grammar) { + super(grammar); + } + + @Override + public AssemblySymbol getSym() { + return null; + } + + @Override + protected void print(PrintStream out, String indent) { + out.print(""); + } + + @Override + public String generateString() { + return ""; + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseNumericToken.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseNumericToken.java index 39427a0ae1..936b7f1f46 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseNumericToken.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseNumericToken.java @@ -84,9 +84,4 @@ public class AssemblyParseNumericToken extends AssemblyParseToken { public long getNumericValue() { return val; } - - @Override - public boolean isNumeric() { - return true; - } } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseTreeNode.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseTreeNode.java index ca80792d60..e9abcd33e4 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseTreeNode.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/assembler/sleigh/tree/AssemblyParseTreeNode.java @@ -83,24 +83,6 @@ public abstract class AssemblyParseTreeNode { */ protected abstract void print(PrintStream out, String indent); - /** - * Check if this node yields a subconstructor resolution - * - * @return true if this node yields a subconstructor resolution - */ - public boolean isConstructor() { - return false; - } - - /** - * Check if this node yields a numeric value - * - * @return true if this node yields a numeric value - */ - public boolean isNumeric() { - return false; - } - /** * Get the grammar used to parse the tree * diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/ARMAssemblyTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/ARMAssemblyTest.java index 9d4a6ea3c3..421c9290ef 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/ARMAssemblyTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/ARMAssemblyTest.java @@ -55,7 +55,12 @@ public class ARMAssemblyTest extends AbstractAssemblyTest { } @Test - public void testAssembly_T_add_w_pc_r0_r7_asr_0xf() { + public void testAssemble_mov_pc_n0x0() { + assertOneCompatRestExact("mov pc,#0x0", "00:f0:a0:e3"); + } + + @Test + public void testAssemble_T_add_w_pc_r0_r7_asr_0xf() { assertOneCompatRestExact("add.w pc,r0,r7, asr #0xf", "00:eb:e7:3f", THUMB, 0x0000c3b8, "add.w pc,r0,r7, asr #0xf"); } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AbstractAssemblyTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AbstractAssemblyTest.java index a2cd1e40e0..5815c4913f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AbstractAssemblyTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AbstractAssemblyTest.java @@ -204,8 +204,8 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest { * @param ctxstr a string describing the input context pattern * @see AssemblyPatternBlock#fromString(String) */ - protected void checkAllExact(AssemblyResolutionResults rr, Collection disassembly, - long addr, String ctxstr) { + protected void checkAllExact(AssemblyResolutionResults rr, + Collection disassembly, long addr, String ctxstr) { Address address = lang.getDefaultSpace().getAddress(addr); final AssemblyPatternBlock ctx = (ctxstr == null ? context.getDefaultAt(address) : AssemblyPatternBlock.fromString(ctxstr)).fillMask(); @@ -221,10 +221,10 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest { errs.add((AssemblyResolvedError) ar); continue; } - AssemblyResolvedPatterns rcon = (AssemblyResolvedPatterns) ar; + AssemblyResolvedPatterns rp = (AssemblyResolvedPatterns) ar; try { - dbg.println(" " + rcon.lineToString()); - for (byte[] ins : rcon.possibleInsVals(ctx)) { + dbg.println(" " + rp.lineToString()); + for (byte[] ins : rp.possibleInsVals(ctx)) { dbg.println(" " + NumericUtilities.convertBytesToString(ins)); PseudoInstruction pi = disassemble(addr, ins, ctx.getVals()); String cons = dumpConstructorTree(pi); @@ -233,7 +233,7 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest { if (!disassembly.contains(dis.trim())) { failedOne = true; misTxtToCons.put(dis, cons); - misTxtConsToRes.put(dis + cons, rcon); + misTxtConsToRes.put(dis + cons, rp); } gotOne = true; } @@ -364,8 +364,8 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest { } @Override - public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, - AssemblyPatternBlock ctx) throws AssemblySemanticException { + public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx) + throws AssemblySemanticException { if (checkOneCompat) { checkOneCompat(instr, rr); } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AssemblyTestCase.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AssemblyTestCase.java index 6f0c810e99..0a3c5558db 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AssemblyTestCase.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/AssemblyTestCase.java @@ -15,7 +15,7 @@ */ package ghidra.app.plugin.assembler.sleigh; -import static org.junit.Assert.*; +import static org.junit.Assert.fail; import java.util.*; @@ -365,8 +365,8 @@ public abstract class AssemblyTestCase extends AbstractGenericTest { } @Override - public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, - AssemblyPatternBlock ctx) throws AssemblySemanticException { + public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx) + throws AssemblySemanticException { if (checkOneCompat) { checkOneCompat(instr, rr); } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/MIPSAssemblyTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/MIPSAssemblyTest.java index 0c36f81741..d174f34eeb 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/MIPSAssemblyTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/MIPSAssemblyTest.java @@ -15,9 +15,13 @@ */ package ghidra.app.plugin.assembler.sleigh; +import java.math.BigInteger; + import org.junit.Test; +import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock; import ghidra.program.model.lang.LanguageID; +import ghidra.program.model.lang.RegisterValue; public class MIPSAssemblyTest extends AbstractAssemblyTest { @@ -30,4 +34,15 @@ public class MIPSAssemblyTest extends AbstractAssemblyTest { public void testAssemble_jal_0x00420fa0() { assertOneCompatRestExact("jal 0x00420fa0", "0c:10:83:e8", 0x00400d4); } + + @Test + public void testAssembly_restore_0x1b8_ra_s0_s1() { + RegisterValue ctxVal = new RegisterValue(lang.getContextBaseRegister()); + ctxVal = ctxVal.assign(lang.getRegister("ISA_MODE"), BigInteger.ONE); + ctxVal = ctxVal.assign(lang.getRegister("PAIR_INSTRUCTION_FLAG"), BigInteger.ZERO); + ctxVal = ctxVal.assign(lang.getRegister("RELP"), BigInteger.ONE); + assertOneCompatRestExact("restore 0x1b8,ra,s0-s1", "f0:30:64:77", + AssemblyPatternBlock.fromRegisterValue(ctxVal).fillMask().toString(), 0x0040000, + "restore 0x1b8,ra,s0-s1"); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/SolverTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/SolverTest.java index 3bd2f13c14..478a553c87 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/SolverTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/SolverTest.java @@ -36,13 +36,13 @@ import ghidra.app.plugin.processors.sleigh.template.ConstructTpl; import ghidra.app.plugin.processors.sleigh.template.HandleTpl; import ghidra.framework.Application; import ghidra.framework.ApplicationConfiguration; -import ghidra.program.model.lang.*; -import ghidra.program.util.DefaultLanguageService; +import ghidra.program.model.lang.LanguageID; import ghidra.util.Msg; import ghidra.xml.XmlPullParser; import ghidra.xml.XmlPullParserFactory; public class SolverTest { + static final DefaultAssemblyResolutionFactory FACTORY = new DefaultAssemblyResolutionFactory(); private static final MaskedLong nil = MaskedLong.ZERO; private static final MaskedLong unk = MaskedLong.UNKS; @@ -165,29 +165,26 @@ public class SolverTest { @Test public void testCatOrSolver() throws SAXException, NeedsBackfillException { - XmlPullParser parser = XmlPullParserFactory.create("" + // - // - "\n" + // - " \n" + // - " \n" + // - " \n" + // - " \n" + // - " \n" + // - "\n" + // - "", "Test", null, true); + XmlPullParser parser = XmlPullParserFactory.create(""" + + + + + + + + """, + "Test", null, true); PatternExpression exp = PatternExpression.restoreExpression(parser, null); RecursiveDescentSolver solver = RecursiveDescentSolver.getSolver(); AssemblyResolution res = - solver.solve(exp, MaskedLong.fromLong(0x78), Collections.emptyMap(), - AssemblyResolution.nop("NOP"), "Test"); - AssemblyResolution e = AssemblyResolvedPatterns.fromString("ins:X7:X8", "Test", null); + solver.solve(FACTORY, exp, MaskedLong.fromLong(0x78), Collections.emptyMap(), + FACTORY.nop("NOP"), "Test"); + AssemblyResolution e = FACTORY.fromString("ins:X7:X8", "Test", null); assertEquals(e, res); } - - private static Language getLanguage(String languageName) throws LanguageNotFoundException { - LanguageService languageService = DefaultLanguageService.getLanguageService(); - return languageService.getLanguage(new LanguageID(languageName)); - } public static Constructor findConstructor(String langId, String subtableName, String patternStr) throws Exception { @@ -329,4 +326,90 @@ public class SolverTest { assertTrue(MaskedLong.fromLong(-0x800000000000000L).isInRange(0xffffffffffffffffL, true)); // NOTE: -0x8000000000000001L will wrap around and appear positive } + + @Test + public void testAssemblyPatternBlockMaskOut() { + AssemblyPatternBlock base = AssemblyPatternBlock.fromString("8C:45:00:00"); + AssemblyPatternBlock extraMask = AssemblyPatternBlock.fromString("XX:X5:XX:XX"); + AssemblyPatternBlock expectedAnswer = AssemblyPatternBlock.fromString("8C:4X:00:00"); + AssemblyPatternBlock computed = base.maskOut(extraMask); + assertEquals(expectedAnswer, computed); + + base = AssemblyPatternBlock.fromString("8C:45:00:00"); + extraMask = AssemblyPatternBlock.fromString("XX:X5:XX:XX"); + expectedAnswer = AssemblyPatternBlock.fromString("8C:4X:00:00"); + computed = base.maskOut(extraMask); + assertEquals(expectedAnswer, computed); + + base = AssemblyPatternBlock.fromString("8C:45:67:89"); + byte[] z = new byte[2]; + z[0] = 0x44; + z[1] = 0x77; + extraMask = AssemblyPatternBlock.fromBytes(2, z); + expectedAnswer = AssemblyPatternBlock.fromString("8C:45:XX:XX"); + computed = base.maskOut(extraMask); + assertEquals(expectedAnswer, computed); + + extraMask = AssemblyPatternBlock.fromBytes(1, z); + expectedAnswer = AssemblyPatternBlock.fromString("8C:XX:XX:89"); + computed = base.maskOut(extraMask); + assertEquals(expectedAnswer, computed); + + base = AssemblyPatternBlock.fromString("01:02:03:04:05:06:07:08"); + extraMask = AssemblyPatternBlock.fromBytes(1, z); + expectedAnswer = AssemblyPatternBlock.fromString("01:XX:XX:04:05:06:07:08"); + computed = base.maskOut(extraMask); + assertEquals(expectedAnswer, computed); + + byte[] z3 = new byte[3]; + z3[0] = 0x44; + z3[1] = 0x77; + z3[2] = 0x78; + base = AssemblyPatternBlock.fromBytes(4, z3); + extraMask = AssemblyPatternBlock.fromBytes(1, z); + expectedAnswer = AssemblyPatternBlock.fromBytes(4, z3); + computed = base.maskOut(extraMask); + assertEquals(expectedAnswer, computed); + + extraMask = AssemblyPatternBlock.fromBytes(4, z); + byte[] z4 = new byte[1]; + z4[0] = 0x78; + expectedAnswer = AssemblyPatternBlock.fromBytes(6, z4); + computed = base.maskOut(extraMask); + assertEquals(expectedAnswer, computed); + } + + @Test + public void testAssemblyPatternBlockTrim() { + AssemblyPatternBlock base = AssemblyPatternBlock.fromString("XC:45:00:0X"); + AssemblyPatternBlock expectedAnswer = AssemblyPatternBlock.fromString("c4:50:00"); + var computed = base.trim(); + assertEquals(expectedAnswer, computed); + + base = base.shift(5); + computed = base.trim(); + assertEquals(expectedAnswer, computed); + + base = AssemblyPatternBlock.fromString("XX:XX:00:XX:10:XX"); + expectedAnswer = AssemblyPatternBlock.fromString("00:XX:10"); + computed = base.trim(); + assertEquals(expectedAnswer, computed); + + base = base.shift(2); + expectedAnswer = AssemblyPatternBlock.fromString("00:XX:10"); + computed = base.trim(); + assertEquals(expectedAnswer, computed); + + base = AssemblyPatternBlock.fromString("[x1xx]X"); + expectedAnswer = AssemblyPatternBlock.fromString("X[xxx1]"); + computed = base.trim(); + assertEquals(expectedAnswer, computed); + + // The "f" here has the "sign bit" set... we wan't to make sure it's treated as + // unsigned + base = AssemblyPatternBlock.fromString("F[x1xx]").shift(3); + expectedAnswer = AssemblyPatternBlock.fromString("[xx11][11x1]"); + computed = base.trim(); + assertEquals(expectedAnswer, computed); + } } diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/parse/ParserTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/parse/ParserTest.java index c94f354ed7..5501834a1f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/parse/ParserTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/parse/ParserTest.java @@ -15,7 +15,8 @@ */ package ghidra.app.plugin.assembler.sleigh.parse; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.io.PrintStream; import java.util.*; @@ -25,19 +26,22 @@ import org.apache.commons.lang3.StringUtils; import org.junit.Test; import ghidra.app.plugin.assembler.sleigh.grammars.*; +import ghidra.app.plugin.assembler.sleigh.sem.DefaultAssemblyResolutionFactory; import ghidra.app.plugin.assembler.sleigh.symbol.*; import ghidra.app.plugin.assembler.sleigh.tree.*; import ghidra.app.plugin.assembler.sleigh.util.AsmUtil; import ghidra.util.NullOutputStream; public class ParserTest { + private static final DefaultAssemblyResolutionFactory FACTORY = + new DefaultAssemblyResolutionFactory(); private boolean tracing = false; private PrintStream out = tracing ? System.out : new PrintStream(new NullOutputStream()); @Test public void testFirstFollow() throws Exception { - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal E = new AssemblyNonTerminal("E"); AssemblyNonTerminal T = new AssemblyNonTerminal("T"); AssemblyNonTerminal F = new AssemblyNonTerminal("F"); @@ -102,7 +106,7 @@ public class ParserTest { @Test public void testLRStates() throws Exception { - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal Sp = new AssemblyNonTerminal("S'"); AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal X = new AssemblyNonTerminal("X"); @@ -148,7 +152,7 @@ public class ParserTest { public void testLALRWithEpsilon37() throws Exception { // This comes from page 37 of http://digital.cs.usu.edu/~allan/Compilers/Notes/LRParsing.pdf - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal Ep = new AssemblyNonTerminal("E'"); AssemblyNonTerminal E = new AssemblyNonTerminal("E"); AssemblyNonTerminal T = new AssemblyNonTerminal("T"); @@ -226,7 +230,7 @@ public class ParserTest { public void testLALRWithEpsilon33999() throws Exception { // This comes from http://cs.stackexchange.com/questions/33999/lalr1-parsers-and-the-epsilon-transition - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal A = new AssemblyNonTerminal("A"); AssemblyNonTerminal B = new AssemblyNonTerminal("B"); @@ -283,7 +287,7 @@ public class ParserTest { public void testLALRFromTutorial() throws Exception { // http://web.cs.dal.ca/~sjackson/lalr1.html - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal N = new AssemblyNonTerminal("N"); AssemblyNonTerminal E = new AssemblyNonTerminal("E"); @@ -378,7 +382,7 @@ public class ParserTest { @Test public void testListsFromARM() throws Exception { - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal S = new AssemblyNonTerminal("S"); @@ -480,7 +484,7 @@ public class ParserTest { @Test public void testEndsOptionalWhitespaceEpsilon() { - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal E = new AssemblyNonTerminal("E"); @@ -505,7 +509,7 @@ public class ParserTest { @Test public void testExpectsPastWhitespace() { - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal S = new AssemblyNonTerminal("S"); addProduction(S, g, "a", " ", "b"); @@ -525,7 +529,7 @@ public class ParserTest { @Test public void testExpectsPastMissingWhitespace() { - AssemblyGrammar g = new AssemblyGrammar(); + AssemblyGrammar g = new AssemblyGrammar(FACTORY); AssemblyNonTerminal S = new AssemblyNonTerminal("S"); addProduction(S, g, "a", " ", "b"); @@ -565,7 +569,7 @@ public class ParserTest { rhs.addWS(); } else { - rhs.addSymbol(new AssemblyStringTerminal((String) o)); + rhs.addSymbol(new AssemblyStringTerminal((String) o, null)); } } else { diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyPatternBlockTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyPatternBlockTest.java new file mode 100644 index 0000000000..743f266b2a --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/sem/AssemblyPatternBlockTest.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.plugin.assembler.sleigh.sem; + +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +public class AssemblyPatternBlockTest { + @Test + public void testBitShiftRightByteArray() { + assertArrayEquals(new byte[] { 1, 1, 1, 1 }, + AssemblyPatternBlock.bitShiftRightByteArray(new byte[] { 2, 2, 2, 2 }, 1)); + assertArrayEquals(new byte[] { 1, 7, (byte) 0x81, 1 }, + AssemblyPatternBlock.bitShiftRightByteArray(new byte[] { 2, 0xf, 2, 2 }, 1)); + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/x86AssemblyTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/x86AssemblyTest.java index 9ad07679c1..d81a7bba33 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/x86AssemblyTest.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/assembler/sleigh/x86AssemblyTest.java @@ -38,4 +38,14 @@ public class x86AssemblyTest extends AbstractAssemblyTest { assertOneCompatRestExact("ADD ECX,dword ptr [-0x8 + EDX]", "03:4a:f8"); } } + + @Test + public void testAssemble_CALL_0x00401234() { + assertOneCompatRestExact("CALL 0x00401234", "e8:2f:12:40:c0"); + } + + @Test + public void testAssemble_CALL_0x00401234_at0() { + assertOneCompatRestExact("CALL 0x00401234", "e8:2f:12:40:00", 0); + } }