GP-4185: Make Assembler more extensible

This commit is contained in:
Dan
2023-11-03 17:38:07 -04:00
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;
@@ -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;
}
@@ -295,7 +298,7 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
*
* @param res the combined resolution requirements derived from the subconstructors
* @param vals any defined symbols (usually {@code inst_start}, and {@code inst_next})
* @return the resolution with context changes applied in reverse, or an error
* @return the resolution with context changes applied in reverse, or an error
*/
public AssemblyResolution solveContextChanges(AssemblyResolvedPatterns res,
Map<String, Long> vals) {
@@ -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;
/**
* 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 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();
public interface AssemblyResolution extends Comparable<AssemblyResolution> {
/**
* 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
* {@inheritDoc}
*
* <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);
}
}
@@ -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));
}
}

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