mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 02:09:44 +02:00
GP-4185: Make Assembler more extensible
This commit is contained in:
parent
02814f64a6
commit
e7458ed08b
72 changed files with 4303 additions and 2813 deletions
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> There must be an active transaction on the bound program for this method to
|
||||
* succeed.
|
||||
*
|
||||
* @param at the location where the resulting instructions should be placed
|
||||
* @param listing a new-line separated or array sequence of instructions
|
||||
* @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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyParseResult> parseLine(String line);
|
||||
|
||||
/**
|
||||
* Resolve a given parse tree at the given address, assuming the given context
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The resolved instructions are given as masks and values. Where the mask does not
|
||||
* cover, you can choose any value.
|
||||
*
|
||||
* @param parse a parse result giving a valid tree
|
||||
* @param at the location of the start of the instruction
|
||||
* @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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The resolved instructions are given as masks and values. Where the mask does not
|
||||
* cover, you can choose any value.
|
||||
*
|
||||
* @param parse a parse result giving a valid tree
|
||||
* @param at the location of the start of the instruction
|
||||
* @return the results of semantic resolution
|
||||
*/
|
||||
public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at);
|
||||
|
||||
/**
|
||||
* Assemble a line instruction at the given address.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolvedPatterns> {
|
||||
}
|
||||
|
|
|
@ -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<AssemblyResolvedPatterns, Assembler> {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
|
|
@ -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<AssemblyResolvedPatterns> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RP extends AssemblyResolvedPatterns> {
|
||||
/**
|
||||
* Assemble a sequence of instructions and place them at the given address.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> There must be an active transaction on the bound program for this method to
|
||||
* succeed.
|
||||
*
|
||||
* @param at the location where the resulting instructions should be placed
|
||||
* @param listing a new-line separated or array sequence of instructions
|
||||
* @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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyParseResult> parseLine(String line);
|
||||
|
||||
/**
|
||||
* Resolve a given parse tree at the given address, assuming the given context
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The resolved instructions are given as masks and values. Where the mask does not
|
||||
* cover, you can choose any value.
|
||||
*
|
||||
* @param parse a parse result giving a valid tree
|
||||
* @param at the location of the start of the instruction
|
||||
* @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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The resolved instructions are given as masks and values. Where the mask does not
|
||||
* cover, you can choose any value.
|
||||
*
|
||||
* @param parse a parse result giving a valid tree
|
||||
* @param at the location of the start of the instruction
|
||||
* @return the results of semantic resolution
|
||||
*/
|
||||
public AssemblyResolutionResults resolveTree(AssemblyParseResult parse, Address at);
|
||||
|
||||
/**
|
||||
* Assemble a line instruction at the given address.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
|
@ -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<RP>> {
|
||||
/**
|
||||
* 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);
|
||||
}
|
|
@ -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<RP extends AssemblyResolvedPatterns>
|
||||
implements GenericAssembler<RP> {
|
||||
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<RP, ?> factory;
|
||||
protected final AssemblySelector selector;
|
||||
protected final AssemblyParser parser;
|
||||
protected final AssemblyDefaultContext defaultContext;
|
||||
protected final AssemblyContextGraph ctxGraph;
|
||||
|
||||
protected AssemblyNumericSymbols symbols;
|
||||
|
||||
protected AbstractSleighAssembler(AbstractAssemblyResolutionFactory<RP, ?> 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<RP, ?> 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<RP> 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<AssemblyParseResult> 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<RP> 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<AssemblyParseResult> 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);
|
||||
}
|
||||
}
|
|
@ -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<RP>>
|
||||
implements GenericAssemblerBuilder<RP, A> {
|
||||
protected static final DbgTimer dbg =
|
||||
SystemUtilities.isInTestingBatchMode() ? DbgTimer.INACTIVE : DbgTimer.ACTIVE;
|
||||
|
||||
protected final SleighLanguage lang;
|
||||
protected final AbstractAssemblyResolutionFactory<RP, ?> 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<String, AssemblySymbol> builtSymbols = new HashMap<>();
|
||||
|
||||
public AbstractSleighAssemblerBuilder(SleighLanguage lang) {
|
||||
this.lang = lang;
|
||||
this.factory = newResolutionFactory();
|
||||
}
|
||||
|
||||
protected abstract AbstractAssemblyResolutionFactory<RP, ?> 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<String, Integer> invVarnodeList(VarnodeListSymbol vnlist) {
|
||||
MultiValuedMap<String, Integer> 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<Long, Integer> invValueMap(ValueMapSymbol vm) {
|
||||
Map<Long, Integer> result = new HashMap<>();
|
||||
List<Long> 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<String, Integer> invNameSymbol(NameSymbol ns) {
|
||||
MultiValuedMap<String, Integer> 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}
|
||||
*
|
||||
* <p>
|
||||
* For subtables, this results in a non-terminal, for all others, the result in a terminal.
|
||||
*
|
||||
* @param cons the constructor to which the operand belongs
|
||||
* @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
|
||||
*
|
||||
* <p>
|
||||
* This handles a special case, where a constructor prints just one operand and exports that
|
||||
* same operand, often with an explicit size, or as an address in a given space. In such cases,
|
||||
* the listing displays that operand according to that exported size.
|
||||
*
|
||||
* <p>
|
||||
* For assembly, this gives a few opportunities: 1) We can/must ensure the specified value fits,
|
||||
* by checking the size. 2) We can/must mask the goal when solving the defining pattern
|
||||
* expression for the operand. 3)) We can/must check that a label's address space matches that
|
||||
* represented by the operand, when used for a numeric terminal.
|
||||
*
|
||||
* @param cons the constructor from which the production is being derived
|
||||
* @param opsym the operand symbol corresponding to the grammatical symbol, whose size we wish
|
||||
* to determine.
|
||||
* @return the size of the operand in bits
|
||||
*/
|
||||
protected 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<AssemblyNonTerminal> rhs = new AssemblySentential<>();
|
||||
List<Integer> 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<AssemblyNonTerminal> rhs, DisjointPattern pattern, Constructor cons,
|
||||
List<Integer> 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;
|
||||
}
|
||||
}
|
|
@ -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<AssemblyResolvedPatterns>
|
||||
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<AssemblyResolvedPatterns, ?> 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<AssemblyResolvedPatterns, ?> 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<AssemblyParseResult> 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<AssemblyParseResult> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, AssemblySymbol> builtSymbols = new HashMap<>();
|
||||
public class SleighAssemblerBuilder
|
||||
extends AbstractSleighAssemblerBuilder<AssemblyResolvedPatterns, Assembler>
|
||||
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<String, Integer> invVarnodeList(VarnodeListSymbol vnlist) {
|
||||
MultiValuedMap<String, Integer> 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<Long, Integer> invValueMap(ValueMapSymbol vm) {
|
||||
Map<Long, Integer> result = new HashMap<>();
|
||||
List<Long> 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<String, Integer> invNameSymbol(NameSymbol ns) {
|
||||
MultiValuedMap<String, Integer> 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}
|
||||
*
|
||||
* <p>
|
||||
* For subtables, this results in a non-terminal, for all others, the result in a terminal.
|
||||
*
|
||||
* @param cons the constructor to which the operand belongs
|
||||
* @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
|
||||
*
|
||||
* <p>
|
||||
* This handles a special case, where a constructor prints just one operand and exports that
|
||||
* same operand, often with an explicit size, or as an address in a given space. In such cases,
|
||||
* the listing displays that operand according to that exported size.
|
||||
*
|
||||
* <p>
|
||||
* For assembly, this gives a few opportunities: 1) We can/must ensure the specified value fits,
|
||||
* by checking the size. 2) We can/must mask the goal when solving the defining pattern
|
||||
* expression for the operand. 3)) We can/must check that a label's address space matches that
|
||||
* represented by the operand, when used for a numeric terminal.
|
||||
*
|
||||
* @param cons the constructor from which the production is being derived
|
||||
* @param opsym the operand symbol corresponding to the grammatical symbol, whose size we wish
|
||||
* to determine.
|
||||
* @return the size of the operand in bits
|
||||
*/
|
||||
protected 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<AssemblyNonTerminal> rhs = new AssemblySentential<>();
|
||||
List<Integer> 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T extends BinaryExpression>
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException {
|
||||
public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
T exp, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description) throws NeedsBackfillException {
|
||||
MaskedLong lval = solver.getValue(exp.getLeft(), vals, cur);
|
||||
MaskedLong rval = solver.getValue(exp.getRight(), vals, cur);
|
||||
|
||||
|
@ -58,26 +57,27 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
|
|||
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<T extends BinaryExpression>
|
|||
}
|
||||
}
|
||||
|
||||
protected AssemblyResolution solveLeftSide(PatternExpression lexp, MaskedLong rval,
|
||||
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> 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<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> 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<String, Long> vals,
|
||||
protected AssemblyResolution solveLeftSide(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
PatternExpression lexp, MaskedLong rval, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> 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<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> 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<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException, SolverException {
|
||||
throw new NeedsBackfillException("_two_sided_");
|
||||
}
|
||||
|
||||
|
|
|
@ -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<T extends PatternExpression> {
|
|||
* @return the resolution
|
||||
* @throws NeedsBackfillException if the expression refers to an undefined symbol
|
||||
*/
|
||||
public abstract AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) throws NeedsBackfillException;
|
||||
public abstract AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
T exp, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description) throws NeedsBackfillException;
|
||||
|
||||
/**
|
||||
* Attempt to get a constant value for the expression
|
||||
|
|
|
@ -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<T extends UnaryExpression>
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException {
|
||||
public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory, T exp,
|
||||
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description) throws NeedsBackfillException {
|
||||
MaskedLong uval = solver.getValue(exp.getUnary(), vals, cur);
|
||||
try {
|
||||
if (uval != null && uval.isFullyDefined()) {
|
||||
MaskedLong cval = compute(uval);
|
||||
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);
|
||||
}
|
||||
/*
|
||||
|
|
|
@ -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<ConstantValue>
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(ConstantValue cv, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) {
|
||||
public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
ConstantValue cv, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> 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<ConstantValue>
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,15 +35,18 @@ public class ContextFieldSolver extends AbstractExpressionSolver<ContextField> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(ContextField cf, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) {
|
||||
public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
ContextField cf, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description) {
|
||||
assert cf.minValue() == 0; // In case someone decides to do signedness there.
|
||||
if (!goal.isInRange(cf.maxValue(), cf.hasSignbit())) {
|
||||
return AssemblyResolution.error("Value " + goal + " is not valid for " + cf,
|
||||
description);
|
||||
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
|
||||
|
|
|
@ -39,12 +39,14 @@ public class EndInstructionValueSolver extends AbstractExpressionSolver<EndInstr
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(EndInstructionValue iv, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> 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<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> 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<EndInstr
|
|||
@Override
|
||||
public MaskedLong getValue(EndInstructionValue iv, Map<String, Long> 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<EndInstr
|
|||
@Override
|
||||
public MaskedLong valueForResolution(EndInstructionValue exp, Map<String, Long> 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
|
||||
|
|
|
@ -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<Le
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AssemblyResolution solveTwoSided(LeftShiftExpression exp, MaskedLong goal,
|
||||
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) throws NeedsBackfillException, SolverException {
|
||||
protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
LeftShiftExpression exp, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException, SolverException {
|
||||
// Do not guess the same parameter recursively
|
||||
if (hints.contains(DefaultSolverHint.GUESSING_LEFT_SHIFT_AMOUNT)) {
|
||||
// NOTE: Nested left shifts ought to be written as a left shift by a sum
|
||||
return super.solveTwoSided(exp, goal, vals, 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<Le
|
|||
// If the goal is 0s, then any shift will do, so long as the shifted value is 0
|
||||
try {
|
||||
// NB. goal is already 0s, so just use it as subgoal for lhs of shift
|
||||
AssemblyResolution lres =
|
||||
solver.solve(exp.getLeft(), goal, vals, cur, hintsWithLShift, description);
|
||||
AssemblyResolution lres = solver.solve(factory, exp.getLeft(), goal, vals, cur,
|
||||
hintsWithLShift, description);
|
||||
if (lres.isError()) {
|
||||
throw new SolverException("Solving left:=0 failed");
|
||||
}
|
||||
|
@ -97,13 +97,13 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<Le
|
|||
MaskedLong reqr = MaskedLong.fromLong(shift);
|
||||
MaskedLong reql = computeLeft(reqr, goal);
|
||||
|
||||
AssemblyResolution lres =
|
||||
solver.solve(exp.getLeft(), reql, vals, cur, hintsWithLShift, description);
|
||||
AssemblyResolution lres = solver.solve(factory, exp.getLeft(), reql, vals, cur,
|
||||
hintsWithLShift, 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");
|
||||
}
|
||||
|
@ -121,6 +121,6 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<Le
|
|||
// try the next
|
||||
}
|
||||
}
|
||||
return super.solveTwoSided(exp, goal, vals, cur, hints, description);
|
||||
return super.solveTwoSided(factory, exp, goal, vals, cur, hints, description);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.MultExpression;
|
||||
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
|
||||
|
||||
|
@ -102,25 +101,26 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
|
|||
super(MultExpression.class);
|
||||
}
|
||||
|
||||
protected AssemblyResolution tryRep(PatternExpression lexp, MaskedLong rval, MaskedLong repGoal,
|
||||
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description) throws NeedsBackfillException {
|
||||
protected AssemblyResolution tryRep(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
PatternExpression lexp, MaskedLong rval, MaskedLong repGoal, MaskedLong goal,
|
||||
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) throws NeedsBackfillException {
|
||||
MaskedLong lval = repGoal.divideUnsigned(rval);
|
||||
if (lval.multiply(rval).agrees(goal)) {
|
||||
return solver.solve(lexp, lval, vals, 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<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description)
|
||||
protected AssemblyResolution solveLeftSide(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
PatternExpression lexp, MaskedLong rval, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException, SolverException {
|
||||
// Try the usual case first
|
||||
ResultTracker tracker = new ResultTracker();
|
||||
AssemblyResolution sol = tracker.trySolverFunc(() -> {
|
||||
return super.solveLeftSide(lexp, rval, goal, vals, 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<MultExp
|
|||
if (reps > 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<MultExp
|
|||
repMsk = -1L >>> 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<MultExp
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AssemblyResolution solveRightSide(PatternExpression rexp, MaskedLong lval,
|
||||
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description)
|
||||
protected AssemblyResolution solveRightSide(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
PatternExpression rexp, MaskedLong lval, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> 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
|
||||
|
|
|
@ -39,9 +39,10 @@ public class Next2InstructionValueSolver extends AbstractExpressionSolver<Next2I
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(Next2InstructionValue iv, MaskedLong goal,
|
||||
Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) {
|
||||
public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
Next2InstructionValue exp, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException {
|
||||
throw new AssertionError(
|
||||
"INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_NEXT2);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ public class OperandValueSolver extends AbstractExpressionSolver<OperandValue> {
|
|||
* Obtains the "defining expression"
|
||||
*
|
||||
* <p>
|
||||
* 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<OperandValue> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(OperandValue ov, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException {
|
||||
public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
OperandValue ov, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description) throws NeedsBackfillException {
|
||||
Constructor cons = ov.getConstructor();
|
||||
OperandSymbol sym = cons.getOperand(ov.getIndex());
|
||||
PatternExpression patexp = getDefiningExpression(sym);
|
||||
if (patexp == null) {
|
||||
if (goal.equals(MaskedLong.ZERO)) {
|
||||
return AssemblyResolution.nop(description, null, 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
|
||||
|
|
|
@ -53,7 +53,8 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
return goal.invOr(rval);
|
||||
}
|
||||
|
||||
protected AssemblyResolution tryCatenationExpression(OrExpression exp, MaskedLong goal,
|
||||
protected AssemblyResolution tryCatenationExpression(
|
||||
AbstractAssemblyResolutionFactory<?, ?> factory, OrExpression exp, MaskedLong goal,
|
||||
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) throws SolverException {
|
||||
/*
|
||||
|
@ -68,7 +69,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
fields.put(64L, new ConstantValue(0));
|
||||
long lo = 0;
|
||||
PatternExpression fieldExp = null;
|
||||
AssemblyResolvedPatterns result = AssemblyResolution.nop(description);
|
||||
AssemblyResolvedPatterns result = factory.nop(description);
|
||||
try (DbgCtx dc = dbg.start("Trying solution of field catenation")) {
|
||||
dbg.println("Original: " + goal + ":= " + exp);
|
||||
for (Map.Entry<Long, PatternExpression> ent : fields.entrySet()) {
|
||||
|
@ -81,7 +82,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
dbg.println("Part(" + hi + ":" + lo + "]:= " + fieldExp);
|
||||
MaskedLong part = goal.shiftLeft(64 - hi).shiftRightPositional(64 - hi + lo);
|
||||
dbg.println("Solving: " + part + ":= " + fieldExp);
|
||||
AssemblyResolution sol = solver.solve(fieldExp, part, vals, cur, hints,
|
||||
AssemblyResolution sol = solver.solve(factory, fieldExp, part, vals, cur, hints,
|
||||
description + " with shift " + lo);
|
||||
if (sol.isError()) {
|
||||
return sol;
|
||||
|
@ -98,7 +99,8 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
return result;
|
||||
}
|
||||
|
||||
protected AssemblyResolution tryCircularShiftExpression(OrExpression exp, MaskedLong goal,
|
||||
protected AssemblyResolution tryCircularShiftExpression(
|
||||
AbstractAssemblyResolutionFactory<?, ?> factory, OrExpression exp, MaskedLong goal,
|
||||
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) throws SolverException {
|
||||
// If OR is being used to accomplish a circular shift, then we can apply a clever solver.
|
||||
|
@ -166,11 +168,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
// At this point, I know it's a circular shift
|
||||
dbg.println("Identified circular shift: value:= " + expValu1 + ", shift:= " + expShift +
|
||||
", size:= " + size + ", dir:= " + (dir == 1 ? "right" : "left"));
|
||||
return solveLeftCircularShift(expValu1, expShift, size, dir, goal, vals, cur, hints,
|
||||
description);
|
||||
return solveLeftCircularShift(factory, expValu1, expShift, size, dir, goal, vals, cur,
|
||||
hints, description);
|
||||
}
|
||||
|
||||
protected AssemblyResolution solveLeftCircularShift(PatternExpression expValue,
|
||||
protected AssemblyResolution solveLeftCircularShift(
|
||||
AbstractAssemblyResolutionFactory<?, ?> factory, PatternExpression expValue,
|
||||
PatternExpression expShift, int size, int dir, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException, SolverException {
|
||||
|
@ -194,12 +197,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
throw new AssertionError("Should not have constants when solving special forms");
|
||||
}
|
||||
else if (valValue != null) {
|
||||
return solver.solve(expShift, computeCircShiftG(valValue, size, dir, goal), vals, cur,
|
||||
hints, description);
|
||||
return solver.solve(factory, expShift, computeCircShiftG(valValue, size, dir, goal),
|
||||
vals, cur, hints, description);
|
||||
}
|
||||
else if (valShift != null) {
|
||||
return solver.solve(expValue, computeCircShiftF(valShift, size, dir, goal), vals, cur,
|
||||
hints, description);
|
||||
return solver.solve(factory, expValue, computeCircShiftF(valShift, size, dir, goal),
|
||||
vals, cur, hints, description);
|
||||
}
|
||||
|
||||
// Oiy. Try guessing the shift amount, starting at 0
|
||||
|
@ -213,14 +216,14 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
try {
|
||||
MaskedLong reqShift = MaskedLong.fromLong(shift);
|
||||
MaskedLong reqValue = computeCircShiftF(reqShift, size, dir, goal);
|
||||
AssemblyResolution resValue = solver.solve(expValue, reqValue, vals, cur,
|
||||
AssemblyResolution resValue = solver.solve(factory, expValue, reqValue, vals, cur,
|
||||
hintsWithCircularShift, description);
|
||||
if (resValue.isError()) {
|
||||
AssemblyResolvedError err = (AssemblyResolvedError) resValue;
|
||||
throw new SolverException("Solving f failed: " + err.getError());
|
||||
}
|
||||
AssemblyResolution resShift =
|
||||
solver.solve(expShift, reqShift, vals, cur, hints, description);
|
||||
solver.solve(factory, expShift, reqShift, vals, cur, hints, description);
|
||||
if (resShift.isError()) {
|
||||
AssemblyResolvedError err = (AssemblyResolvedError) resShift;
|
||||
throw new SolverException("Solving g failed: " + err.getError());
|
||||
|
@ -267,11 +270,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
}
|
||||
|
||||
@Override
|
||||
protected AssemblyResolution solveTwoSided(OrExpression exp, MaskedLong goal,
|
||||
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) throws NeedsBackfillException, SolverException {
|
||||
protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
OrExpression exp, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> 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<OrExpress
|
|||
}
|
||||
|
||||
try {
|
||||
return tryCircularShiftExpression(exp, goal, vals, cur, hints, description);
|
||||
return tryCircularShiftExpression(factory, exp, goal, vals, cur, hints, description);
|
||||
}
|
||||
catch (Exception e) {
|
||||
dbg.println("while solving: " + goal + "=:" + exp);
|
||||
|
@ -291,21 +295,22 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
|
|||
long value = MATCHERS.val.get(match).getValue();
|
||||
PatternValue field = MATCHERS.fld.get(match);
|
||||
// Solve for equals, then either return that, or forbid it, depending on goal
|
||||
AssemblyResolution solution =
|
||||
solver.solve(field, MaskedLong.fromLong(value), vals, cur, hints, description);
|
||||
AssemblyResolution solution = solver.solve(factory, field, MaskedLong.fromLong(value),
|
||||
vals, cur, hints, description);
|
||||
if (goal.equals(MaskedLong.fromMaskAndValue(0, 1))) {
|
||||
return solution;
|
||||
}
|
||||
if (goal.equals(MaskedLong.fromMaskAndValue(1, 1))) {
|
||||
if (solution.isError()) {
|
||||
return AssemblyResolution.nop(description);
|
||||
return factory.nop(description);
|
||||
}
|
||||
if (solution.isBackfill()) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
AssemblyResolvedPatterns forbidden = (AssemblyResolvedPatterns) solution;
|
||||
forbidden = forbidden.withDescription("Solved 'not equals'");
|
||||
return AssemblyResolution.nop(description).withForbids(Set.of(forbidden));
|
||||
AssemblyResolvedPatterns rp = factory.nop(description);
|
||||
return rp.withForbids(Set.of(forbidden));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
|
|||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution;
|
||||
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
|
||||
import ghidra.app.plugin.assembler.sleigh.sem.*;
|
||||
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
|
||||
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
|
||||
|
||||
|
@ -115,11 +114,13 @@ public class RecursiveDescentSolver {
|
|||
* @return the encoded solution
|
||||
* @throws NeedsBackfillException a solution may exist, but a required symbol is missing
|
||||
*/
|
||||
protected AssemblyResolution solve(PatternExpression exp, MaskedLong goal,
|
||||
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) throws NeedsBackfillException {
|
||||
protected AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
PatternExpression exp, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> 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<String, Long> 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<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, String description) throws NeedsBackfillException {
|
||||
return solve(factory, exp, goal, vals, cur, Set.of(), description);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) throws NeedsBackfillException, SolverException {
|
||||
protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
RightShiftExpression exp, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
|
||||
throws NeedsBackfillException, SolverException {
|
||||
// Do the similar thing as in {@link LeftShiftExpressionSolver}
|
||||
|
||||
// Do not guess the same parameter recursively
|
||||
if (hints.contains(DefaultSolverHint.GUESSING_RIGHT_SHIFT_AMOUNT)) {
|
||||
// NOTE: Nested right shifts ought to be written as a right shift by a sum
|
||||
return super.solveTwoSided(exp, goal, vals, 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,9 @@ public class StartInstructionValueSolver extends AbstractExpressionSolver<StartI
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(StartInstructionValue iv, MaskedLong goal,
|
||||
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
|
||||
String description) {
|
||||
public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
StartInstructionValue iv, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) {
|
||||
throw new AssertionError(
|
||||
"INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_START);
|
||||
}
|
||||
|
|
|
@ -35,15 +35,18 @@ public class TokenFieldSolver extends AbstractExpressionSolver<TokenField> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution solve(TokenField tf, MaskedLong goal, Map<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) {
|
||||
public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
TokenField tf, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
|
||||
Set<SolverHint> hints, String description) {
|
||||
assert tf.minValue() == 0; // In case someone decides to do signedness there.
|
||||
if (!goal.isInRange(tf.maxValue(), tf.hasSignbit())) {
|
||||
return AssemblyResolution.error("Value " + goal + " is not valid for " + tf,
|
||||
description);
|
||||
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
|
||||
|
|
|
@ -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<AssemblyNonTerminal, AssemblyProduction> {
|
||||
protected final AbstractAssemblyResolutionFactory<?, ?> factory;
|
||||
// a nested map of semantics by production, by constructor
|
||||
protected final Map<AssemblyProduction, Map<Constructor, AssemblyConstructorSemantic>> semanticsByProduction =
|
||||
new TreeMap<>();
|
||||
protected final Map<AssemblyProduction, //
|
||||
Map<Constructor, AssemblyConstructorSemantic>> semanticsByProduction = new TreeMap<>();
|
||||
protected final Map<Constructor, AssemblyConstructorSemantic> semanticsByConstructor =
|
||||
new HashMap<>();
|
||||
// a map of purely recursive, e.g., I => I, productions by name of LHS
|
||||
protected final Map<String, AssemblyProduction> pureRecursive = new TreeMap<>();
|
||||
|
||||
public AssemblyGrammar(AbstractAssemblyResolutionFactory<?, ?> factory) {
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AssemblyProduction newProduction(AssemblyNonTerminal lhs,
|
||||
AssemblySentential<AssemblyNonTerminal> rhs) {
|
||||
|
@ -73,7 +79,7 @@ public class AssemblyGrammar
|
|||
Map<Constructor, AssemblyConstructorSemantic> 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");
|
||||
|
|
|
@ -28,4 +28,8 @@ public class AssemblyProduction extends AbstractAssemblyProduction<AssemblyNonTe
|
|||
AssemblySentential<AssemblyNonTerminal> rhs) {
|
||||
super(lhs, rhs);
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -127,7 +127,7 @@ public class AssemblySentential<NT extends AssemblyNonTerminal>
|
|||
*/
|
||||
private static class WhiteSpace extends AssemblyStringTerminal {
|
||||
private WhiteSpace() {
|
||||
super(" ");
|
||||
super(" ", null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -166,6 +166,11 @@ public class AssemblySentential<NT extends AssemblyNonTerminal>
|
|||
public Collection<String> getSuggestions(String got, AssemblyNumericSymbols symbols) {
|
||||
return Collections.singleton(" ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWhiteSpace() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,7 +222,7 @@ public class AssemblySentential<NT extends AssemblyNonTerminal>
|
|||
* 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<NT extends AssemblyNonTerminal>
|
|||
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();
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* These may represent a successful construction ({@link AssemblyResolvedPatterns}, a future field
|
||||
* ({@link AssemblyResolvedBackfill}), or an error ({@link AssemblyResolvedError}).
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolution> 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<? extends AssemblyResolution> 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<AssemblyResolution> getAllRight() {
|
||||
List<AssemblyResolution> result = new ArrayList<>();
|
||||
collectAllRight(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void collectAllRight(Collection<AssemblyResolution> into) {
|
||||
into.add(this);
|
||||
if (right == null) {
|
||||
return;
|
||||
}
|
||||
right.collectAllRight(into);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child portion of {@link #toString()}
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolution> getChildren() {
|
||||
return children;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssemblyResolution getRight() {
|
||||
return right;
|
||||
}
|
||||
}
|
|
@ -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<B, T>, //
|
||||
T extends AssemblyResolution> {
|
||||
protected String description;
|
||||
protected List<AssemblyResolution> 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<AssemblyResolution> 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<AbstractAssemblyResolvedPatternsBuilder<RP>, RP> {
|
||||
protected Constructor cons;
|
||||
protected AssemblyPatternBlock ins;
|
||||
protected AssemblyPatternBlock ctx;
|
||||
protected Set<AssemblyResolvedBackfill> backfills;
|
||||
protected Set<AssemblyResolvedPatterns> 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<AbstractAssemblyResolvedBackfillBuilder<BF>, BF> {
|
||||
protected PatternExpression exp;
|
||||
protected MaskedLong goal;
|
||||
protected int inslen;
|
||||
protected int offset;
|
||||
}
|
||||
|
||||
public class DefaultAssemblyResolvedPatternBuilder
|
||||
extends AbstractAssemblyResolvedPatternsBuilder<AssemblyResolvedPatterns> {
|
||||
@Override
|
||||
protected AssemblyResolvedPatterns build() {
|
||||
return new DefaultAssemblyResolvedPatterns(AbstractAssemblyResolutionFactory.this,
|
||||
description, cons, children, right, ins, ctx, backfills, forbids);
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultAssemblyResolvedBackfillBuilder
|
||||
extends AbstractAssemblyResolvedBackfillBuilder<AssemblyResolvedBackfill> {
|
||||
@Override
|
||||
protected AssemblyResolvedBackfill build() {
|
||||
return new DefaultAssemblyResolvedBackfill(AbstractAssemblyResolutionFactory.this,
|
||||
description, exp, goal, inslen, offset);
|
||||
}
|
||||
}
|
||||
|
||||
public class AssemblyResolvedErrorBuilder extends
|
||||
AbstractAssemblyResolutionBuilder<AssemblyResolvedErrorBuilder, AssemblyResolvedError> {
|
||||
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<RP> newPatternsBuilder();
|
||||
|
||||
public abstract AbstractAssemblyResolvedBackfillBuilder<BF> 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<AssemblyResolution> 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<String, Long> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Long> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Long> 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<RP> 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<RP> nopBuilder(String description,
|
||||
List<AssemblyResolution> 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<AssemblyResolution> children, AssemblyResolution right) {
|
||||
return nopBuilder(description, children, right).build();
|
||||
}
|
||||
|
||||
public AbstractAssemblyResolvedBackfillBuilder<BF> 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<RP> resolvedBuilder(AssemblyPatternBlock ins,
|
||||
AssemblyPatternBlock ctx, String description, Constructor cons,
|
||||
List<AssemblyResolution> 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
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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<AssemblyResolution> children, AssemblyResolution right) {
|
||||
return resolvedBuilder(ins, ctx, description, cons, children, right).build();
|
||||
}
|
||||
|
||||
protected AbstractAssemblyResolvedPatternsBuilder<RP> 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<RP> 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<RP> 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<RP> fromStringBuilder(String str,
|
||||
String description, List<AssemblyResolution> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolution> children) {
|
||||
return fromStringBuilder(str, description, children).build();
|
||||
}
|
||||
}
|
|
@ -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<AssemblyConstructorSemantic> 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<AssemblyConstructorSemantic> 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<AssemblyResolvedPatterns> resolve(AssemblyResolvedPatterns fromRight,
|
||||
Collection<AssemblyResolvedError> errors);
|
||||
|
||||
public AbstractAssemblyTreeResolver<?> getResolver() {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
public List<AssemblyConstructorSemantic> getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
public int getShift() {
|
||||
return shift;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the length in bytes of the operand represented by this node
|
||||
*
|
||||
|
|
|
@ -29,7 +29,7 @@ import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
|
|||
* @param <N> the type of parse tree node to process
|
||||
*/
|
||||
public abstract class AbstractAssemblyStateGenerator<N extends AssemblyParseTreeNode> {
|
||||
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<N extends AssemblyParseTree
|
|||
path.stream().map(sem -> sem.getLocation()).collect(Collectors.joining(",")) + "]";
|
||||
}
|
||||
|
||||
final List<AssemblyConstructorSemantic> path;
|
||||
final int shift;
|
||||
public final List<AssemblyConstructorSemantic> path;
|
||||
public final int shift;
|
||||
|
||||
/**
|
||||
* Construct a context
|
||||
|
@ -84,7 +84,7 @@ public abstract class AbstractAssemblyStateGenerator<N extends AssemblyParseTree
|
|||
}
|
||||
}
|
||||
|
||||
protected final AssemblyTreeResolver resolver;
|
||||
protected final AbstractAssemblyTreeResolver<?> resolver;
|
||||
protected final N node;
|
||||
protected final AssemblyResolvedPatterns fromLeft;
|
||||
|
||||
|
@ -95,7 +95,7 @@ public abstract class AbstractAssemblyStateGenerator<N extends AssemblyParseTree
|
|||
* @param node the node from which to generate states
|
||||
* @param fromLeft the accumulated patterns from the left sibling or the parent
|
||||
*/
|
||||
public AbstractAssemblyStateGenerator(AssemblyTreeResolver resolver, N node,
|
||||
public AbstractAssemblyStateGenerator(AbstractAssemblyTreeResolver<?> resolver, N node,
|
||||
AssemblyResolvedPatterns fromLeft) {
|
||||
this.resolver = resolver;
|
||||
this.node = node;
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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<RP extends AssemblyResolvedPatterns> {
|
||||
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<RP, ?> factory;
|
||||
protected final SleighLanguage lang;
|
||||
protected final Address at;
|
||||
protected final Map<String, Long> 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<RP, ?> 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<RP, ?> 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<AssemblyResolvedError> errors = new ArrayList<>();
|
||||
Stream<AssemblyGeneratedPrototype> 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<AssemblyResolvedPatterns> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<Deque<AssemblyConstructorSemantic>> paths =
|
||||
ctxGraph.computeOptimalApplications(src, table, dst, table);
|
||||
DBG.println("Found " + paths.size());
|
||||
for (Deque<AssemblyConstructorSemantic> path : paths) {
|
||||
DBG.println(" " + path);
|
||||
result.absorb(applyRecursionPath(path, tree, rootRec, ar));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt a second time to solve operands and context changes
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolution> patterns =
|
||||
sem.getPatterns()
|
||||
.stream()
|
||||
.map(p -> p.shift(shift))
|
||||
.collect(Collectors.toList());
|
||||
return temp.apply(factory, new Applicator() {
|
||||
@Override
|
||||
public Iterable<AssemblyResolution> 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
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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<AssemblyConstructorSemantic> 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
|
||||
*
|
||||
* <p>
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ public class AssemblyConstructState extends AbstractAssemblyState {
|
|||
* @param operands the operands
|
||||
* @return the farthest end byte
|
||||
*/
|
||||
protected static int computeEnd(List<AbstractAssemblyState> operands) {
|
||||
protected static int computeEnd(List<? extends AbstractAssemblyState> 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<AssemblyConstructorSemantic> path, int shift,
|
||||
AssemblyConstructorSemantic sem, List<AbstractAssemblyState> 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;
|
||||
|
|
|
@ -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<AssemblyParseTreeNode> orderOpNodes(AssemblyConstructorSemantic sem) {
|
||||
Constructor cons = sem.getConstructor();
|
||||
List<AssemblyParseTreeNode> result =
|
||||
Arrays.asList(new AssemblyParseTreeNode[cons.getNumOperands()]);
|
||||
AssemblyParseTreeNode[] arr = new AssemblyParseTreeNode[cons.getNumOperands()];
|
||||
List<AssemblyParseTreeNode> result = Arrays.asList(arr);
|
||||
Arrays.fill(arr, new AssemblyParseHiddenNode(resolver.grammar));
|
||||
int index = 0;
|
||||
AssemblyProduction production = node.getProduction();
|
||||
List<AssemblyParseTreeNode> 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
|
||||
|
|
|
@ -36,9 +36,10 @@ import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol;
|
|||
*/
|
||||
public class AssemblyConstructorSemantic implements Comparable<AssemblyConstructorSemantic> {
|
||||
protected static final RecursiveDescentSolver SOLVER = RecursiveDescentSolver.getSolver();
|
||||
protected static final DbgTimer DBG = AssemblyTreeResolver.DBG;
|
||||
protected static final DbgTimer DBG = AbstractAssemblyTreeResolver.DBG;
|
||||
|
||||
protected final Set<AssemblyResolvedPatterns> patterns = new HashSet<>();
|
||||
protected final AbstractAssemblyResolutionFactory<?, ?> factory;
|
||||
protected final Constructor cons;
|
||||
protected final List<Integer> indices;
|
||||
protected final List<ContextChange> contextChanges;
|
||||
|
@ -54,7 +55,9 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
|
|||
* @param indices the indices of RHS non-terminals in the associated production that represent
|
||||
* an operand in the SLEIGH constructor
|
||||
*/
|
||||
public AssemblyConstructorSemantic(Constructor cons, List<Integer> indices) {
|
||||
public AssemblyConstructorSemantic(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
Constructor cons, List<Integer> indices) {
|
||||
this.factory = factory;
|
||||
this.cons = cons;
|
||||
this.indices = Collections.unmodifiableList(indices);
|
||||
List<ContextChange> changes = new ArrayList<>(cons.getContextChanges());
|
||||
|
@ -69,7 +72,7 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
|
|||
* @param pat the pattern
|
||||
*/
|
||||
public void addPattern(DisjointPattern pat) {
|
||||
addPattern(AssemblyResolution.fromPattern(pat, cons.getMinimumLength(),
|
||||
addPattern(factory.fromPattern(pat, cons.getMinimumLength(),
|
||||
"Generated constructor pattern " + getLocation(), cons));
|
||||
}
|
||||
|
||||
|
@ -238,7 +241,7 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
|
|||
*/
|
||||
|
||||
// If the two patterns cannot be combined, then they are disjoint.
|
||||
AssemblyResolvedPatterns sibpat = AssemblyResolution.fromPattern(sibDP,
|
||||
AssemblyResolvedPatterns sibpat = factory.fromPattern(sibDP,
|
||||
sibcons.getMinimumLength(), "For specialization check", sibcons);
|
||||
AssemblyResolvedPatterns comb = pat.combine(sibpat);
|
||||
if (null == comb) {
|
||||
|
@ -258,7 +261,7 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
|
|||
}
|
||||
|
||||
// We can't apply the line number rule unless the sibling's context is an overset
|
||||
if (!comb.ctx.equals(pat.ctx)) {
|
||||
if (!comb.getContext().equals(pat.getContext())) {
|
||||
return CONTINUE;
|
||||
}
|
||||
|
||||
|
@ -320,12 +323,12 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
|
|||
DBG.println("Masked out: " + res.lineToString());
|
||||
|
||||
// Now, solve
|
||||
AssemblyResolution sol = AssemblyTreeResolver.solveOrBackfill(
|
||||
cop.getPatternExpression(), reqval, vals, res, "Solution to " + cop);
|
||||
AssemblyResolution sol = factory.solveOrBackfill(cop.getPatternExpression(), reqval,
|
||||
vals, res, "Solution to " + cop);
|
||||
DBG.println("Solution: " + sol.lineToString());
|
||||
if (sol.isError()) {
|
||||
AssemblyResolvedError err = (AssemblyResolvedError) sol;
|
||||
return AssemblyResolution.error(err.getError(), res);
|
||||
return factory.error(err.getError(), res);
|
||||
}
|
||||
|
||||
// Now, forward the new requirements to my parents.
|
||||
|
@ -333,8 +336,7 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
|
|||
AssemblyResolvedPatterns solcon = (AssemblyResolvedPatterns) sol;
|
||||
AssemblyResolvedPatterns check = res.combine(solcon);
|
||||
if (null == check) {
|
||||
return AssemblyResolution.error(
|
||||
"A context change caused a conflict: " + sol, res);
|
||||
return factory.error("A context change caused a conflict: " + sol, res);
|
||||
}
|
||||
res = check;
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ import ghidra.graph.algo.DijkstraShortestPathsAlgorithm;
|
|||
* much larger its LALR(1) parser would become.
|
||||
*/
|
||||
public class AssemblyContextGraph implements GImplicitDirectedGraph<Vertex, Edge> {
|
||||
protected final AbstractAssemblyResolutionFactory<?, ?> factory;
|
||||
protected final Map<String, Set<AssemblyConstructorSemantic>> semantics =
|
||||
LazyMap.lazyMap(new HashMap<>(), () -> new HashSet<>());
|
||||
protected final AssemblyGrammar grammar;
|
||||
|
@ -72,7 +73,9 @@ public class AssemblyContextGraph implements GImplicitDirectedGraph<Vertex, Edge
|
|||
* @param lang the language
|
||||
* @param grammar the grammar derived from the given language
|
||||
*/
|
||||
public AssemblyContextGraph(SleighLanguage lang, AssemblyGrammar grammar) {
|
||||
public AssemblyContextGraph(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
SleighLanguage lang, AssemblyGrammar grammar) {
|
||||
this.factory = factory;
|
||||
this.grammar = grammar;
|
||||
this.lang = lang;
|
||||
|
||||
|
@ -343,7 +346,7 @@ public class AssemblyContextGraph implements GImplicitDirectedGraph<Vertex, Edge
|
|||
Set<Edge> 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<Vertex, Edge
|
|||
continue;
|
||||
}
|
||||
|
||||
AssemblyResolvedPatterns orc =
|
||||
AssemblyResolution.contextOnly(outer, "For context transition");
|
||||
AssemblyResolvedPatterns orc = factory.contextOnly(outer, "For context transition");
|
||||
AssemblyResolvedPatterns irc = sem.applyContextChangesForward(Map.of(), orc);
|
||||
AssemblyPatternBlock inner = irc.getContext();
|
||||
|
||||
|
|
|
@ -15,11 +15,10 @@
|
|||
*/
|
||||
package ghidra.app.plugin.assembler.sleigh.sem;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.*;
|
||||
|
||||
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseHiddenNode;
|
||||
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseTreeNode;
|
||||
import ghidra.app.plugin.processors.sleigh.Constructor;
|
||||
import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol;
|
||||
|
@ -42,7 +41,7 @@ public class AssemblyHiddenConstructStateGenerator extends AssemblyConstructStat
|
|||
* @param subtableSym
|
||||
* @param fromLeft the accumulated patterns from the left sibling or the parent
|
||||
*/
|
||||
public AssemblyHiddenConstructStateGenerator(AssemblyTreeResolver resolver,
|
||||
public AssemblyHiddenConstructStateGenerator(AbstractAssemblyTreeResolver<?> 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<AssemblyParseTreeNode> 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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AssemblyConstructorSemantic> path,
|
||||
int shift, OperandSymbol opSym) {
|
||||
|
||||
public AssemblyNopState(AbstractAssemblyTreeResolver<?> resolver,
|
||||
List<AssemblyConstructorSemantic> 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";
|
||||
|
|
|
@ -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<AssemblyGeneratedPrototype> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AssemblyConstructorSemantic> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<AssemblyPatternBlock> {
|
|||
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<AssemblyPatternBlock> {
|
|||
/**
|
||||
* Convert a register value into a pattern block
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyPatternBlock> {
|
|||
return new AssemblyPatternBlock(offset, newMask, newVals);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all bits that are known (1 in mask) in {@code other} to unknown.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* Modifications to the returned array will affect the pattern block. It is <em>not</em> 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<AssemblyPatternBlock> {
|
|||
/**
|
||||
* Get the mask array
|
||||
*
|
||||
* <p>
|
||||
* Modifications to the returned array will affect the pattern block. It is <em>not</em> 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.
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
public interface AssemblyResolution extends Comparable<AssemblyResolution> {
|
||||
|
||||
/**
|
||||
* The (often intermediate) result of assembly
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* <p>
|
||||
* These may represent a successful construction ({@link AssemblyResolvedPatterns}, a future field
|
||||
* ({@link AssemblyResolvedBackfill}), or an error ({@link AssemblyResolvedError}).
|
||||
*
|
||||
* <p>
|
||||
* This class also provides the static factory methods for constructing any of its subclasses.
|
||||
*/
|
||||
public abstract class AssemblyResolution implements Comparable<AssemblyResolution> {
|
||||
protected final String description;
|
||||
protected final List<AssemblyResolution> 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();
|
||||
|
||||
/**
|
||||
* 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<? extends AssemblyResolution> 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
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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<? extends AssemblyResolution> 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<? extends AssemblyResolution> 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<? extends AssemblyResolution> 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<AssemblyResolution> getAllRight() {
|
||||
List<AssemblyResolution> result = new ArrayList<>();
|
||||
collectAllRight(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void collectAllRight(Collection<AssemblyResolution> into) {
|
||||
into.add(this);
|
||||
if (right == null) {
|
||||
return;
|
||||
}
|
||||
right.collectAllRight(into);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child portion of {@link #toString()}
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolution> getChildren();
|
||||
|
||||
/**
|
||||
* Check if this record has children
|
||||
|
@ -306,15 +44,30 @@ public abstract class AssemblyResolution implements Comparable<AssemblyResolutio
|
|||
* @see #childrenToString(String)
|
||||
* @return true if this record has children
|
||||
*/
|
||||
public boolean hasChildren() {
|
||||
if (children == null) {
|
||||
return false;
|
||||
}
|
||||
if (children.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
boolean hasChildren();
|
||||
|
||||
AssemblyResolution getRight();
|
||||
|
||||
/**
|
||||
* Display the resolution result in one line (omitting child details)
|
||||
*
|
||||
* @return the display description
|
||||
*/
|
||||
String lineToString();
|
||||
|
||||
/**
|
||||
* Check if this record describes a backfill
|
||||
*
|
||||
* @return true if the record is a backfill
|
||||
*/
|
||||
boolean isBackfill();
|
||||
|
||||
/**
|
||||
* Check if this record describes an error
|
||||
*
|
||||
* @return true if the record is an error
|
||||
*/
|
||||
boolean isError();
|
||||
|
||||
/**
|
||||
* Shift the resolution's instruction pattern to the right, if applicable
|
||||
|
@ -325,26 +78,20 @@ public abstract class AssemblyResolution implements Comparable<AssemblyResolutio
|
|||
* @param amt the number of bytes to shift.
|
||||
* @return the result
|
||||
*/
|
||||
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);
|
||||
AssemblyResolution shift(int amt);
|
||||
|
||||
/**
|
||||
* Get this same resolution, pushing its right siblings down to its children
|
||||
*/
|
||||
public abstract AssemblyResolution parent(String description, int opCount);
|
||||
AssemblyResolution parent(String description, int opCount);
|
||||
|
||||
void collectAllRight(Collection<AssemblyResolution> 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);
|
||||
}
|
||||
|
|
|
@ -27,21 +27,21 @@ import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
|
|||
* A set of possible assembly resolutions for a single SLEIGH constructor
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolution> {
|
||||
protected static final DbgTimer DBG = AssemblyTreeResolver.DBG;
|
||||
protected static final DbgTimer DBG = AbstractAssemblyTreeResolver.DBG;
|
||||
|
||||
public interface Applicator {
|
||||
Iterable<? extends AssemblyResolution> 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<AssemblyReso
|
|||
resolutions = new LinkedHashSet<>();
|
||||
}
|
||||
|
||||
private AssemblyResolutionResults(Set<AssemblyResolution> resolutions) {
|
||||
protected AssemblyResolutionResults(Set<AssemblyResolution> 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<AssemblyReso
|
|||
return this.resolutions.remove(ar);
|
||||
}
|
||||
|
||||
protected AssemblyResolutionResults apply(Applicator applicator) {
|
||||
AssemblyResolutionResults results = new AssemblyResolutionResults();
|
||||
protected AssemblyResolutionResults apply(AbstractAssemblyResolutionFactory<?, ?> 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<AssemblyReso
|
|||
return results;
|
||||
}
|
||||
|
||||
protected AssemblyResolutionResults apply(
|
||||
protected AssemblyResolutionResults apply(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
Function<AssemblyResolvedPatterns, AssemblyResolution> 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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Long> 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<String, Long> vals,
|
||||
AssemblyResolvedPatterns cur);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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<? extends AssemblyResolution> 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<AssemblyResolution> allRight = getAllRight();
|
||||
return new AssemblyResolvedError(description, allRight, null, error);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
public interface AssemblyResolvedPatterns extends AssemblyResolution {
|
||||
|
||||
/**
|
||||
* A {@link AssemblyResolution} indicating successful application of a constructor
|
||||
* Get the instruction block
|
||||
*
|
||||
* @return the instruction block
|
||||
*/
|
||||
AssemblyPatternBlock getInstruction();
|
||||
|
||||
/**
|
||||
* Get the context block
|
||||
*
|
||||
* @return the context block
|
||||
*/
|
||||
AssemblyPatternBlock getContext();
|
||||
|
||||
/**
|
||||
* Get the length of the instruction encoding
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* This is used to ensure each operand is encoded at the correct offset
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* <b>NOTE:</b> this DOES include the offset<br>
|
||||
* <b>NOTE:</b> this DOES include pending backfills
|
||||
*
|
||||
* @return the length of the instruction block
|
||||
*/
|
||||
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<AssemblyResolvedBackfill> backfills;
|
||||
protected final Set<AssemblyResolvedPatterns> 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;
|
||||
}
|
||||
int getInstructionLength();
|
||||
|
||||
/**
|
||||
* @see AssemblyResolution#resolved(AssemblyPatternBlock, AssemblyPatternBlock, String, Constructor, List, AssemblyResolution)
|
||||
*/
|
||||
AssemblyResolvedPatterns(String description, Constructor cons,
|
||||
List<? extends AssemblyResolution> children, AssemblyResolution right,
|
||||
AssemblyPatternBlock ins, AssemblyPatternBlock ctx,
|
||||
Set<AssemblyResolvedBackfill> backfills, Set<AssemblyResolvedPatterns> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new successful SLEIGH constructor resolution from a string representation
|
||||
* Get the length of the instruction encoding, excluding trailing undefined bytes
|
||||
*
|
||||
* <p>
|
||||
* This was used primarily in testing, to specify expected results.
|
||||
* <b>NOTE:</b> this DOES include the offset<br>
|
||||
* <b>NOTE:</b> this DOES NOT include pending backfills
|
||||
*
|
||||
* @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
|
||||
* @return the length of the defined bytes in the instruction block
|
||||
*/
|
||||
public static AssemblyResolvedPatterns fromString(String str, String description,
|
||||
List<AssemblyResolution> 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<AssemblyResolvedBackfill> newBackfills = new HashSet<>();
|
||||
for (AssemblyResolvedBackfill bf : this.backfills) {
|
||||
newBackfills.add(bf.shift(amt));
|
||||
}
|
||||
|
||||
Set<AssemblyResolvedPatterns> 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));
|
||||
}
|
||||
int getDefinedInstructionLength();
|
||||
|
||||
/**
|
||||
* Truncate (unshift) the resolved instruction pattern from the left
|
||||
* Get the backfill records for this resolution, if any
|
||||
*
|
||||
* <b>NOTE:</b> 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
|
||||
* @return the backfills
|
||||
*/
|
||||
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);
|
||||
}
|
||||
Collection<AssemblyResolvedBackfill> getBackfills();
|
||||
|
||||
/**
|
||||
* Check if the current encoding is forbidden by one of the attached patterns
|
||||
* Check if this resolution has pending backfills to apply
|
||||
*
|
||||
* @return true if there are backfills
|
||||
*/
|
||||
boolean hasBackfills();
|
||||
|
||||
/**
|
||||
* Get the forbidden patterns for this resolution
|
||||
*
|
||||
* <p>
|
||||
* The pattern becomes forbidden if this encoding's known bits are an overset of any forbidden
|
||||
* pattern's known bits.
|
||||
* 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.
|
||||
*
|
||||
* @return false if the pattern is forbidden (and thus in error), true if permitted
|
||||
* @return the forbidden patterns
|
||||
*/
|
||||
public AssemblyResolution checkNotForbidden() {
|
||||
Set<AssemblyResolvedPatterns> 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));
|
||||
}
|
||||
Collection<AssemblyResolvedPatterns> getForbids();
|
||||
|
||||
/**
|
||||
* Check if this and another resolution have equal encodings
|
||||
* Decode a portion of the instruction block
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* @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 boolean bitsEqual(AssemblyResolvedPatterns that) {
|
||||
return this.ins.equals(that.ins) && this.ctx.equals(that.ctx);
|
||||
}
|
||||
MaskedLong readInstruction(int byteStart, int size);
|
||||
|
||||
/**
|
||||
* Combine the encodings and backfills of the given resolution into this one
|
||||
* Decode a portion of the context block
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
* @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(AssemblyResolvedPatterns that) {
|
||||
// Not really a backfill, but I would like to re-use code
|
||||
return combineLessBackfill(that, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine a backfill result
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*/
|
||||
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<AssemblyResolvedBackfill> newBackfills = new HashSet<>(this.backfills);
|
||||
newBackfills.addAll(that.backfills);
|
||||
if (bf != null) {
|
||||
newBackfills.remove(bf);
|
||||
}
|
||||
Set<AssemblyResolvedPatterns> newForbids = new HashSet<>(this.forbids);
|
||||
newForbids.addAll(that.forbids);
|
||||
return new AssemblyResolvedPatterns(description, cons, children, right, newIns, newCtx,
|
||||
Collections.unmodifiableSet(newBackfills), Collections.unmodifiableSet(newForbids));
|
||||
}
|
||||
|
||||
/**
|
||||
* Combine the given backfill record into this resolution
|
||||
*
|
||||
* @param bf the backfill record
|
||||
* @return the result
|
||||
*/
|
||||
public AssemblyResolvedPatterns combine(AssemblyResolvedBackfill bf) {
|
||||
Set<AssemblyResolvedBackfill> 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<AssemblyResolvedPatterns> more) {
|
||||
Set<AssemblyResolvedPatterns> 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
|
||||
* <p>
|
||||
* 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<AssemblyResolution> 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Long> 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<String, Long> 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
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* 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<String, Long> vals) {
|
||||
if (!hasForbids()) {
|
||||
return this;
|
||||
}
|
||||
Set<AssemblyResolvedPatterns> 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
|
||||
*
|
||||
* <p>
|
||||
* This is used to ensure each operand is encoded at the correct offset
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> this DOES include the offset<br>
|
||||
* <b>NOTE:</b> 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
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> this DOES include the offset<br>
|
||||
* <b>NOTE:</b> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<String> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Long> 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<byte[]> possibleInsVals(AssemblyPatternBlock forCtx) {
|
||||
AssemblyPatternBlock ctxCompat = ctx.combine(forCtx);
|
||||
if (ctxCompat == null) {
|
||||
return List.of();
|
||||
}
|
||||
Predicate<byte[]> 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<byte[]>() {
|
||||
@Override
|
||||
public Iterator<byte[]> 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<byte[]> 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
|
||||
* <p>
|
||||
* 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<String> 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<Integer> 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
|
||||
*
|
||||
* <b>NOTE:</b> 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<AssemblyResolvedPatterns> 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
|
||||
*
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
|
|
@ -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<AssemblyParseToken> {
|
||||
protected final OperandSymbol opSym;
|
||||
|
||||
public AssemblyStringStateGenerator(AbstractAssemblyTreeResolver<?> resolver,
|
||||
AssemblyParseToken node, OperandSymbol opSym, AssemblyResolvedPatterns fromLeft) {
|
||||
super(resolver, node, fromLeft);
|
||||
this.opSym = opSym;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<AssemblyGeneratedPrototype> generate(GeneratorContext gc) {
|
||||
return Stream.of(new AssemblyGeneratedPrototype(
|
||||
new AssemblyOperandState(resolver, gc.path, gc.shift, node.getSym(), 0, opSym),
|
||||
fromLeft));
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolvedPatterns> {
|
||||
|
||||
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<String, Long> 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<AssemblyResolvedError> errors = new ArrayList<>();
|
||||
Stream<AssemblyGeneratedPrototype> 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<AssemblyResolvedPatterns> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<Deque<AssemblyConstructorSemantic>> paths =
|
||||
ctxGraph.computeOptimalApplications(src, table, dst, table);
|
||||
DBG.println("Found " + paths.size());
|
||||
for (Deque<AssemblyConstructorSemantic> path : paths) {
|
||||
DBG.println(" " + path);
|
||||
result.absorb(applyRecursionPath(path, tree, rootRec, rc));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt a second time to solve operands and context changes
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolvedPatterns> patterns =
|
||||
sem.getPatterns().stream().map(p -> p.shift(shift)).collect(Collectors.toList());
|
||||
return temp.apply(new Applicator() {
|
||||
@Override
|
||||
public Iterable<? extends AssemblyResolution> 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
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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<AssemblyConstructorSemantic> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Long> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Long> vals, AssemblyResolvedPatterns cur, String description) {
|
||||
return solveOrBackfill(exp, MaskedLong.fromLong(goal), vals, cur, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to solve an expression
|
||||
*
|
||||
* <p>
|
||||
* 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<String, Long> 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<AssemblyResolvedPatterns, ?> factory,
|
||||
SleighLanguage lang, Address at, AssemblyParseBranch tree, AssemblyPatternBlock context,
|
||||
AssemblyContextGraph ctxGraph) {
|
||||
super(factory, lang, at, tree, context, ctxGraph);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AssemblyResolvedPatterns, AssemblyResolvedBackfill> {
|
||||
|
||||
@Override
|
||||
public DefaultAssemblyResolvedPatternBuilder newPatternsBuilder() {
|
||||
return new DefaultAssemblyResolvedPatternBuilder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultAssemblyResolvedBackfillBuilder newBackfillBuilder() {
|
||||
return new DefaultAssemblyResolvedBackfillBuilder();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> 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<String, Long> 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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<? extends AssemblyResolution> 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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolvedBackfill> backfills;
|
||||
protected final Set<AssemblyResolvedPatterns> forbids;
|
||||
|
||||
/**
|
||||
* @see AssemblyResolution#resolved(AssemblyPatternBlock, AssemblyPatternBlock, String,
|
||||
* Constructor, List, AssemblyResolution)
|
||||
*/
|
||||
protected DefaultAssemblyResolvedPatterns(AbstractAssemblyResolutionFactory<?, ?> factory,
|
||||
String description, Constructor cons, List<? extends AssemblyResolution> children,
|
||||
AssemblyResolution right, AssemblyPatternBlock ins, AssemblyPatternBlock ctx,
|
||||
Set<AssemblyResolvedBackfill> backfills, Set<AssemblyResolvedPatterns> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<AssemblyResolvedPatterns> 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<AssemblyResolvedPatterns> 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<AssemblyResolution> 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<String, Long> 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<String, Long> 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<String, Long> 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<String> 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
|
||||
*
|
||||
* <p>
|
||||
* 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<byte[]> possibleInsVals(AssemblyPatternBlock forCtx) {
|
||||
AssemblyPatternBlock ctxCompat = ctx.combine(forCtx);
|
||||
if (ctxCompat == null) {
|
||||
return List.of();
|
||||
}
|
||||
Predicate<byte[]> 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<byte[]>() {
|
||||
@Override
|
||||
public Iterator<byte[]> 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<String> 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<Integer> 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<AssemblyResolvedBackfill> getBackfills() {
|
||||
return backfills;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<AssemblyResolvedPatterns> getForbids() {
|
||||
return forbids;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,4 +69,8 @@ public class AssemblyNumericMapTerminal extends AssemblyNumericTerminal {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<Long, Integer> getMap() {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -289,4 +289,8 @@ public class AssemblyNumericTerminal extends AssemblyTerminal {
|
|||
public int getBitSize() {
|
||||
return bitsize;
|
||||
}
|
||||
|
||||
public AddressSpace getSpace() {
|
||||
return space;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, Integer> getMap() {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -166,9 +166,8 @@ public class AssemblyParseBranch extends AssemblyParseTreeNode
|
|||
return substs.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isConstructor() {
|
||||
return true;
|
||||
return prod.isConstructor();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -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("<hidden>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String generateString() {
|
||||
return "";
|
||||
}
|
||||
}
|
|
@ -84,9 +84,4 @@ public class AssemblyParseNumericToken extends AssemblyParseToken {
|
|||
public long getNumericValue() {
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNumeric() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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<String> disassembly,
|
||||
long addr, String ctxstr) {
|
||||
protected void checkAllExact(AssemblyResolutionResults rr,
|
||||
Collection<String> 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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,30 +165,27 @@ public class SolverTest {
|
|||
|
||||
@Test
|
||||
public void testCatOrSolver() throws SAXException, NeedsBackfillException {
|
||||
XmlPullParser parser = XmlPullParserFactory.create("" + //
|
||||
//
|
||||
"<or_exp>\n" + //
|
||||
" <lshift_exp>\n" + //
|
||||
" <tokenfield bigendian='false' signbit='false' bitstart='0' bitend='3' bytestart='0' byteend='0' shift='0'/>\n" + //
|
||||
" <intb val='4'/>\n" + //
|
||||
" </lshift_exp>\n" + //
|
||||
" <tokenfield bigendian='false' signbit='false' bitstart='8' bitend='11' bytestart='1' byteend='1' shift='0'/>\n" + //
|
||||
"</or_exp>\n" + //
|
||||
"", "Test", null, true);
|
||||
XmlPullParser parser = XmlPullParserFactory.create("""
|
||||
<or_exp>
|
||||
<lshift_exp>
|
||||
<tokenfield bigendian='false' signbit='false' bitstart='0' bitend='3'
|
||||
bytestart='0' byteend='0' shift='0'/>
|
||||
<intb val='4'/>
|
||||
</lshift_exp>
|
||||
<tokenfield bigendian='false' signbit='false' bitstart='8' bitend='11'
|
||||
bytestart='1' byteend='1' shift='0'/>
|
||||
</or_exp>
|
||||
""",
|
||||
"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 {
|
||||
if (!Application.isInitialized()) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue