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

View file

@ -85,8 +85,8 @@ public class StackUnwinderTest extends AbstractGhidraHeadedDebuggerTest {
public static final AssemblySelector NO_16BIT_CALLS = new AssemblySelector() { public static final AssemblySelector NO_16BIT_CALLS = new AssemblySelector() {
@Override @Override
public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx)
AssemblyPatternBlock ctx) throws AssemblySemanticException { throws AssemblySemanticException {
for (AssemblyResolvedPatterns res : filterCompatibleAndSort(rr, ctx)) { for (AssemblyResolvedPatterns res : filterCompatibleAndSort(rr, ctx)) {
byte[] ins = res.getInstruction().getVals(); byte[] ins = res.getInstruction().getVals();
// HACK to avoid 16-bit CALL.... TODO: Why does this happen? // 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)); "Filtered 16-bit call " + NumericUtilities.convertBytesToString(ins));
continue; continue;
} }
return AssemblyResolution.resolved(res.getInstruction().fillMask(), return new Selection(res.getInstruction().fillMask(), res.getContext());
res.getContext(), "Selected", null, null, null);
} }
throw new AssemblySemanticException(semanticErrors); throw new AssemblySemanticException(semanticErrors);
} }

View file

@ -63,8 +63,8 @@ public class AssemblyThrasherDevScript extends GhidraScript {
} }
@Override @Override
public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx)
AssemblyPatternBlock ctx) throws AssemblySemanticException { throws AssemblySemanticException {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
boolean gotOne = false; boolean gotOne = false;
boolean failedOne = false; boolean failedOne = false;

View file

@ -15,15 +15,7 @@
*/ */
package ghidra.app.plugin.assembler; package ghidra.app.plugin.assembler;
import java.util.Collection; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
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;
/** /**
* The primary interface for performing assembly in Ghidra. * 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 * Use the {@link Assemblers} class to obtain a suitable implementation for a given program or
* language. * language.
*/ */
public interface Assembler { public interface Assembler extends GenericAssembler<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);
} }

View file

@ -15,42 +15,18 @@
*/ */
package ghidra.app.plugin.assembler; package ghidra.app.plugin.assembler;
import ghidra.program.model.lang.Language; import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
/** /**
* An interface to build an assembler for a given language * An interface to build an assembler for a given language
*/ */
public interface AssemblerBuilder { public interface AssemblerBuilder
/** extends GenericAssemblerBuilder<AssemblyResolvedPatterns, Assembler> {
* Get the ID of the language for which this instance builds an assembler
*
* @return the language ID
*/
public LanguageID getLanguageID();
/** @Override
* 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 Assembler getAssembler(AssemblySelector selector); public Assembler getAssembler(AssemblySelector selector);
/** @Override
* 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 Assembler getAssembler(AssemblySelector selector, Program program); public Assembler getAssembler(AssemblySelector selector, Program program);
} }

View file

@ -118,6 +118,16 @@ public class AssemblySelector {
return sorted; 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. * Select an instruction from the possible results.
* *
@ -134,16 +144,15 @@ public class AssemblySelector {
* @param rr the collection of resolved constructors * @param rr the collection of resolved constructors
* @param ctx the applicable context. * @param ctx the applicable context.
* @return a single resolved constructor with a full instruction mask. * @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, public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx)
AssemblyPatternBlock ctx) throws AssemblySemanticException { throws AssemblySemanticException {
List<AssemblyResolvedPatterns> sorted = filterCompatibleAndSort(rr, ctx); List<AssemblyResolvedPatterns> sorted = filterCompatibleAndSort(rr, ctx);
// Pick just the first // Pick just the first
AssemblyResolvedPatterns res = sorted.get(0); AssemblyResolvedPatterns res = sorted.get(0);
// Just set the mask to ffs (effectively choosing 0 for the omitted bits) // Just set the mask to ffs (effectively choosing 0 for the omitted bits)
return AssemblyResolution.resolved(res.getInstruction().fillMask(), res.getContext(), return new Selection(res.getInstruction().fillMask(), res.getContext());
"Selected", null, null, null);
} }
} }

View file

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

View file

@ -0,0 +1,55 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.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);
}

View file

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

View file

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

View file

@ -15,28 +15,13 @@
*/ */
package ghidra.app.plugin.assembler.sleigh; 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.*;
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.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.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.framework.model.DomainObjectChangedEvent; import ghidra.program.model.address.Address;
import ghidra.framework.model.DomainObjectListener; import ghidra.program.model.listing.Program;
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;
/** /**
* An {@link Assembler} for a {@link SleighLanguage}. * 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 * For documentation on how the SLEIGH assembler works, see {@link SleighAssemblerBuilder}. To use
* the assembler, please use {@link Assemblers#getAssembler(Program)} or similar. * the assembler, please use {@link Assemblers#getAssembler(Program)} or similar.
*/ */
public class SleighAssembler implements Assembler { public class SleighAssembler extends AbstractSleighAssembler<AssemblyResolvedPatterns>
protected static final DbgTimer dbg = DbgTimer.INACTIVE; implements Assembler {
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;
/** /**
* Construct a SleighAssembler. * Construct a SleighAssembler.
@ -84,13 +42,11 @@ public class SleighAssembler implements Assembler {
* @param defaultContext the default context for the language * @param defaultContext the default context for the language
* @param ctxGraph the context graph * @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) { AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) {
this(selector, (SleighLanguage) program.getLanguage(), parser, defaultContext, ctxGraph); super(factory, selector, program, parser, defaultContext, ctxGraph);
this.program = program;
this.listing = program.getListing();
this.memory = program.getMemory();
} }
/** /**
@ -105,182 +61,16 @@ public class SleighAssembler implements Assembler {
* @param defaultContext the default context for the language * @param defaultContext the default context for the language
* @param ctxGraph the context graph * @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) { AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) {
this.selector = selector; super(factory, selector, lang, parser, defaultContext, ctxGraph);
this.lang = lang;
this.parser = parser;
this.defaultContext = defaultContext;
this.ctxGraph = ctxGraph;
} }
@Override @Override
public Instruction patchProgram(AssemblyResolvedPatterns res, Address at) protected AssemblyTreeResolver newResolver(Address at, AssemblyParseBranch tree,
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,
AssemblyPatternBlock ctx) { AssemblyPatternBlock ctx) {
if (parse.isError()) { return new AssemblyTreeResolver(factory, lang, at, tree, ctx, ctxGraph);
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);
} }
} }

View file

@ -15,29 +15,11 @@
*/ */
package ghidra.app.plugin.assembler.sleigh; 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.*;
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.parse.AssemblyParser;
import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.symbol.*; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
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.program.model.listing.Program;
import ghidra.util.SystemUtilities;
/** /**
* An {@link AssemblerBuilder} capable of supporting almost any {@link SleighLanguage} * 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 * implementation includes optional diagnostics, but even then, decoding them takes some familiarity
* and expertise. * and expertise.
*/ */
public class SleighAssemblerBuilder implements AssemblerBuilder { public class SleighAssemblerBuilder
protected static final DbgTimer dbg = extends AbstractSleighAssemblerBuilder<AssemblyResolvedPatterns, Assembler>
SystemUtilities.isInTestingBatchMode() ? DbgTimer.INACTIVE : DbgTimer.ACTIVE; implements AssemblerBuilder {
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<>();
/** /**
* Construct an assembler builder for the given SLEIGH language * Construct an assembler builder for the given SLEIGH language
@ -321,319 +292,32 @@ public class SleighAssemblerBuilder implements AssemblerBuilder {
* @param lang the language * @param lang the language
*/ */
public SleighAssemblerBuilder(SleighLanguage lang) { public SleighAssemblerBuilder(SleighLanguage lang) {
this.lang = lang; super(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 @Override
public LanguageID getLanguageID() { protected AbstractAssemblyResolutionFactory< //
return lang.getLanguageID(); AssemblyResolvedPatterns, AssemblyResolvedBackfill> newResolutionFactory() {
return new DefaultAssemblyResolutionFactory();
} }
@Override @Override
public SleighLanguage getLanguage() { protected SleighAssembler newAssembler(AssemblySelector selector) {
return lang; 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 @Override
public SleighAssembler getAssembler(AssemblySelector selector) { public SleighAssembler getAssembler(AssemblySelector selector) {
generateAssembler(); return (SleighAssembler) super.getAssembler(selector);
SleighAssembler asm = new SleighAssembler(selector, lang, parser, defaultContext, ctxGraph);
return asm;
} }
@Override @Override
public SleighAssembler getAssembler(AssemblySelector selector, Program program) { public SleighAssembler getAssembler(AssemblySelector selector, Program program) {
generateAssembler(); return (SleighAssembler) super.getAssembler(selector, program);
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;
} }
} }

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.BinaryExpression; import ghidra.app.plugin.processors.sleigh.expression.BinaryExpression;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
@ -36,9 +35,9 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
} }
@Override @Override
public AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) T exp, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
throws NeedsBackfillException { Set<SolverHint> hints, String description) throws NeedsBackfillException {
MaskedLong lval = solver.getValue(exp.getLeft(), vals, cur); MaskedLong lval = solver.getValue(exp.getLeft(), vals, cur);
MaskedLong rval = solver.getValue(exp.getRight(), vals, cur); MaskedLong rval = solver.getValue(exp.getRight(), vals, cur);
@ -58,26 +57,27 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
try { try {
if (lval != null && rval != null) { if (lval != null && rval != null) {
MaskedLong cval = compute(lval, rval); MaskedLong cval = compute(lval, rval);
return ConstantValueSolver.checkConstAgrees(cval, goal, description); return ConstantValueSolver.checkConstAgrees(factory, cval, goal, description);
} }
else if (lval != null) { else if (lval != null) {
return solveRightSide(exp.getRight(), lval, goal, vals, cur, hints, return solveRightSide(factory, exp.getRight(), lval, goal, vals, cur, hints,
description); description);
} }
else if (rval != null) { 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 { else {
// Each solver may provide a strategy for solving expression where both sides are // Each solver may provide a strategy for solving expression where both sides are
// variable, e.g., two fields being concatenated via OR. // 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) { catch (NeedsBackfillException e) {
throw e; throw e;
} }
catch (SolverException e) { catch (SolverException e) {
return AssemblyResolution.error(e.getMessage(), description); return factory.newErrorBuilder().error(e.getMessage()).description(description).build();
} }
catch (AssertionError e) { catch (AssertionError e) {
dbg.println("While solving: " + exp + " (" + description + ")"); dbg.println("While solving: " + exp + " (" + description + ")");
@ -85,23 +85,25 @@ public abstract class AbstractBinaryExpressionSolver<T extends BinaryExpression>
} }
} }
protected AssemblyResolution solveLeftSide(PatternExpression lexp, MaskedLong rval, protected AssemblyResolution solveLeftSide(AbstractAssemblyResolutionFactory<?, ?> factory,
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur, PatternExpression lexp, MaskedLong rval, MaskedLong goal, Map<String, Long> vals,
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,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException { 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_"); throw new NeedsBackfillException("_two_sided_");
} }

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
@ -54,9 +53,9 @@ public abstract class AbstractExpressionSolver<T extends PatternExpression> {
* @return the resolution * @return the resolution
* @throws NeedsBackfillException if the expression refers to an undefined symbol * @throws NeedsBackfillException if the expression refers to an undefined symbol
*/ */
public abstract AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals, public abstract AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, T exp, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
String description) throws NeedsBackfillException; Set<SolverHint> hints, String description) throws NeedsBackfillException;
/** /**
* Attempt to get a constant value for the expression * Attempt to get a constant value for the expression

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.UnaryExpression; import ghidra.app.plugin.processors.sleigh.expression.UnaryExpression;
/** /**
@ -35,18 +34,18 @@ public abstract class AbstractUnaryExpressionSolver<T extends UnaryExpression>
} }
@Override @Override
public AssemblyResolution solve(T exp, MaskedLong goal, Map<String, Long> vals, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory, T exp,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
throws NeedsBackfillException { Set<SolverHint> hints, String description) throws NeedsBackfillException {
MaskedLong uval = solver.getValue(exp.getUnary(), vals, cur); MaskedLong uval = solver.getValue(exp.getUnary(), vals, cur);
try { try {
if (uval != null && uval.isFullyDefined()) { if (uval != null && uval.isFullyDefined()) {
MaskedLong cval = compute(uval); MaskedLong cval = compute(uval);
if (cval != null) { 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); description);
} }
/* /*

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.ConstantValue; import ghidra.app.plugin.processors.sleigh.expression.ConstantValue;
/** /**
@ -36,11 +35,11 @@ public class ConstantValueSolver extends AbstractExpressionSolver<ConstantValue>
} }
@Override @Override
public AssemblyResolution solve(ConstantValue cv, MaskedLong goal, Map<String, Long> vals, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, ConstantValue cv, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
String description) { Set<SolverHint> hints, String description) {
MaskedLong value = getValue(cv, vals, cur); MaskedLong value = getValue(cv, vals, cur);
return checkConstAgrees(value, goal, description); return checkConstAgrees(factory, value, goal, description);
} }
@Override @Override
@ -60,12 +59,15 @@ public class ConstantValueSolver extends AbstractExpressionSolver<ConstantValue>
return MaskedLong.fromLong(cv.getValue()); return MaskedLong.fromLong(cv.getValue());
} }
static AssemblyResolution checkConstAgrees(MaskedLong value, MaskedLong goal, static AssemblyResolution checkConstAgrees(
AbstractAssemblyResolutionFactory<?, ?> factory, MaskedLong value, MaskedLong goal,
String description) { String description) {
if (!value.agrees(goal)) { if (!value.agrees(goal)) {
return AssemblyResolution.error( return factory.newErrorBuilder()
"Constant value " + value + " does not agree with child requirements", description); .error("Constant value " + value + " does not agree with child requirements")
.description(description)
.build();
} }
return AssemblyResolution.nop(description, null, null); return factory.nop(description);
} }
} }

View file

@ -35,15 +35,18 @@ public class ContextFieldSolver extends AbstractExpressionSolver<ContextField> {
} }
@Override @Override
public AssemblyResolution solve(ContextField cf, MaskedLong goal, Map<String, Long> vals, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) { 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. assert cf.minValue() == 0; // In case someone decides to do signedness there.
if (!goal.isInRange(cf.maxValue(), cf.hasSignbit())) { if (!goal.isInRange(cf.maxValue(), cf.hasSignbit())) {
return AssemblyResolution.error("Value " + goal + " is not valid for " + cf, return factory.newErrorBuilder()
description); .error("Value " + goal + " is not valid for " + cf)
.description(description)
.build();
} }
AssemblyPatternBlock block = AssemblyPatternBlock.fromContextField(cf, goal); AssemblyPatternBlock block = AssemblyPatternBlock.fromContextField(cf, goal);
return AssemblyResolution.contextOnly(block, description); return factory.contextOnly(block, description);
} }
@Override @Override

View file

@ -39,12 +39,14 @@ public class EndInstructionValueSolver extends AbstractExpressionSolver<EndInstr
} }
@Override @Override
public AssemblyResolution solve(EndInstructionValue iv, MaskedLong goal, Map<String, Long> vals, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) { EndInstructionValue exp, MaskedLong goal, Map<String, Long> vals,
throw new AssertionError( AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
"INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_NEXT); 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 * serves as a sanity check on the SLEIGH spec. I can't think of a good reason to try to
* solve INST_NEXT == const. * solve INST_NEXT == const.
*/ */
@ -53,9 +55,9 @@ public class EndInstructionValueSolver extends AbstractExpressionSolver<EndInstr
@Override @Override
public MaskedLong getValue(EndInstructionValue iv, Map<String, Long> vals, public MaskedLong getValue(EndInstructionValue iv, Map<String, Long> vals,
AssemblyResolvedPatterns cur) throws NeedsBackfillException { AssemblyResolvedPatterns cur) throws NeedsBackfillException {
Long instNext = vals.get(AssemblyTreeResolver.INST_NEXT); Long instNext = vals.get(AbstractAssemblyTreeResolver.INST_NEXT);
if (instNext == null) { if (instNext == null) {
throw new NeedsBackfillException(AssemblyTreeResolver.INST_NEXT); throw new NeedsBackfillException(AbstractAssemblyTreeResolver.INST_NEXT);
} }
return MaskedLong.fromLong(instNext); return MaskedLong.fromLong(instNext);
} }
@ -68,7 +70,7 @@ public class EndInstructionValueSolver extends AbstractExpressionSolver<EndInstr
@Override @Override
public MaskedLong valueForResolution(EndInstructionValue exp, Map<String, Long> vals, public MaskedLong valueForResolution(EndInstructionValue exp, Map<String, Long> vals,
AssemblyResolvedPatterns rc) { AssemblyResolvedPatterns rc) {
Long instNext = vals.get(AssemblyTreeResolver.INST_NEXT); Long instNext = vals.get(AbstractAssemblyTreeResolver.INST_NEXT);
if (instNext == null) { if (instNext == null) {
/** /**
* This method is used in forward state construction, so just leave unknown. This may * This method is used in forward state construction, so just leave unknown. This may

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.LeftShiftExpression; import ghidra.app.plugin.processors.sleigh.expression.LeftShiftExpression;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -60,13 +59,14 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<Le
} }
@Override @Override
protected AssemblyResolution solveTwoSided(LeftShiftExpression exp, MaskedLong goal, protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory<?, ?> factory,
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints, LeftShiftExpression exp, MaskedLong goal, Map<String, Long> vals,
String description) throws NeedsBackfillException, SolverException { AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
// Do not guess the same parameter recursively // Do not guess the same parameter recursively
if (hints.contains(DefaultSolverHint.GUESSING_LEFT_SHIFT_AMOUNT)) { if (hints.contains(DefaultSolverHint.GUESSING_LEFT_SHIFT_AMOUNT)) {
// NOTE: Nested left shifts ought to be written as a left shift by a sum // 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 // 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 // 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 // If the goal is 0s, then any shift will do, so long as the shifted value is 0
try { try {
// NB. goal is already 0s, so just use it as subgoal for lhs of shift // NB. goal is already 0s, so just use it as subgoal for lhs of shift
AssemblyResolution lres = AssemblyResolution lres = solver.solve(factory, exp.getLeft(), goal, vals, cur,
solver.solve(exp.getLeft(), goal, vals, cur, hintsWithLShift, description); hintsWithLShift, description);
if (lres.isError()) { if (lres.isError()) {
throw new SolverException("Solving left:=0 failed"); throw new SolverException("Solving left:=0 failed");
} }
@ -97,13 +97,13 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<Le
MaskedLong reqr = MaskedLong.fromLong(shift); MaskedLong reqr = MaskedLong.fromLong(shift);
MaskedLong reql = computeLeft(reqr, goal); MaskedLong reql = computeLeft(reqr, goal);
AssemblyResolution lres = AssemblyResolution lres = solver.solve(factory, exp.getLeft(), reql, vals, cur,
solver.solve(exp.getLeft(), reql, vals, cur, hintsWithLShift, description); hintsWithLShift, description);
if (lres.isError()) { if (lres.isError()) {
throw new SolverException("Solving left failed"); throw new SolverException("Solving left failed");
} }
AssemblyResolution rres = AssemblyResolution rres =
solver.solve(exp.getRight(), reqr, vals, cur, hints, description); solver.solve(factory, exp.getRight(), reqr, vals, cur, hints, description);
if (rres.isError()) { if (rres.isError()) {
throw new SolverException("Solving right failed"); throw new SolverException("Solving right failed");
} }
@ -121,6 +121,6 @@ public class LeftShiftExpressionSolver extends AbstractBinaryExpressionSolver<Le
// try the next // try the next
} }
} }
return super.solveTwoSided(exp, goal, vals, cur, hints, description); return super.solveTwoSided(factory, exp, goal, vals, cur, hints, description);
} }
} }

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.MultExpression; import ghidra.app.plugin.processors.sleigh.expression.MultExpression;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
@ -102,25 +101,26 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
super(MultExpression.class); super(MultExpression.class);
} }
protected AssemblyResolution tryRep(PatternExpression lexp, MaskedLong rval, MaskedLong repGoal, protected AssemblyResolution tryRep(AbstractAssemblyResolutionFactory<?, ?> factory,
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur, PatternExpression lexp, MaskedLong rval, MaskedLong repGoal, MaskedLong goal,
Set<SolverHint> hints, String description) throws NeedsBackfillException { Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws NeedsBackfillException {
MaskedLong lval = repGoal.divideUnsigned(rval); MaskedLong lval = repGoal.divideUnsigned(rval);
if (lval.multiply(rval).agrees(goal)) { 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; return null;
} }
@Override @Override
protected AssemblyResolution solveLeftSide(PatternExpression lexp, MaskedLong rval, protected AssemblyResolution solveLeftSide(AbstractAssemblyResolutionFactory<?, ?> factory,
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur, PatternExpression lexp, MaskedLong rval, MaskedLong goal, Map<String, Long> vals,
Set<SolverHint> hints, String description) AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException { throws NeedsBackfillException, SolverException {
// Try the usual case first // Try the usual case first
ResultTracker tracker = new ResultTracker(); ResultTracker tracker = new ResultTracker();
AssemblyResolution sol = tracker.trySolverFunc(() -> { 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) { if (sol != null) {
return sol; return sol;
@ -150,7 +150,8 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
if (reps > 0) { if (reps > 0) {
MaskedLong repRightGoal = MaskedLong.fromMaskAndValue(repMsk, repVal); MaskedLong repRightGoal = MaskedLong.fromMaskAndValue(repMsk, repVal);
sol = tracker.trySolverFunc(() -> { sol = tracker.trySolverFunc(() -> {
return tryRep(lexp, rval, repRightGoal, goal, vals, cur, hintsWithRepetition, return tryRep(factory, lexp, rval, repRightGoal, goal, vals, cur,
hintsWithRepetition,
description); description);
}); });
if (sol != null) { if (sol != null) {
@ -168,8 +169,8 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
repMsk = -1L >>> i; repMsk = -1L >>> i;
MaskedLong repLeftGoal = MaskedLong.fromMaskAndValue(repMsk, repVal); MaskedLong repLeftGoal = MaskedLong.fromMaskAndValue(repMsk, repVal);
sol = tracker.trySolverFunc(() -> { sol = tracker.trySolverFunc(() -> {
return tryRep(lexp, rval, repLeftGoal, goal, vals, cur, hintsWithRepetition, return tryRep(factory, lexp, rval, repLeftGoal, goal, vals, cur,
description); hintsWithRepetition, description);
}); });
if (sol != null) { if (sol != null) {
return sol; return sol;
@ -180,11 +181,11 @@ public class MultExpressionSolver extends AbstractBinaryExpressionSolver<MultExp
} }
@Override @Override
protected AssemblyResolution solveRightSide(PatternExpression rexp, MaskedLong lval, protected AssemblyResolution solveRightSide(AbstractAssemblyResolutionFactory<?, ?> factory,
MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur, PatternExpression rexp, MaskedLong lval, MaskedLong goal, Map<String, Long> vals,
Set<SolverHint> hints, String description) AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException { throws NeedsBackfillException, SolverException {
return solveLeftSide(rexp, lval, goal, vals, cur, hints, description); return solveLeftSide(factory, rexp, lval, goal, vals, cur, hints, description);
} }
@Override @Override

View file

@ -39,9 +39,10 @@ public class Next2InstructionValueSolver extends AbstractExpressionSolver<Next2I
} }
@Override @Override
public AssemblyResolution solve(Next2InstructionValue iv, MaskedLong goal, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
Map<String, Long> vals, Next2InstructionValue exp, MaskedLong goal, Map<String, Long> vals,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) { AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException {
throw new AssertionError( throw new AssertionError(
"INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_NEXT2); "INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_NEXT2);
} }

View file

@ -41,7 +41,7 @@ public class OperandValueSolver extends AbstractExpressionSolver<OperandValue> {
* Obtains the "defining expression" * Obtains the "defining expression"
* *
* <p> * <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. * its defining symbol.
* *
* @return the defining expression, or null if neither is available * @return the defining expression, or null if neither is available
@ -60,25 +60,31 @@ public class OperandValueSolver extends AbstractExpressionSolver<OperandValue> {
} }
@Override @Override
public AssemblyResolution solve(OperandValue ov, MaskedLong goal, Map<String, Long> vals, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) OperandValue ov, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
throws NeedsBackfillException { Set<SolverHint> hints, String description) throws NeedsBackfillException {
Constructor cons = ov.getConstructor(); Constructor cons = ov.getConstructor();
OperandSymbol sym = cons.getOperand(ov.getIndex()); OperandSymbol sym = cons.getOperand(ov.getIndex());
PatternExpression patexp = getDefiningExpression(sym); PatternExpression patexp = getDefiningExpression(sym);
if (patexp == null) { if (patexp == null) {
if (goal.equals(MaskedLong.ZERO)) { if (goal.equals(MaskedLong.ZERO)) {
return AssemblyResolution.nop(description, null, null); return factory.nop(description);
} }
return AssemblyResolution.error("Operand " + sym.getName() + return factory.newErrorBuilder()
" is undefined and does not agree with child requirements", description); .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()) { if (result.isError()) {
AssemblyResolvedError err = (AssemblyResolvedError) result; AssemblyResolvedError err = (AssemblyResolvedError) result;
return AssemblyResolution.error(err.getError(), return factory.newErrorBuilder()
"Solution to " + sym.getName() + " := " + goal + " = " + patexp, .error(err.getError())
List.of(result), null); .description("Solution to " + sym.getName() + " := " + goal + " = " + patexp)
.children(List.of(result))
.build();
} }
// TODO: Shifting here seems like a hack to me. // TODO: Shifting here seems like a hack to me.
// I assume this only comes at the top of an expression // I assume this only comes at the top of an expression

View file

@ -53,7 +53,8 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
return goal.invOr(rval); 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, Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws SolverException { String description) throws SolverException {
/* /*
@ -68,7 +69,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
fields.put(64L, new ConstantValue(0)); fields.put(64L, new ConstantValue(0));
long lo = 0; long lo = 0;
PatternExpression fieldExp = null; PatternExpression fieldExp = null;
AssemblyResolvedPatterns result = AssemblyResolution.nop(description); AssemblyResolvedPatterns result = factory.nop(description);
try (DbgCtx dc = dbg.start("Trying solution of field catenation")) { try (DbgCtx dc = dbg.start("Trying solution of field catenation")) {
dbg.println("Original: " + goal + ":= " + exp); dbg.println("Original: " + goal + ":= " + exp);
for (Map.Entry<Long, PatternExpression> ent : fields.entrySet()) { for (Map.Entry<Long, PatternExpression> ent : fields.entrySet()) {
@ -81,7 +82,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
dbg.println("Part(" + hi + ":" + lo + "]:= " + fieldExp); dbg.println("Part(" + hi + ":" + lo + "]:= " + fieldExp);
MaskedLong part = goal.shiftLeft(64 - hi).shiftRightPositional(64 - hi + lo); MaskedLong part = goal.shiftLeft(64 - hi).shiftRightPositional(64 - hi + lo);
dbg.println("Solving: " + part + ":= " + fieldExp); 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); description + " with shift " + lo);
if (sol.isError()) { if (sol.isError()) {
return sol; return sol;
@ -98,7 +99,8 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
return result; 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, Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints,
String description) throws SolverException { String description) throws SolverException {
// If OR is being used to accomplish a circular shift, then we can apply a clever solver. // 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 // At this point, I know it's a circular shift
dbg.println("Identified circular shift: value:= " + expValu1 + ", shift:= " + expShift + dbg.println("Identified circular shift: value:= " + expValu1 + ", shift:= " + expShift +
", size:= " + size + ", dir:= " + (dir == 1 ? "right" : "left")); ", size:= " + size + ", dir:= " + (dir == 1 ? "right" : "left"));
return solveLeftCircularShift(expValu1, expShift, size, dir, goal, vals, cur, hints, return solveLeftCircularShift(factory, expValu1, expShift, size, dir, goal, vals, cur,
description); 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, PatternExpression expShift, int size, int dir, MaskedLong goal, Map<String, Long> vals,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException { throws NeedsBackfillException, SolverException {
@ -194,12 +197,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
throw new AssertionError("Should not have constants when solving special forms"); throw new AssertionError("Should not have constants when solving special forms");
} }
else if (valValue != null) { else if (valValue != null) {
return solver.solve(expShift, computeCircShiftG(valValue, size, dir, goal), vals, cur, return solver.solve(factory, expShift, computeCircShiftG(valValue, size, dir, goal),
hints, description); vals, cur, hints, description);
} }
else if (valShift != null) { else if (valShift != null) {
return solver.solve(expValue, computeCircShiftF(valShift, size, dir, goal), vals, cur, return solver.solve(factory, expValue, computeCircShiftF(valShift, size, dir, goal),
hints, description); vals, cur, hints, description);
} }
// Oiy. Try guessing the shift amount, starting at 0 // Oiy. Try guessing the shift amount, starting at 0
@ -213,14 +216,14 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
try { try {
MaskedLong reqShift = MaskedLong.fromLong(shift); MaskedLong reqShift = MaskedLong.fromLong(shift);
MaskedLong reqValue = computeCircShiftF(reqShift, size, dir, goal); 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); hintsWithCircularShift, description);
if (resValue.isError()) { if (resValue.isError()) {
AssemblyResolvedError err = (AssemblyResolvedError) resValue; AssemblyResolvedError err = (AssemblyResolvedError) resValue;
throw new SolverException("Solving f failed: " + err.getError()); throw new SolverException("Solving f failed: " + err.getError());
} }
AssemblyResolution resShift = AssemblyResolution resShift =
solver.solve(expShift, reqShift, vals, cur, hints, description); solver.solve(factory, expShift, reqShift, vals, cur, hints, description);
if (resShift.isError()) { if (resShift.isError()) {
AssemblyResolvedError err = (AssemblyResolvedError) resShift; AssemblyResolvedError err = (AssemblyResolvedError) resShift;
throw new SolverException("Solving g failed: " + err.getError()); throw new SolverException("Solving g failed: " + err.getError());
@ -267,11 +270,12 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
} }
@Override @Override
protected AssemblyResolution solveTwoSided(OrExpression exp, MaskedLong goal, protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory<?, ?> factory,
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints, OrExpression exp, MaskedLong goal, Map<String, Long> vals, AssemblyResolvedPatterns cur,
String description) throws NeedsBackfillException, SolverException { Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
try { try {
return tryCatenationExpression(exp, goal, vals, cur, hints, description); return tryCatenationExpression(factory, exp, goal, vals, cur, hints, description);
} }
catch (Exception e) { catch (Exception e) {
dbg.println("while solving: " + goal + "=:" + exp); dbg.println("while solving: " + goal + "=:" + exp);
@ -279,7 +283,7 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
} }
try { try {
return tryCircularShiftExpression(exp, goal, vals, cur, hints, description); return tryCircularShiftExpression(factory, exp, goal, vals, cur, hints, description);
} }
catch (Exception e) { catch (Exception e) {
dbg.println("while solving: " + goal + "=:" + exp); dbg.println("while solving: " + goal + "=:" + exp);
@ -291,21 +295,22 @@ public class OrExpressionSolver extends AbstractBinaryExpressionSolver<OrExpress
long value = MATCHERS.val.get(match).getValue(); long value = MATCHERS.val.get(match).getValue();
PatternValue field = MATCHERS.fld.get(match); PatternValue field = MATCHERS.fld.get(match);
// Solve for equals, then either return that, or forbid it, depending on goal // Solve for equals, then either return that, or forbid it, depending on goal
AssemblyResolution solution = AssemblyResolution solution = solver.solve(factory, field, MaskedLong.fromLong(value),
solver.solve(field, MaskedLong.fromLong(value), vals, cur, hints, description); vals, cur, hints, description);
if (goal.equals(MaskedLong.fromMaskAndValue(0, 1))) { if (goal.equals(MaskedLong.fromMaskAndValue(0, 1))) {
return solution; return solution;
} }
if (goal.equals(MaskedLong.fromMaskAndValue(1, 1))) { if (goal.equals(MaskedLong.fromMaskAndValue(1, 1))) {
if (solution.isError()) { if (solution.isError()) {
return AssemblyResolution.nop(description); return factory.nop(description);
} }
if (solution.isBackfill()) { if (solution.isBackfill()) {
throw new AssertionError(); throw new AssertionError();
} }
AssemblyResolvedPatterns forbidden = (AssemblyResolvedPatterns) solution; AssemblyResolvedPatterns forbidden = (AssemblyResolvedPatterns) solution;
forbidden = forbidden.withDescription("Solved 'not equals'"); 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));
} }
} }

View file

@ -17,8 +17,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.*; import java.util.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer; import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression; import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
@ -115,11 +114,13 @@ public class RecursiveDescentSolver {
* @return the encoded solution * @return the encoded solution
* @throws NeedsBackfillException a solution may exist, but a required symbol is missing * @throws NeedsBackfillException a solution may exist, but a required symbol is missing
*/ */
protected AssemblyResolution solve(PatternExpression exp, MaskedLong goal, protected AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints, PatternExpression exp, MaskedLong goal, Map<String, Long> vals,
String description) throws NeedsBackfillException { AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException {
try { 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) { catch (UnsupportedOperationException e) {
DBG.println("Error solving " + exp + " = " + goal); DBG.println("Error solving " + exp + " = " + goal);
@ -152,10 +153,10 @@ public class RecursiveDescentSolver {
* @return the encoded solution * @return the encoded solution
* @throws NeedsBackfillException a solution may exist, but a required symbol is missing * @throws NeedsBackfillException a solution may exist, but a required symbol is missing
*/ */
public AssemblyResolution solve(PatternExpression exp, MaskedLong goal, Map<String, Long> vals, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolvedPatterns cur, String description) PatternExpression exp, MaskedLong goal, Map<String, Long> vals,
throws NeedsBackfillException { AssemblyResolvedPatterns cur, String description) throws NeedsBackfillException {
return solve(exp, goal, vals, cur, Set.of(), description); return solve(factory, exp, goal, vals, cur, Set.of(), description);
} }
/** /**

View file

@ -18,8 +18,7 @@ package ghidra.app.plugin.assembler.sleigh.expr;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolution; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedPatterns;
import ghidra.app.plugin.processors.sleigh.expression.RightShiftExpression; import ghidra.app.plugin.processors.sleigh.expression.RightShiftExpression;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -61,15 +60,16 @@ public class RightShiftExpressionSolver
} }
@Override @Override
protected AssemblyResolution solveTwoSided(RightShiftExpression exp, MaskedLong goal, protected AssemblyResolution solveTwoSided(AbstractAssemblyResolutionFactory<?, ?> factory,
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints, RightShiftExpression exp, MaskedLong goal, Map<String, Long> vals,
String description) throws NeedsBackfillException, SolverException { AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description)
throws NeedsBackfillException, SolverException {
// Do the similar thing as in {@link LeftShiftExpressionSolver} // Do the similar thing as in {@link LeftShiftExpressionSolver}
// Do not guess the same parameter recursively // Do not guess the same parameter recursively
if (hints.contains(DefaultSolverHint.GUESSING_RIGHT_SHIFT_AMOUNT)) { if (hints.contains(DefaultSolverHint.GUESSING_RIGHT_SHIFT_AMOUNT)) {
// NOTE: Nested right shifts ought to be written as a right shift by a sum // 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); int maxShift = Long.numberOfLeadingZeros(goal.val);
@ -80,13 +80,13 @@ public class RightShiftExpressionSolver
MaskedLong reqr = MaskedLong.fromLong(shift); MaskedLong reqr = MaskedLong.fromLong(shift);
MaskedLong reql = computeLeft(reqr, goal); MaskedLong reql = computeLeft(reqr, goal);
AssemblyResolution lres = AssemblyResolution lres = solver.solve(factory, exp.getLeft(), reql, vals, cur,
solver.solve(exp.getLeft(), reql, vals, cur, hintsWithRShift, description); hintsWithRShift, description);
if (lres.isError()) { if (lres.isError()) {
throw new SolverException("Solving left failed"); throw new SolverException("Solving left failed");
} }
AssemblyResolution rres = AssemblyResolution rres =
solver.solve(exp.getRight(), reqr, vals, cur, hints, description); solver.solve(factory, exp.getRight(), reqr, vals, cur, hints, description);
if (rres.isError()) { if (rres.isError()) {
throw new SolverException("Solving right failed"); throw new SolverException("Solving right failed");
} }
@ -104,6 +104,6 @@ public class RightShiftExpressionSolver
// try the next // try the next
} }
} }
return super.solveTwoSided(exp, goal, vals, cur, hints, description); return super.solveTwoSided(factory, exp, goal, vals, cur, hints, description);
} }
} }

View file

@ -35,9 +35,9 @@ public class StartInstructionValueSolver extends AbstractExpressionSolver<StartI
} }
@Override @Override
public AssemblyResolution solve(StartInstructionValue iv, MaskedLong goal, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
Map<String, Long> vals, AssemblyResolvedPatterns cur, Set<SolverHint> hints, StartInstructionValue iv, MaskedLong goal, Map<String, Long> vals,
String description) { AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) {
throw new AssertionError( throw new AssertionError(
"INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_START); "INTERNAL: Should never be asked to solve for " + AssemblyTreeResolver.INST_START);
} }

View file

@ -35,15 +35,18 @@ public class TokenFieldSolver extends AbstractExpressionSolver<TokenField> {
} }
@Override @Override
public AssemblyResolution solve(TokenField tf, MaskedLong goal, Map<String, Long> vals, public AssemblyResolution solve(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolvedPatterns cur, Set<SolverHint> hints, String description) { 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. assert tf.minValue() == 0; // In case someone decides to do signedness there.
if (!goal.isInRange(tf.maxValue(), tf.hasSignbit())) { if (!goal.isInRange(tf.maxValue(), tf.hasSignbit())) {
return AssemblyResolution.error("Value " + goal + " is not valid for " + tf, return factory.newErrorBuilder()
description); .error("Value " + goal + " is not valid for " + tf)
.description(description)
.build();
} }
AssemblyPatternBlock block = AssemblyPatternBlock.fromTokenField(tf, goal); AssemblyPatternBlock block = AssemblyPatternBlock.fromTokenField(tf, goal);
return AssemblyResolution.instrOnly(block, description); return factory.instrOnly(block, description);
} }
@Override @Override

View file

@ -17,6 +17,7 @@ package ghidra.app.plugin.assembler.sleigh.grammars;
import java.util.*; 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.sem.AssemblyConstructorSemantic;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal; import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal;
import ghidra.app.plugin.processors.sleigh.Constructor; import ghidra.app.plugin.processors.sleigh.Constructor;
@ -33,14 +34,19 @@ import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern;
*/ */
public class AssemblyGrammar public class AssemblyGrammar
extends AbstractAssemblyGrammar<AssemblyNonTerminal, AssemblyProduction> { extends AbstractAssemblyGrammar<AssemblyNonTerminal, AssemblyProduction> {
protected final AbstractAssemblyResolutionFactory<?, ?> factory;
// a nested map of semantics by production, by constructor // a nested map of semantics by production, by constructor
protected final Map<AssemblyProduction, Map<Constructor, AssemblyConstructorSemantic>> semanticsByProduction = protected final Map<AssemblyProduction, //
new TreeMap<>(); Map<Constructor, AssemblyConstructorSemantic>> semanticsByProduction = new TreeMap<>();
protected final Map<Constructor, AssemblyConstructorSemantic> semanticsByConstructor = protected final Map<Constructor, AssemblyConstructorSemantic> semanticsByConstructor =
new HashMap<>(); new HashMap<>();
// a map of purely recursive, e.g., I => I, productions by name of LHS // a map of purely recursive, e.g., I => I, productions by name of LHS
protected final Map<String, AssemblyProduction> pureRecursive = new TreeMap<>(); protected final Map<String, AssemblyProduction> pureRecursive = new TreeMap<>();
public AssemblyGrammar(AbstractAssemblyResolutionFactory<?, ?> factory) {
this.factory = factory;
}
@Override @Override
protected AssemblyProduction newProduction(AssemblyNonTerminal lhs, protected AssemblyProduction newProduction(AssemblyNonTerminal lhs,
AssemblySentential<AssemblyNonTerminal> rhs) { AssemblySentential<AssemblyNonTerminal> rhs) {
@ -73,7 +79,7 @@ public class AssemblyGrammar
Map<Constructor, AssemblyConstructorSemantic> map = Map<Constructor, AssemblyConstructorSemantic> map =
semanticsByProduction.computeIfAbsent(prod, p -> new TreeMap<>()); semanticsByProduction.computeIfAbsent(prod, p -> new TreeMap<>());
AssemblyConstructorSemantic sem = AssemblyConstructorSemantic sem =
map.computeIfAbsent(cons, c -> new AssemblyConstructorSemantic(cons, indices)); map.computeIfAbsent(cons, c -> new AssemblyConstructorSemantic(factory, cons, indices));
if (!indices.equals(sem.getOperandIndices())) { if (!indices.equals(sem.getOperandIndices())) {
throw new IllegalStateException( throw new IllegalStateException(
"Productions of the same constructor must have same operand indices"); "Productions of the same constructor must have same operand indices");

View file

@ -28,4 +28,8 @@ public class AssemblyProduction extends AbstractAssemblyProduction<AssemblyNonTe
AssemblySentential<AssemblyNonTerminal> rhs) { AssemblySentential<AssemblyNonTerminal> rhs) {
super(lhs, rhs); super(lhs, rhs);
} }
public boolean isConstructor() {
return true;
}
} }

View file

@ -127,7 +127,7 @@ public class AssemblySentential<NT extends AssemblyNonTerminal>
*/ */
private static class WhiteSpace extends AssemblyStringTerminal { private static class WhiteSpace extends AssemblyStringTerminal {
private WhiteSpace() { private WhiteSpace() {
super(" "); super(" ", null);
} }
@Override @Override
@ -166,6 +166,11 @@ public class AssemblySentential<NT extends AssemblyNonTerminal>
public Collection<String> getSuggestions(String got, AssemblyNumericSymbols symbols) { public Collection<String> getSuggestions(String got, AssemblyNumericSymbols symbols) {
return Collections.singleton(" "); 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. * Add a comma followed by optional whitespace.
*/ */
public void addCommaWS() { public void addCommaWS() {
addSymbol(new AssemblyStringTerminal(",")); addSymbol(new AssemblyStringTerminal(",", null));
addWS(); addWS();
} }
@ -240,7 +245,7 @@ public class AssemblySentential<NT extends AssemblyNonTerminal>
if (!Character.isLetterOrDigit(first)) { if (!Character.isLetterOrDigit(first)) {
addWS(); addWS();
} }
addSymbol(new AssemblyStringTerminal(tstr)); addSymbol(new AssemblyStringTerminal(tstr, null));
char last = tstr.charAt(tstr.length() - 1); char last = tstr.charAt(tstr.length() - 1);
if (!str.endsWith(tstr)) { if (!str.endsWith(tstr)) {
addWS(); addWS();

View file

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

View file

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

View file

@ -25,14 +25,16 @@ import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
* Base for a node in an assembly prototype * Base for a node in an assembly prototype
*/ */
public abstract class AbstractAssemblyState { 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 List<AssemblyConstructorSemantic> path;
protected final int shift; protected final int shift;
protected final int length; protected final int length;
protected final int hash; protected volatile boolean hasHash = false;
protected volatile int hash;
/** /**
* Construct a node * Construct a node
@ -42,18 +44,21 @@ public abstract class AbstractAssemblyState {
* @param shift the (right) shift in bytes for this operand * @param shift the (right) shift in bytes for this operand
* @param length the length of this operand * @param length the length of this operand
*/ */
protected AbstractAssemblyState(AssemblyTreeResolver resolver, protected AbstractAssemblyState(AbstractAssemblyTreeResolver<?> resolver,
List<AssemblyConstructorSemantic> path, int shift, int length) { List<AssemblyConstructorSemantic> path, int shift, int length) {
this.resolver = resolver; this.resolver = resolver;
this.factory = resolver.factory;
this.path = path; this.path = path;
this.shift = shift; this.shift = shift;
this.length = length; this.length = length;
this.hash = computeHash();
} }
@Override @Override
public int hashCode() { public int hashCode() {
if (!hasHash) {
this.hash = computeHash();
this.hasHash = true;
}
return hash; return hash;
} }
@ -77,6 +82,18 @@ public abstract class AbstractAssemblyState {
protected abstract Stream<AssemblyResolvedPatterns> resolve(AssemblyResolvedPatterns fromRight, protected abstract Stream<AssemblyResolvedPatterns> resolve(AssemblyResolvedPatterns fromRight,
Collection<AssemblyResolvedError> errors); 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 * Get the length in bytes of the operand represented by this node
* *

View file

@ -29,7 +29,7 @@ import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
* @param <N> the type of parse tree node to process * @param <N> the type of parse tree node to process
*/ */
public abstract class AbstractAssemblyStateGenerator<N extends AssemblyParseTreeNode> { 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 * 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(",")) + "]"; path.stream().map(sem -> sem.getLocation()).collect(Collectors.joining(",")) + "]";
} }
final List<AssemblyConstructorSemantic> path; public final List<AssemblyConstructorSemantic> path;
final int shift; public final int shift;
/** /**
* Construct a context * 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 N node;
protected final AssemblyResolvedPatterns fromLeft; 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 node the node from which to generate states
* @param fromLeft the accumulated patterns from the left sibling or the parent * @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) { AssemblyResolvedPatterns fromLeft) {
this.resolver = resolver; this.resolver = resolver;
this.node = node; this.node = node;

View file

@ -0,0 +1,527 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import ghidra.app.plugin.assembler.sleigh.SleighAssemblerBuilder;
import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyProduction;
import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyStateGenerator.GeneratorContext;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolutionResults.Applicator;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal;
import ghidra.app.plugin.assembler.sleigh.tree.*;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.plugin.processors.sleigh.symbol.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.lang.InsufficientBytesException;
import ghidra.program.model.lang.UnknownInstructionException;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.program.model.mem.MemBuffer;
/**
* The workhorse of semantic resolution for the assembler
*
* <p>
* This class takes a parse tree and some additional information (start address, context, etc.) and
* attempts to determine possible encodings using the semantics associated with each branch of the
* given parse tree. Details of this process are described in {@link SleighAssemblerBuilder}.
*
* @see SleighAssemblerBuilder
*/
public abstract class AbstractAssemblyTreeResolver<RP extends AssemblyResolvedPatterns> {
protected static final RecursiveDescentSolver SOLVER = RecursiveDescentSolver.getSolver();
protected static final DbgTimer DBG = DbgTimer.INACTIVE;
public static final String INST_START = "inst_start";
public static final String INST_NEXT = "inst_next";
public static final String INST_NEXT2 = "inst_next2";
protected final AbstractAssemblyResolutionFactory<RP, ?> factory;
protected final SleighLanguage lang;
protected final Address at;
protected final Map<String, Long> vals = new HashMap<>();
protected final AssemblyParseBranch tree;
protected final AssemblyGrammar grammar;
protected final AssemblyPatternBlock context;
protected final AssemblyContextGraph ctxGraph;
/**
* Construct a resolver for the given parse tree
*
* @param lang
* @param at the address where the instruction will start
* @param tree the parse tree
* @param context the context expected at {@code instStart}
* @param ctxGraph the context transition graph used to resolve purely-recursive productions
*/
public AbstractAssemblyTreeResolver(AbstractAssemblyResolutionFactory<RP, ?> factory,
SleighLanguage lang, Address at, AssemblyParseBranch tree, AssemblyPatternBlock context,
AssemblyContextGraph ctxGraph) {
this.factory = factory;
this.lang = lang;
this.at = at;
this.vals.put(INST_START, at.getAddressableWordOffset());
this.tree = tree;
this.grammar = tree.getGrammar();
this.context = context.fillMask();
this.ctxGraph = ctxGraph;
}
public AbstractAssemblyResolutionFactory<RP, ?> getFactory() {
return factory;
}
/**
* Resolve the tree for the given parameters
*
* @return a set of resolutions (encodings and errors)
*/
public AssemblyResolutionResults resolve() {
RP empty = factory.nop("Empty");
AssemblyConstructStateGenerator rootGen =
new AssemblyConstructStateGenerator(this, tree, empty);
Collection<AssemblyResolvedError> errors = new ArrayList<>();
Stream<AssemblyGeneratedPrototype> protStream =
rootGen.generate(new GeneratorContext(List.of(), 0));
if (DBG == DbgTimer.ACTIVE) {
try (DbgCtx dc = DBG.start("Prototypes:")) {
protStream = protStream.map(prot -> {
DBG.println(prot);
return prot;
}).collect(Collectors.toList()).stream();
}
}
Stream<AssemblyResolvedPatterns> patStream =
protStream.map(p -> p.state).distinct().flatMap(s -> s.resolve(empty, errors));
AssemblyResolutionResults results =
patStream.collect(Collectors.toCollection(factory::newAssemblyResolutionResults));
results = resolveRootRecursion(results);
results = selectContext(results);
results = resolvePendingBackfills(results);
// TODO: Remove this? It's subsumed by filterByDisassembly, and more accurately....
results = filterForbidden(results);
results = filterByDisassembly(results);
for (AssemblyResolvedError err : errors) {
results.add(err);
}
return results;
}
/**
* If applicable, get the {@code I => I} production of the grammar
*
* @return the production
*/
protected AssemblyProduction getRootRecursion() {
assert tree.getParent() == null;
AssemblyProduction rootProd = tree.getProduction();
AssemblyNonTerminal start = rootProd.getLHS();
AssemblyProduction rec = grammar.getPureRecursion(start);
return rec;
}
/**
* If necessary, resolve recursive constructors at the root, usually for prefixes
*
* <p>
* If there are no pure recursive constructors at the root, then this simply returns
* {@code temp} unmodified.
*
* @param temp the resolved root results
* @return the results with pure recursive constructors applied to obtain a compatible context
*/
// Ugh, public so I can refer to it in javadocs...
public AssemblyResolutionResults resolveRootRecursion(AssemblyResolutionResults temp) {
AssemblyProduction rootRec = getRootRecursion();
if (rootRec == null) {
return temp;
}
try (DbgCtx dc = DBG.start("Resolving root recursion:")) {
AssemblyResolutionResults result = factory.newAssemblyResolutionResults();
for (AssemblyResolution ar : temp) {
if (ar.isError()) {
result.add(ar);
continue;
}
@SuppressWarnings("unchecked")
RP rp = (RP) ar;
AssemblyPatternBlock dst = rp.getContext();
// TODO: The desired context may need to be passed in. For now, just take start.
AssemblyPatternBlock src = context; // NOTE: This is only correct for "instruction"
String table = "instruction";
DBG.println("Finding paths from " + src + " to " + ar.lineToString());
Collection<Deque<AssemblyConstructorSemantic>> paths =
ctxGraph.computeOptimalApplications(src, table, dst, table);
DBG.println("Found " + paths.size());
for (Deque<AssemblyConstructorSemantic> path : paths) {
DBG.println(" " + path);
result.absorb(applyRecursionPath(path, tree, rootRec, ar));
}
}
return result;
}
}
/**
* Attempt a second time to solve operands and context changes
*
* <p>
* Backfills that depended on {@code inst_next} should now easily be solved, since the
* instruction length is now known.
*
* @param temp the resolved results, with backfill pending
* @return the results without backfill, possible with new errors
*/
protected AssemblyResolutionResults resolvePendingBackfills(AssemblyResolutionResults temp) {
return temp.apply(factory, rp -> {
if (!rp.hasBackfills()) {
return rp;
}
vals.put(INST_NEXT, at.add(rp.getInstructionLength()).getAddressableWordOffset());
// inst_next2 use not really supported
vals.put(INST_NEXT2, at.add(rp.getInstructionLength()).getAddressableWordOffset());
DBG.println("Backfilling: " + rp);
AssemblyResolution ar = rp.backfill(SOLVER, vals);
DBG.println("Backfilled final: " + ar);
return ar;
}).apply(factory, rp -> {
if (rp.hasBackfills()) {
return factory.newErrorBuilder()
.error("Solution is incomplete")
.description("failed backfill")
.children(List.of(rp))
.build();
}
return rp;
});
}
/**
* Filter out results whose context do not match that requested
*
* @param temp the results whose contexts have not yet been checked
* @return the results that pass. Those that do not are replaced with errors.
*/
protected AssemblyResolutionResults selectContext(AssemblyResolutionResults temp) {
RP ctx = factory.contextOnly(context, "Selecting context");
return temp.apply(factory, rp -> {
AssemblyResolution check = rp.combine(ctx);
if (null == check) {
return factory.newErrorBuilder()
.error("Incompatible context")
.description("resolving")
.children(List.of(rp))
.build();
}
return check;
});
}
/**
* Filter out results that would certainly be disassembled differently than assembled
*
* <p>
* Because of constructor precedence rules, it is possible to assemble a pattern from a
* prototype that would not result in equivalent disassembly. This can be detected in some cases
* via the "forbids" mechanism, where more specific constructors are recorded with the result.
* If the generated pattern matches on of those more-specific constructors, it is forbidden.
*
* @param temp the results whose forbids have not yet been checked
* @return the results that pass. Those that do not are replaced with errors.
*/
protected AssemblyResolutionResults filterForbidden(AssemblyResolutionResults temp) {
return temp.apply(factory, rp -> rp.checkNotForbidden());
}
/**
* Filter out results that get disassembled differently than assembled
*
* <p>
* The forbids mechanism is not perfect, so as a final fail safe, we disassemble the result and
* compare the prototypes.
*
* @param temp the results whose disassemblies have not yet been checked
* @return the results that pass. Those that do not are replaced with errors.
*/
protected AssemblyResolutionResults filterByDisassembly(AssemblyResolutionResults temp) {
AssemblyDefaultContext asmCtx = new AssemblyDefaultContext(lang);
asmCtx.setContextRegister(context);
return temp.apply(factory, rp -> {
MemBuffer buf =
new ByteMemBufferImpl(at, rp.getInstruction().getVals(), lang.isBigEndian());
try {
SleighInstructionPrototype ip =
(SleighInstructionPrototype) lang.parse(buf, asmCtx, false);
if (!rp.equivalentConstructState(ip.getRootState())) {
return factory.error("Disassembly prototype mismatch", rp);
}
return rp;
}
catch (InsufficientBytesException | UnknownInstructionException e) {
return factory.error("Disassembly failed: " + e.getMessage(), rp);
}
});
}
/**
* Get the state generator for a given operand and parse tree node
*
* @param opSym the operand symbol
* @param node the corresponding parse tree node, possibly null indicating a hidden operand
* @param fromLeft the accumulated patterns from the left sibling or parent
* @return the generator
*/
protected AbstractAssemblyStateGenerator<?> getStateGenerator(OperandSymbol opSym,
AssemblyParseTreeNode node, AssemblyResolvedPatterns fromLeft) {
if (node instanceof AssemblyParseHiddenNode) {
return getHiddenStateGenerator(opSym, fromLeft);
}
if (node instanceof AssemblyParseNumericToken token) {
return new AssemblyOperandStateGenerator(this, token, opSym, fromLeft);
}
if (node instanceof AssemblyParseBranch branch) {
return new AssemblyConstructStateGenerator(this, branch, fromLeft);
}
if (node instanceof AssemblyParseToken token && node.getSym().takesOperandIndex()) {
return new AssemblyStringStateGenerator(this, token, opSym, fromLeft);
}
throw new AssertionError();
}
/**
* Get the state generator for a hidden operand
*
* @param opSym the operand symbol
* @param fromLeft the accumulated patterns from the left sibling or parent
* @return the generator
*/
protected AbstractAssemblyStateGenerator<?> getHiddenStateGenerator(OperandSymbol opSym,
AssemblyResolvedPatterns fromLeft) {
TripleSymbol defSym = opSym.getDefiningSymbol();
if (defSym instanceof SubtableSymbol subtable) {
return new AssemblyHiddenConstructStateGenerator(this, subtable, fromLeft);
}
return new AssemblyNopStateGenerator(this, opSym, fromLeft);
}
/**
* Apply a constructor pattern
*
* <p>
* TODO: This is currently used only for resolving recursion. Could this be factored with
* {@link AssemblyConstructState#resolve(AssemblyResolvedPatterns, Collection)}?
*
* @param sem the SLEIGH constructor
* @param shift the shift
* @param fromChildren the results from the single resolved child
* @return the results
*/
protected AssemblyResolutionResults resolvePatterns(AssemblyConstructorSemantic sem, int shift,
AssemblyResolutionResults fromChildren) {
AssemblyResolutionResults results = fromChildren;
results = applyMutations(sem, results);
results = applyPatterns(sem, shift, results);
results = tryResolveBackfills(results);
return results;
}
protected AssemblyResolutionResults parent(String description, AssemblyResolutionResults temp,
int opCount) {
return temp.stream()
.map(r -> r.parent(description, opCount))
.collect(Collectors.toCollection(factory::newAssemblyResolutionResults));
}
/**
* TODO: This is currently used only for resolving recursion. Could this be factored with
* {@link AssemblyConstructState#resolveMutations(AssemblyResolvedPatterns, Collection)}?
*/
protected AssemblyResolutionResults applyMutations(AssemblyConstructorSemantic sem,
AssemblyResolutionResults temp) {
DBG.println("Applying context mutations:");
return temp.apply(factory, rp -> {
DBG.println("Current: " + rp.lineToString());
AssemblyResolution backctx = sem.solveContextChanges(rp, vals);
DBG.println("Mutated: " + backctx.lineToString());
return backctx;
}).apply(factory, rp -> {
return rp.solveContextChangesForForbids(sem, vals);
});
}
/**
* TODO: This is currently used only for resolving recursion. Could this be factored with
* {@link AssemblyConstructState#resolvePatterns(AssemblyResolvedPatterns, Collection)}?
*/
protected AssemblyResolutionResults applyPatterns(AssemblyConstructorSemantic sem, int shift,
AssemblyResolutionResults temp) {
DBG.println("Applying patterns:");
Collection<AssemblyResolution> patterns =
sem.getPatterns()
.stream()
.map(p -> p.shift(shift))
.collect(Collectors.toList());
return temp.apply(factory, new Applicator() {
@Override
public Iterable<AssemblyResolution> getPatterns(
AssemblyResolvedPatterns cur) {
return patterns;
}
@Override
public AssemblyResolvedPatterns setRight(AssemblyResolvedPatterns res,
AssemblyResolvedPatterns cur) {
// This is typically applied by parent, so don't insert sibling
return res;
}
@Override
public String describeError(AssemblyResolvedPatterns rp, AssemblyResolution pat) {
return "The patterns conflict " + pat.lineToString();
}
@Override
public AssemblyResolvedPatterns combineBackfill(AssemblyResolvedPatterns cur,
AssemblyResolvedBackfill bf) {
throw new AssertionError();
}
@Override
public AssemblyResolution finish(AssemblyResolvedPatterns resolved) {
return resolved.checkNotForbidden();
}
});
}
/**
* Apply constructors as indicated by a path returned by the context resolution graph
*
* <p>
* <b>NOTE:</b> The given path will be emptied during processing.
*
* @param path the path to apply
* @param branch the branch corresponding to the production whose LHS has a purely-recursive
* definition.
* @param rec the purely-recursive production
* @param child the intermediate result to apply the constructors to
* @return the results
*/
protected AssemblyResolutionResults applyRecursionPath(Deque<AssemblyConstructorSemantic> path,
AssemblyParseBranch branch, AssemblyProduction rec, AssemblyResolution child) {
/*
* A constructor may have multiple patterns, so I cannot assume I will get at most one
* output at each constructor in the path. Start (1) collecting all the results, then (2)
* filter out and report the errors, then (3) feed successful resolutions into the next
* constructor in the path (or finish).
*/
AssemblyResolutionResults results = factory.newAssemblyResolutionResults();
results.add(child);
while (!path.isEmpty()) {
AssemblyConstructorSemantic sem = path.pollLast();
int opIdx = sem.getOperandIndex(0);
Constructor cons = sem.getConstructor();
OperandSymbol opSym = cons.getOperand(opIdx);
if (-1 != opSym.getOffsetBase()) {
throw new AssertionError("TODO");
}
int offset = opSym.getRelativeOffset();
results = parent("Resolving recursive constructor: " + cons.getSourceFile() + ":" +
cons.getLineno(), results, 1);
results = results.apply(factory, rp -> rp.shift(offset));
results =
resolvePatterns(sem, 0, results).apply(factory, rp -> rp.withConstructor(cons));
}
return results;
}
/**
* TODO: This is currently used only for resolving recursion. It seems it's missing from the
* refactor?
*/
protected AssemblyResolutionResults tryResolveBackfills(AssemblyResolutionResults results) {
AssemblyResolutionResults res = factory.newAssemblyResolutionResults();
next_ar: for (AssemblyResolution ar : results) {
if (ar.isError()) {
res.add(ar);
continue;
}
while (true) {
AssemblyResolvedPatterns rp = (AssemblyResolvedPatterns) ar;
if (!rp.hasBackfills()) {
// finish: The complete solution is known
res.add(ar);
continue next_ar;
}
ar = rp.backfill(SOLVER, vals);
if (ar.isError() || ar.isBackfill()) {
// fail: It is now known that the solution doesn't exist
res.add(ar);
continue next_ar;
}
if (ar.equals(rp)) {
// fail: The solution is /still/ not known, and we made no progress
res.add(ar);
continue next_ar;
}
// Some progress was made, continue trying until we finish or fail
}
}
return res;
}
/**
* Compute the offset of an operand encoded in the instruction block
*
* <p>
* TODO: Currently, there are duplicate mechanisms for resolving a constructor: 1) The newer
* mechanism implemented in {@link AssemblyConstructState}, and 2) the older one implemented in
* {@link #applyPatterns(AssemblyConstructorSemantic, int, AssemblyResolutionResults)}. The
* latter seems to require this method, since it does not have pre-computed shifts as in the
* former. We should probably remove the latter in favor of the former....
*
* @param opsym the operand symbol
* @param cons the constructor containing the operand
* @return the offset (right shift) to apply to the encoded operand
*/
public static int computeOffset(OperandSymbol opsym, Constructor cons) {
int offset = opsym.getRelativeOffset();
int baseidx = opsym.getOffsetBase();
if (baseidx != -1) {
OperandSymbol baseop = cons.getOperand(baseidx);
offset += baseop.getMinimumLength();
offset += computeOffset(baseop, cons);
}
return offset;
}
public AssemblyGrammar getGrammar() {
return grammar;
}
}

View file

@ -39,7 +39,7 @@ public class AssemblyConstructState extends AbstractAssemblyState {
* @param operands the operands * @param operands the operands
* @return the farthest end byte * @return the farthest end byte
*/ */
protected static int computeEnd(List<AbstractAssemblyState> operands) { protected static int computeEnd(List<? extends AbstractAssemblyState> operands) {
return operands.stream() return operands.stream()
.map(s -> s.shift + s.length) .map(s -> s.shift + s.length)
.reduce(0, Integer::max); .reduce(0, Integer::max);
@ -61,7 +61,7 @@ public class AssemblyConstructState extends AbstractAssemblyState {
* @param sem the selected SLEIGH constructor * @param sem the selected SLEIGH constructor
* @param children the child state for each operand in the 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, List<AssemblyConstructorSemantic> path, int shift,
AssemblyConstructorSemantic sem, List<AbstractAssemblyState> children) { AssemblyConstructorSemantic sem, List<AbstractAssemblyState> children) {
super(resolver, path, shift, super(resolver, path, shift,
@ -150,8 +150,10 @@ public class AssemblyConstructState extends AbstractAssemblyState {
}) })
.filter(ar -> { .filter(ar -> {
if (ar == null) { if (ar == null) {
errors.add(AssemblyResolution.error("Pattern conflict", errors.add(factory.newErrorBuilder()
"Resolving " + sem.getLocation() + " in " + path)); .error("Pattern conflict")
.description("Resolving " + sem.getLocation() + " in " + path)
.build());
return false; return false;
} }
return true; return true;

View file

@ -20,8 +20,7 @@ import java.util.stream.Stream;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyProduction; import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyProduction;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblySymbol; import ghidra.app.plugin.assembler.sleigh.symbol.AssemblySymbol;
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch; import ghidra.app.plugin.assembler.sleigh.tree.*;
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseTreeNode;
import ghidra.app.plugin.assembler.sleigh.util.AsmUtil; import ghidra.app.plugin.assembler.sleigh.util.AsmUtil;
import ghidra.app.plugin.processors.sleigh.Constructor; import ghidra.app.plugin.processors.sleigh.Constructor;
import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol; 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 node the node from which to generate states
* @param fromLeft the accumulated patterns from the left sibling or the parent * @param fromLeft the accumulated patterns from the left sibling or the parent
*/ */
public AssemblyConstructStateGenerator(AssemblyTreeResolver resolver, AssemblyParseBranch node, public AssemblyConstructStateGenerator(AbstractAssemblyTreeResolver<?> resolver,
AssemblyResolvedPatterns fromLeft) { AssemblyParseBranch node, AssemblyResolvedPatterns fromLeft) {
super(resolver, node, fromLeft); super(resolver, node, fromLeft);
} }
@ -68,8 +67,9 @@ public class AssemblyConstructStateGenerator
*/ */
protected List<AssemblyParseTreeNode> orderOpNodes(AssemblyConstructorSemantic sem) { protected List<AssemblyParseTreeNode> orderOpNodes(AssemblyConstructorSemantic sem) {
Constructor cons = sem.getConstructor(); Constructor cons = sem.getConstructor();
List<AssemblyParseTreeNode> result = AssemblyParseTreeNode[] arr = new AssemblyParseTreeNode[cons.getNumOperands()];
Arrays.asList(new AssemblyParseTreeNode[cons.getNumOperands()]); List<AssemblyParseTreeNode> result = Arrays.asList(arr);
Arrays.fill(arr, new AssemblyParseHiddenNode(resolver.grammar));
int index = 0; int index = 0;
AssemblyProduction production = node.getProduction(); AssemblyProduction production = node.getProduction();
List<AssemblyParseTreeNode> substitutions = node.getSubstitutions(); 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 * child operand boundary is numbered. The offset base must always refer to an operand to the
* left. * 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 * @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. * 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 * @param fromLeft the accumulated patterns from the left sibling. The root invocation should
* pass the patterns accumulated after context changes. * pass the patterns accumulated after context changes.
* @param sem the selected SLEIGH constructor, whose operands to generate * @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 * @param children the list of children generated so far. The root invocation should pass the
* empty list. * empty list.
* @return the stream of generated (sub) prototypes * @return the stream of generated (sub) prototypes

View file

@ -36,9 +36,10 @@ import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol;
*/ */
public class AssemblyConstructorSemantic implements Comparable<AssemblyConstructorSemantic> { public class AssemblyConstructorSemantic implements Comparable<AssemblyConstructorSemantic> {
protected static final RecursiveDescentSolver SOLVER = RecursiveDescentSolver.getSolver(); 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 Set<AssemblyResolvedPatterns> patterns = new HashSet<>();
protected final AbstractAssemblyResolutionFactory<?, ?> factory;
protected final Constructor cons; protected final Constructor cons;
protected final List<Integer> indices; protected final List<Integer> indices;
protected final List<ContextChange> contextChanges; 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 * @param indices the indices of RHS non-terminals in the associated production that represent
* an operand in the SLEIGH constructor * 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.cons = cons;
this.indices = Collections.unmodifiableList(indices); this.indices = Collections.unmodifiableList(indices);
List<ContextChange> changes = new ArrayList<>(cons.getContextChanges()); List<ContextChange> changes = new ArrayList<>(cons.getContextChanges());
@ -69,7 +72,7 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
* @param pat the pattern * @param pat the pattern
*/ */
public void addPattern(DisjointPattern pat) { public void addPattern(DisjointPattern pat) {
addPattern(AssemblyResolution.fromPattern(pat, cons.getMinimumLength(), addPattern(factory.fromPattern(pat, cons.getMinimumLength(),
"Generated constructor pattern " + getLocation(), cons)); "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. // 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); sibcons.getMinimumLength(), "For specialization check", sibcons);
AssemblyResolvedPatterns comb = pat.combine(sibpat); AssemblyResolvedPatterns comb = pat.combine(sibpat);
if (null == comb) { 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 // 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; return CONTINUE;
} }
@ -295,7 +298,7 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
* *
* @param res the combined resolution requirements derived from the subconstructors * @param res the combined resolution requirements derived from the subconstructors
* @param vals any defined symbols (usually {@code inst_start}, and {@code inst_next}) * @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, public AssemblyResolution solveContextChanges(AssemblyResolvedPatterns res,
Map<String, Long> vals) { Map<String, Long> vals) {
@ -320,12 +323,12 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
DBG.println("Masked out: " + res.lineToString()); DBG.println("Masked out: " + res.lineToString());
// Now, solve // Now, solve
AssemblyResolution sol = AssemblyTreeResolver.solveOrBackfill( AssemblyResolution sol = factory.solveOrBackfill(cop.getPatternExpression(), reqval,
cop.getPatternExpression(), reqval, vals, res, "Solution to " + cop); vals, res, "Solution to " + cop);
DBG.println("Solution: " + sol.lineToString()); DBG.println("Solution: " + sol.lineToString());
if (sol.isError()) { if (sol.isError()) {
AssemblyResolvedError err = (AssemblyResolvedError) sol; AssemblyResolvedError err = (AssemblyResolvedError) sol;
return AssemblyResolution.error(err.getError(), res); return factory.error(err.getError(), res);
} }
// Now, forward the new requirements to my parents. // Now, forward the new requirements to my parents.
@ -333,8 +336,7 @@ public class AssemblyConstructorSemantic implements Comparable<AssemblyConstruct
AssemblyResolvedPatterns solcon = (AssemblyResolvedPatterns) sol; AssemblyResolvedPatterns solcon = (AssemblyResolvedPatterns) sol;
AssemblyResolvedPatterns check = res.combine(solcon); AssemblyResolvedPatterns check = res.combine(solcon);
if (null == check) { if (null == check) {
return AssemblyResolution.error( return factory.error("A context change caused a conflict: " + sol, res);
"A context change caused a conflict: " + sol, res);
} }
res = check; res = check;
} }

View file

@ -46,6 +46,7 @@ import ghidra.graph.algo.DijkstraShortestPathsAlgorithm;
* much larger its LALR(1) parser would become. * much larger its LALR(1) parser would become.
*/ */
public class AssemblyContextGraph implements GImplicitDirectedGraph<Vertex, Edge> { public class AssemblyContextGraph implements GImplicitDirectedGraph<Vertex, Edge> {
protected final AbstractAssemblyResolutionFactory<?, ?> factory;
protected final Map<String, Set<AssemblyConstructorSemantic>> semantics = protected final Map<String, Set<AssemblyConstructorSemantic>> semantics =
LazyMap.lazyMap(new HashMap<>(), () -> new HashSet<>()); LazyMap.lazyMap(new HashMap<>(), () -> new HashSet<>());
protected final AssemblyGrammar grammar; protected final AssemblyGrammar grammar;
@ -72,7 +73,9 @@ public class AssemblyContextGraph implements GImplicitDirectedGraph<Vertex, Edge
* @param lang the language * @param lang the language
* @param grammar the grammar derived from the given 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.grammar = grammar;
this.lang = lang; this.lang = lang;
@ -343,7 +346,7 @@ public class AssemblyContextGraph implements GImplicitDirectedGraph<Vertex, Edge
Set<Edge> result = new HashSet<>(); Set<Edge> result = new HashSet<>();
for (AssemblyConstructorSemantic sem : semantics.get(from.subtable)) { for (AssemblyConstructorSemantic sem : semantics.get(from.subtable)) {
for (AssemblyResolvedPatterns rc : sem.patterns) { for (AssemblyResolvedPatterns rc : sem.patterns) {
AssemblyPatternBlock pattern = rc.ctx; AssemblyPatternBlock pattern = rc.getContext();
AssemblyPatternBlock outer = from.context.combine(pattern); AssemblyPatternBlock outer = from.context.combine(pattern);
if (outer == null) { if (outer == null) {
continue; continue;
@ -352,8 +355,7 @@ public class AssemblyContextGraph implements GImplicitDirectedGraph<Vertex, Edge
continue; continue;
} }
AssemblyResolvedPatterns orc = AssemblyResolvedPatterns orc = factory.contextOnly(outer, "For context transition");
AssemblyResolution.contextOnly(outer, "For context transition");
AssemblyResolvedPatterns irc = sem.applyContextChangesForward(Map.of(), orc); AssemblyResolvedPatterns irc = sem.applyContextChangesForward(Map.of(), orc);
AssemblyPatternBlock inner = irc.getContext(); AssemblyPatternBlock inner = irc.getContext();

View file

@ -15,11 +15,10 @@
*/ */
package ghidra.app.plugin.assembler.sleigh.sem; package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.IntStream; import java.util.stream.*;
import java.util.stream.Stream;
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseHiddenNode;
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseTreeNode; import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseTreeNode;
import ghidra.app.plugin.processors.sleigh.Constructor; import ghidra.app.plugin.processors.sleigh.Constructor;
import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol; import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol;
@ -42,7 +41,7 @@ public class AssemblyHiddenConstructStateGenerator extends AssemblyConstructStat
* @param subtableSym * @param subtableSym
* @param fromLeft the accumulated patterns from the left sibling or the parent * @param fromLeft the accumulated patterns from the left sibling or the parent
*/ */
public AssemblyHiddenConstructStateGenerator(AssemblyTreeResolver resolver, public AssemblyHiddenConstructStateGenerator(AbstractAssemblyTreeResolver<?> resolver,
SubtableSymbol subtableSym, AssemblyResolvedPatterns fromLeft) { SubtableSymbol subtableSym, AssemblyResolvedPatterns fromLeft) {
super(resolver, null, fromLeft); super(resolver, null, fromLeft);
this.subtableSym = subtableSym; this.subtableSym = subtableSym;
@ -56,10 +55,17 @@ public class AssemblyHiddenConstructStateGenerator extends AssemblyConstructStat
.flatMap(sem -> applyConstructor(gc, sem)); .flatMap(sem -> applyConstructor(gc, sem));
} }
protected AssemblyParseTreeNode getFiller() {
return new AssemblyParseHiddenNode(resolver.grammar);
}
@Override @Override
protected List<AssemblyParseTreeNode> orderOpNodes(AssemblyConstructorSemantic sem) { protected List<AssemblyParseTreeNode> orderOpNodes(AssemblyConstructorSemantic sem) {
// Just provide null operands, since they're hidden, too. // Just provide hidden operands
Constructor cons = sem.getConstructor(); 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());
} }
} }

View file

@ -22,25 +22,21 @@ import java.util.stream.Stream;
import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol; import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
public class AssemblyNopState extends AbstractAssemblyState { 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()); super(resolver, path, shift, opSym.getMinimumLength());
} }
@Override @Override
public int computeHash() { public int computeHash() {
return "NOP".hashCode(); int result = getClass().hashCode();
result *= 31;
result += Integer.hashCode(shift);
return result;
} }
@Override protected boolean partsEqual(AssemblyNopState that) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AssemblyNopState)) {
return false;
}
AssemblyNopState that = (AssemblyNopState) obj;
if (this.resolver != that.resolver) { if (this.resolver != that.resolver) {
return false; return false;
} }
@ -50,6 +46,18 @@ public class AssemblyNopState extends AbstractAssemblyState {
return true; 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 @Override
public String toString() { public String toString() {
return "NOP"; return "NOP";

View file

@ -39,8 +39,8 @@ public class AssemblyNopStateGenerator
* @param opSym the operand symbol * @param opSym the operand symbol
* @param fromLeft the accumulated patterns from the left sibling or parent * @param fromLeft the accumulated patterns from the left sibling or parent
*/ */
public AssemblyNopStateGenerator(AssemblyTreeResolver resolver, OperandSymbol opSym, public AssemblyNopStateGenerator(AbstractAssemblyTreeResolver<?> resolver,
AssemblyResolvedPatterns fromLeft) { OperandSymbol opSym, AssemblyResolvedPatterns fromLeft) {
super(resolver, null, fromLeft); super(resolver, null, fromLeft);
this.opSym = opSym; this.opSym = opSym;
} }
@ -48,8 +48,7 @@ public class AssemblyNopStateGenerator
@Override @Override
public Stream<AssemblyGeneratedPrototype> generate(GeneratorContext gc) { public Stream<AssemblyGeneratedPrototype> generate(GeneratorContext gc) {
gc.dbg("Generating NOP for " + opSym); gc.dbg("Generating NOP for " + opSym);
return Stream.of( return Stream.of(new AssemblyGeneratedPrototype(
new AssemblyGeneratedPrototype(new AssemblyNopState(resolver, gc.path, gc.shift, opSym), new AssemblyNopState(resolver, gc.path, gc.shift, opSym), fromLeft));
fromLeft));
} }
} }

View file

@ -47,7 +47,7 @@ public class AssemblyOperandState extends AbstractAssemblyState {
* @param value the value of the operand * @param value the value of the operand
* @param opSym the operand symbol * @param opSym the operand symbol
*/ */
public AssemblyOperandState(AssemblyTreeResolver resolver, public AssemblyOperandState(AbstractAssemblyTreeResolver<?> resolver,
List<AssemblyConstructorSemantic> path, int shift, AssemblyTerminal terminal, List<AssemblyConstructorSemantic> path, int shift, AssemblyTerminal terminal,
long value, OperandSymbol opSym) { long value, OperandSymbol opSym) {
super(resolver, path, shift, opSym.getMinimumLength()); super(resolver, path, shift, opSym.getMinimumLength());
@ -58,18 +58,17 @@ public class AssemblyOperandState extends AbstractAssemblyState {
@Override @Override
public int computeHash() { 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 protected boolean partsEqual(AssemblyOperandState that) {
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AssemblyOperandState)) {
return false;
}
AssemblyOperandState that = (AssemblyOperandState) obj;
if (this.resolver != that.resolver) { if (this.resolver != that.resolver) {
return false; return false;
} }
@ -85,6 +84,18 @@ public class AssemblyOperandState extends AbstractAssemblyState {
return true; 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 @Override
public String toString() { public String toString() {
return terminal + "=" + value + "(0x" + Long.toHexString(value) + ")"; return terminal + "=" + value + "(0x" + Long.toHexString(value) + ")";
@ -120,7 +131,7 @@ public class AssemblyOperandState extends AbstractAssemblyState {
DBG.println("Equation: " + symExp + " = " + Long.toHexString(value)); DBG.println("Equation: " + symExp + " = " + Long.toHexString(value));
String desc = "Solution to " + opSym + " in " + Long.toHexString(value) + " = " + symExp; String desc = "Solution to " + opSym + " in " + Long.toHexString(value) + " = " + symExp;
AssemblyResolution sol = AssemblyResolution sol =
AssemblyTreeResolver.solveOrBackfill(symExp, value, bitsize, resolver.vals, null, desc); factory.solveOrBackfill(symExp, value, bitsize, resolver.vals, null, desc);
DBG.println("Solution: " + sol); DBG.println("Solution: " + sol);
AssemblyResolution shifted = sol.shift(shift); AssemblyResolution shifted = sol.shift(shift);
DBG.println("Shifted: " + shifted); DBG.println("Shifted: " + shifted);
@ -143,8 +154,10 @@ public class AssemblyOperandState extends AbstractAssemblyState {
} }
AssemblyResolution combined = fromRight.combine((AssemblyResolvedPatterns) sol); AssemblyResolution combined = fromRight.combine((AssemblyResolvedPatterns) sol);
if (combined == null) { if (combined == null) {
errors.add( errors.add(factory.newErrorBuilder()
AssemblyResolution.error("Pattern/operand conflict", "Resolving " + terminal)); .error("Pattern/operand conflict")
.description("Resolving " + terminal)
.build());
return Stream.of(); return Stream.of();
} }
AssemblyResolvedPatterns pats = (AssemblyResolvedPatterns) combined; AssemblyResolvedPatterns pats = (AssemblyResolvedPatterns) combined;
@ -152,4 +165,16 @@ public class AssemblyOperandState extends AbstractAssemblyState {
return Stream.of(pats.withRight(fromRight).withConstructor(null)); return Stream.of(pats.withRight(fromRight).withConstructor(null));
} }
} }
public AssemblyTerminal getTerminal() {
return terminal;
}
public long getValue() {
return value;
}
public OperandSymbol getOperandSymbol() {
return opSym;
}
} }

View file

@ -35,11 +35,11 @@ public class AssemblyOperandStateGenerator
* Construct the operand state generator * Construct the operand state generator
* *
* @param resolver the resolver * @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 fromLeft the accumulated patterns from the left sibling or parent
* @param opSym the operand symbol * @param opSym the operand symbol
*/ */
public AssemblyOperandStateGenerator(AssemblyTreeResolver resolver, public AssemblyOperandStateGenerator(AbstractAssemblyTreeResolver<?> resolver,
AssemblyParseNumericToken node, OperandSymbol opSym, AssemblyParseNumericToken node, OperandSymbol opSym,
AssemblyResolvedPatterns fromLeft) { AssemblyResolvedPatterns fromLeft) {
super(resolver, node, fromLeft); super(resolver, node, fromLeft);

View file

@ -17,8 +17,7 @@ package ghidra.app.plugin.assembler.sleigh.sem;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.*;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong; import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong;
@ -144,7 +143,7 @@ public class AssemblyPatternBlock implements Comparable<AssemblyPatternBlock> {
AtomicLong msk = new AtomicLong(); AtomicLong msk = new AtomicLong();
AtomicLong val = new AtomicLong(); AtomicLong val = new AtomicLong();
int i = 0; int i = 0;
for (String hex : str.split(":")) { for (String hex : str.substring(pos).split(":")) {
NumericUtilities.convertHexStringToMaskedValue(msk, val, hex, 2, 0, null); NumericUtilities.convertHexStringToMaskedValue(msk, val, hex, 2, 0, null);
mask[i] = (byte) msk.get(); mask[i] = (byte) msk.get();
vals[i] = (byte) val.get(); vals[i] = (byte) val.get();
@ -265,6 +264,7 @@ public class AssemblyPatternBlock implements Comparable<AssemblyPatternBlock> {
/** /**
* Convert a register value into a pattern block * Convert a register value into a pattern block
* *
* <p>
* This is used primarily to compute default context register values, and pass them into an * This is used primarily to compute default context register values, and pass them into an
* assembler. * assembler.
* *
@ -604,9 +604,132 @@ public class AssemblyPatternBlock implements Comparable<AssemblyPatternBlock> {
return new AssemblyPatternBlock(offset, newMask, newVals); 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 * 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 * @return the array
*/ */
public byte[] getVals() { public byte[] getVals() {
@ -616,12 +739,39 @@ public class AssemblyPatternBlock implements Comparable<AssemblyPatternBlock> {
/** /**
* Get the mask array * 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 * @return the array
*/ */
public byte[] getMask() { public byte[] getMask() {
return mask; 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 * Get the number of undefined bytes preceding the mask and values arrays
* *

View file

@ -15,285 +15,23 @@
*/ */
package ghidra.app.plugin.assembler.sleigh.sem; 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; public interface AssemblyResolution extends Comparable<AssemblyResolution> {
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();
/** /**
* Construct a resolution * {@inheritDoc}
*
* @param description a textual description used as part of {@link #toString()}
* @param children for record keeping, any children used in constructing this resolution
*/
AssemblyResolution(String description, List<? extends AssemblyResolution> children,
AssemblyResolution right) {
this.description = description;
this.children = children == null ? List.of() : Collections.unmodifiableList(children);
this.right = right;
}
/* ********************************************************************************************
* Static factory methods
*/
/**
* Build the result of successfully resolving a SLEIGH constructor
* *
* <p> * <p>
* <b>NOTE:</b> This is not used strictly for resolved SLEIGH constructors. It may also be used * Describe this record including indented children, grandchildren, etc., each on its own line.
* 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
*/ */
@Override @Override
public String toString() { String toString();
return toString("");
}
@Override String getDescription();
public int compareTo(AssemblyResolution that) {
return this.toString().compareTo(that.toString()); // LAZY List<AssemblyResolution> getChildren();
}
/** /**
* Check if this record has children * Check if this record has children
@ -306,15 +44,30 @@ public abstract class AssemblyResolution implements Comparable<AssemblyResolutio
* @see #childrenToString(String) * @see #childrenToString(String)
* @return true if this record has children * @return true if this record has children
*/ */
public boolean hasChildren() { boolean hasChildren();
if (children == null) {
return false; AssemblyResolution getRight();
}
if (children.size() == 0) { /**
return false; * Display the resolution result in one line (omitting child details)
} *
return true; * @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 * 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. * @param amt the number of bytes to shift.
* @return the result * @return the result
*/ */
public abstract AssemblyResolution shift(int amt); 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);
/** /**
* Get this same resolution, pushing its right siblings down to its children * 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);
} }

View file

@ -27,21 +27,21 @@ import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
* A set of possible assembly resolutions for a single SLEIGH constructor * A set of possible assembly resolutions for a single SLEIGH constructor
* *
* <p> * <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 * 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 * 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 * possible encoding (less some prefixes) of an instruction. This object stores the possible
* encodings, including error records describing the pruned intermediate results. * encodings, including error records describing the pruned intermediate results.
*/ */
public class AssemblyResolutionResults extends AbstractSetDecorator<AssemblyResolution> { public class AssemblyResolutionResults extends AbstractSetDecorator<AssemblyResolution> {
protected static final DbgTimer DBG = AssemblyTreeResolver.DBG; protected static final DbgTimer DBG = AbstractAssemblyTreeResolver.DBG;
public interface Applicator { public interface Applicator {
Iterable<? extends AssemblyResolution> getPatterns(AssemblyResolvedPatterns cur); Iterable<? extends AssemblyResolution> getPatterns(AssemblyResolvedPatterns cur);
default AssemblyResolvedPatterns setDescription( default AssemblyResolvedPatterns setDescription(
AssemblyResolvedPatterns res, AssemblyResolution from) { AssemblyResolvedPatterns res, AssemblyResolution from) {
AssemblyResolvedPatterns temp = res.withDescription(from.description); AssemblyResolvedPatterns temp = res.withDescription(from.getDescription());
return temp; return temp;
} }
@ -92,20 +92,10 @@ public class AssemblyResolutionResults extends AbstractSetDecorator<AssemblyReso
resolutions = new LinkedHashSet<>(); resolutions = new LinkedHashSet<>();
} }
private AssemblyResolutionResults(Set<AssemblyResolution> resolutions) { protected AssemblyResolutionResults(Set<AssemblyResolution> resolutions) {
this.resolutions = 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 @Override
public boolean add(AssemblyResolution ar) { public boolean add(AssemblyResolution ar) {
return resolutions.add(ar); return resolutions.add(ar);
@ -143,21 +133,22 @@ public class AssemblyResolutionResults extends AbstractSetDecorator<AssemblyReso
return this.resolutions.remove(ar); return this.resolutions.remove(ar);
} }
protected AssemblyResolutionResults apply(Applicator applicator) { protected AssemblyResolutionResults apply(AbstractAssemblyResolutionFactory<?, ?> factory,
AssemblyResolutionResults results = new AssemblyResolutionResults(); Applicator applicator) {
AssemblyResolutionResults results = factory.newAssemblyResolutionResults();
for (AssemblyResolution res : this) { for (AssemblyResolution res : this) {
if (res.isError()) { if (res.isError()) {
results.add(res); results.add(res);
continue; continue;
} }
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) res; AssemblyResolvedPatterns rp = (AssemblyResolvedPatterns) res;
DBG.println("Current: " + rc.lineToString()); DBG.println("Current: " + rp.lineToString());
for (AssemblyResolution pat : applicator.getPatterns(rc)) { for (AssemblyResolution ar : applicator.getPatterns(rp)) {
DBG.println("Pattern: " + pat.lineToString()); DBG.println("Pattern: " + ar.lineToString());
AssemblyResolvedPatterns combined = applicator.combine(rc, pat); AssemblyResolvedPatterns combined = applicator.combine(rp, ar);
DBG.println("Combined: " + (combined == null ? "(null)" : combined.lineToString())); DBG.println("Combined: " + (combined == null ? "(null)" : combined.lineToString()));
if (combined == null) { if (combined == null) {
results.add(AssemblyResolution.error(applicator.describeError(rc, pat), rc)); results.add(factory.error(applicator.describeError(rp, ar), ar));
continue; continue;
} }
results.add(applicator.finish(combined)); results.add(applicator.finish(combined));
@ -166,14 +157,19 @@ public class AssemblyResolutionResults extends AbstractSetDecorator<AssemblyReso
return results; return results;
} }
protected AssemblyResolutionResults apply( protected AssemblyResolutionResults apply(AbstractAssemblyResolutionFactory<?, ?> factory,
Function<AssemblyResolvedPatterns, AssemblyResolution> function) { Function<AssemblyResolvedPatterns, AssemblyResolution> function) {
return stream().map(res -> { return stream().map(res -> {
assert !(res instanceof AssemblyResolvedBackfill); if (res instanceof AssemblyResolvedBackfill) {
if (res.isError()) { throw new AssertionError();
return res;
} }
return function.apply((AssemblyResolvedPatterns) res); if (res instanceof AssemblyResolvedError err) {
}).collect(Collectors.toCollection(AssemblyResolutionResults::new)); return err;
}
if (res instanceof AssemblyResolvedPatterns rp) {
return function.apply(rp);
}
throw new AssertionError();
}).collect(Collectors.toCollection(factory::newAssemblyResolutionResults));
} }
} }

View file

@ -17,66 +17,9 @@ package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.Map; import java.util.Map;
import ghidra.app.plugin.assembler.sleigh.expr.*; import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
/** public interface AssemblyResolvedBackfill extends AssemblyResolution {
* 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();
}
/** /**
* Get the expected length of the instruction portion of the future encoding * 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) * @return the total expected length (including the offset)
*/ */
public int getInstructionLength() { int getInstructionLength();
return offset + inslen;
}
@Override @Override
public boolean isError() { AssemblyResolvedBackfill shift(int amt);
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();
}
/** /**
* Attempt (again) to solve the expression that generated this backfill record * 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 * 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 * 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 * 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). * @param vals the defined symbols, usually the same, but with the missing symbol(s).
* @return the solution result * @return the solution result
*/ */
public AssemblyResolution solve(RecursiveDescentSolver solver, Map<String, Long> vals, AssemblyResolution solve(RecursiveDescentSolver solver, Map<String, Long> vals,
AssemblyResolvedPatterns cur) { 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);
}
}
} }

View file

@ -15,83 +15,8 @@
*/ */
package ghidra.app.plugin.assembler.sleigh.sem; package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.List; public interface AssemblyResolvedError extends AssemblyResolution {
/** String getError();
* 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;
@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);
}
} }

View file

@ -16,334 +16,96 @@
package ghidra.app.plugin.assembler.sleigh.sem; package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang3.StringUtils;
import ghidra.app.plugin.assembler.AssemblySelector;
import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong; import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong;
import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver; import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver;
import ghidra.app.plugin.processors.sleigh.*; import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol;
/** public interface AssemblyResolvedPatterns extends AssemblyResolution {
* A {@link AssemblyResolution} indicating successful application of a constructor
*
* <p>
* This is almost analogous to {@link ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern
* DisjointPattern}, in that is joins an instruction {@link AssemblyPatternBlock} with a
* corresponding context {@link AssemblyPatternBlock}. However, this object is mutable, and it
* collects backfill records, as well as forbidden patterns.
*
* <p>
* When the applied constructor is from the "instruction" subtable, this represents a fully-
* constructed instruction with required context. All backfill records ought to be resolved and
* applied before the final result is given to the user, i.e., passed into the
* {@link AssemblySelector}. If at any time during the resolution or backfill process, the result
* becomes confined to one of the forbidden patterns, it must be dropped, since the encoding will
* actually invoke a more specific SLEIGH constructor.
*/
public class AssemblyResolvedPatterns extends AssemblyResolution {
protected static final String INS = "ins:";
protected static final String CTX = "ctx:";
protected static final String SEP = ",";
protected final Constructor cons;
protected final AssemblyPatternBlock ins;
protected final AssemblyPatternBlock ctx;
protected final Set<AssemblyResolvedBackfill> backfills;
protected final Set<AssemblyResolvedPatterns> forbids;
@Override
protected int computeHash() {
int result = 0;
result += ins.hashCode();
result *= 31;
result += ctx.hashCode();
result *= 31;
result += backfills.hashCode();
result *= 31;
result += forbids.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof AssemblyResolvedPatterns)) {
return false;
}
AssemblyResolvedPatterns that = (AssemblyResolvedPatterns) obj;
if (!this.ins.equals(that.ins)) {
return false;
}
if (!this.ctx.equals(that.ctx)) {
return false;
}
if (!this.backfills.equals(that.backfills)) {
return false;
}
if (!this.forbids.equals(that.forbids)) {
return false;
}
return true;
}
/** /**
* @see AssemblyResolution#resolved(AssemblyPatternBlock, AssemblyPatternBlock, String, Constructor, List, AssemblyResolution) * Get the instruction block
*
* @return the instruction block
*/ */
AssemblyResolvedPatterns(String description, Constructor cons, AssemblyPatternBlock getInstruction();
List<? extends AssemblyResolution> children, AssemblyResolution right,
AssemblyPatternBlock ins, AssemblyPatternBlock ctx,
Set<AssemblyResolvedBackfill> backfills, Set<AssemblyResolvedPatterns> forbids) {
super(description, children, right);
this.cons = cons;
this.ins = ins;
this.ctx = ctx;
this.backfills = backfills == null ? Set.of() : backfills;
this.forbids = forbids == null ? Set.of() : forbids;
}
/** /**
* Build a new successful SLEIGH constructor resolution from a string representation * Get the context block
*
* @return the context block
*/
AssemblyPatternBlock getContext();
/**
* Get the length of the instruction encoding
* *
* <p> * <p>
* This was used primarily in testing, to specify expected results. * This is used to ensure each operand is encoded at the correct offset
*
* @param str the string representation: "{@code ins:[pattern],ctx:[pattern]}"
* @see ghidra.util.NumericUtilities#convertHexStringToMaskedValue(AtomicLong, AtomicLong,
* String, int, int, String) NumericUtilities.convertHexStringToMaskedValue(AtomicLong,
* AtomicLong, String, int, int, String)
* @param description a description of the resolution
* @param children any children involved in the resolution
* @return the decoded resolution
*/
public static AssemblyResolvedPatterns fromString(String str, String description,
List<AssemblyResolution> children) {
AssemblyPatternBlock ins = null;
if (str.startsWith(INS)) {
int end = str.indexOf(SEP);
if (end == -1) {
end = str.length();
}
ins = AssemblyPatternBlock.fromString(str.substring(INS.length(), end));
str = str.substring(end);
if (str.startsWith(SEP)) {
str = str.substring(1);
}
}
AssemblyPatternBlock ctx = null;
if (str.startsWith(CTX)) {
int end = str.length();
ctx = AssemblyPatternBlock.fromString(str.substring(CTX.length(), end));
str = str.substring(end);
}
if (str.length() != 0) {
throw new IllegalArgumentException(str);
}
return AssemblyResolution.resolved(//
ins == null ? AssemblyPatternBlock.nop() : ins,//
ctx == null ? AssemblyPatternBlock.nop() : ctx,//
description, null, children, null);
}
@Override
public AssemblyResolvedPatterns shift(int amt) {
if (amt == 0) {
return this;
}
AssemblyPatternBlock newIns = this.ins.shift(amt);
// Also shift the attached backfills and forbidden patterns
Set<AssemblyResolvedBackfill> newBackfills = new HashSet<>();
for (AssemblyResolvedBackfill bf : this.backfills) {
newBackfills.add(bf.shift(amt));
}
Set<AssemblyResolvedPatterns> newForbids = new HashSet<>();
for (AssemblyResolvedPatterns f : this.forbids) {
newForbids.add(f.shift(amt));
}
return new AssemblyResolvedPatterns(description, cons, children, right, newIns, ctx,
Collections.unmodifiableSet(newBackfills), Collections.unmodifiableSet(newForbids));
}
/**
* Truncate (unshift) the resolved instruction pattern from the left
*
* <b>NOTE:</b> This drops all backfill and forbidden pattern records, since this method is
* typically used to read token fields rather than passed around for resolution.
*
* @param amt the number of bytes to remove from the left
* @return the result
*/
public AssemblyResolvedPatterns truncate(int amt) {
if (amt == 0) {
return this;
}
AssemblyPatternBlock newIns = this.ins.truncate(amt);
return new AssemblyResolvedPatterns("Truncated: " + description, cons, null, right,
newIns, ctx,
null, null);
}
/**
* Check if the current encoding is forbidden by one of the attached patterns
* *
* <p> * <p>
* The pattern becomes forbidden if this encoding's known bits are an overset of any forbidden * <b>NOTE:</b> this DOES include the offset<br>
* pattern's known bits. * <b>NOTE:</b> this DOES include pending backfills
* *
* @return false if the pattern is forbidden (and thus in error), true if permitted * @return the length of the instruction block
*/ */
public AssemblyResolution checkNotForbidden() { int getInstructionLength();
Set<AssemblyResolvedPatterns> newForbids = new HashSet<>();
for (AssemblyResolvedPatterns f : this.forbids) {
AssemblyResolvedPatterns check = this.combine(f);
if (null == check) {
continue;
}
newForbids.add(f);
if (check.bitsEqual(this)) {
// The result would be disassembled by a more-specific constructor.
return AssemblyResolution.error("The result is forbidden by " + f, this);
}
}
return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx,
backfills, Collections.unmodifiableSet(newForbids));
}
/** /**
* Check if this and another resolution have equal encodings * Get the length of the instruction encoding, excluding trailing undefined bytes
* *
* <p> * <p>
* This is like {@link #equals(Object)}, but it ignores backfill records and forbidden patterns. * <b>NOTE:</b> this DOES include the offset<br>
* <b>NOTE:</b> this DOES NOT include pending backfills
* *
* @param that the other resolution * @return the length of the defined bytes in the instruction block
* @return true if both have equal encodings
*/ */
protected boolean bitsEqual(AssemblyResolvedPatterns that) { int getDefinedInstructionLength();
return this.ins.equals(that.ins) && this.ctx.equals(that.ctx);
}
/** /**
* Combine the encodings and backfills of the given resolution into this one * Get the backfill records for this resolution, if any
*
* @return the backfills
*/
Collection<AssemblyResolvedBackfill> getBackfills();
/**
* Check if this resolution has pending backfills to apply
*
* @return true if there are backfills
*/
boolean hasBackfills();
/**
* Get the forbidden patterns for this resolution
* *
* <p> * <p>
* This combines corresponding pattern blocks (assuming they agree), collects backfill records, * These represent patterns included in the current resolution that would actually get matched
* and collects forbidden patterns. * by a more specific constructor somewhere in the resolved tree, and thus are subtracted.
* *
* @param that the other resolution * @return the forbidden patterns
* @return the result if successful, or null
*/ */
public AssemblyResolvedPatterns combine(AssemblyResolvedPatterns that) { Collection<AssemblyResolvedPatterns> getForbids();
// Not really a backfill, but I would like to re-use code
return combineLessBackfill(that, null);
}
/** /**
* Combine a backfill result * Decode a portion of the instruction block
* *
* <p> * @param start the first byte to decode
* When a backfill is successful, the result should be combined with the owning resolution. In * @param len the number of bytes to decode
* addition, for bookkeeping's sake, the resolved record should be removed from the list of * @return the read masked value
* backfills. * @see AssemblyPatternBlock#readBytes(int, int)
*
* @param that the result from backfilling
* @param bf the resolved backfilled record
* @return the result if successful, or null
*/ */
protected AssemblyResolvedPatterns combineLessBackfill(AssemblyResolvedPatterns that, MaskedLong readInstruction(int byteStart, int size);
AssemblyResolvedBackfill bf) {
AssemblyPatternBlock newIns = this.ins.combine(that.ins);
if (newIns == null) {
return null;
}
AssemblyPatternBlock newCtx = this.ctx.combine(that.ctx);
if (newCtx == null) {
return null;
}
Set<AssemblyResolvedBackfill> newBackfills = new HashSet<>(this.backfills);
newBackfills.addAll(that.backfills);
if (bf != null) {
newBackfills.remove(bf);
}
Set<AssemblyResolvedPatterns> newForbids = new HashSet<>(this.forbids);
newForbids.addAll(that.forbids);
return new AssemblyResolvedPatterns(description, cons, children, right, newIns, newCtx,
Collections.unmodifiableSet(newBackfills), Collections.unmodifiableSet(newForbids));
}
/** /**
* Combine the given backfill record into this resolution * Decode a portion of the context block
* *
* @param bf the backfill record * @param start the first byte to decode
* @return the result * @param len the number of bytes to decode
* @return the read masked value
* @see AssemblyPatternBlock#readBytes(int, int)
*/ */
public AssemblyResolvedPatterns combine(AssemblyResolvedBackfill bf) { MaskedLong readContext(int start, int len);
Set<AssemblyResolvedBackfill> newBackfills = new HashSet<>(this.backfills);
newBackfills.add(bf);
return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx,
Collections.unmodifiableSet(newBackfills), forbids);
}
/**
* Create a new resolution from this one with the given forbidden patterns recorded
*
* @param more the additional forbidden patterns to record
* @return the new resolution
*/
public AssemblyResolvedPatterns withForbids(Set<AssemblyResolvedPatterns> more) {
Set<AssemblyResolvedPatterns> combForbids = new HashSet<>(this.forbids);
combForbids.addAll(more);
return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx,
backfills, Collections.unmodifiableSet(more));
}
/**
* Create a copy of this resolution with a new description
*
* @param desc the new description
* @return the copy
*/
public AssemblyResolvedPatterns withDescription(String desc) {
return new AssemblyResolvedPatterns(desc, cons, children, right, ins, ctx, backfills,
forbids);
}
/**
* Create a copy of this resolution with a replaced constructor
*
* @param cons the new constructor
* @return the copy
*/
public AssemblyResolvedPatterns withConstructor(Constructor cons) {
return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx,
backfills,
forbids);
}
/**
* Encode the given value into the context block as specified by an operation
*
* @param cop the context operation specifying the location of the value to encode
* @param val the masked value to encode
* @return the result
*
* This is the forward (as in disassembly) direction of applying context operations. The
* pattern expression is evaluated, and the result is written as specified.
*/
public AssemblyResolvedPatterns writeContextOp(ContextOp cop, MaskedLong val) {
AssemblyPatternBlock newCtx = this.ctx.writeContextOp(cop, val);
return new AssemblyResolvedPatterns(description, cons, children, right, ins, newCtx,
backfills, forbids);
}
/** /**
* Decode the value from the context located where the given context operation would write * Decode the value from the context located where the given context operation would write
@ -358,55 +120,93 @@ public class AssemblyResolvedPatterns extends AssemblyResolution {
* @param cop the context operation whose "variable" to read. * @param cop the context operation whose "variable" to read.
* @return the masked result. * @return the masked result.
*/ */
public MaskedLong readContextOp(ContextOp cop) { MaskedLong readContextOp(ContextOp cop);
return ctx.readContextOp(cop);
}
/** /**
* Duplicate this resolution, with additional description text appended * Check if this and another resolution have equal encodings
* *
* @param append the text to append * <p>
* @return the duplicate NOTE: An additional separator {@code ": "} is inserted * This is like {@link #equals(Object)}, but it ignores backfill records and forbidden patterns.
*
* @param that the other resolution
* @return true if both have equal encodings
*/ */
public AssemblyResolvedPatterns copyAppendDescription(String append) { boolean bitsEqual(AssemblyResolvedPatterns that);
AssemblyResolvedPatterns cp = new AssemblyResolvedPatterns(
description + ": " + append, cons, children, right, ins.copy(), ctx.copy(), backfills,
forbids);
return cp;
}
@Override
public AssemblyResolvedPatterns withRight(AssemblyResolution right) {
AssemblyResolvedPatterns cp = new AssemblyResolvedPatterns(description, cons,
children, right, ins.copy(), ctx.copy(), backfills, forbids);
return cp;
}
public AssemblyResolvedPatterns nopLeftSibling() {
return new AssemblyResolvedPatterns("nop-left", null, null, this, ins.copy(),
ctx.copy(), backfills, forbids);
}
@Override
public AssemblyResolvedPatterns parent(String description, int opCount) {
List<AssemblyResolution> allRight = getAllRight();
AssemblyResolvedPatterns cp = new AssemblyResolvedPatterns(description, cons,
allRight.subList(0, opCount), allRight.get(opCount), ins, ctx, backfills, forbids);
return cp;
}
/** /**
* Set all bits read by a given context operation to unknown * Check if this assembled construct state is the same as the given dis-assembled construct
* state.
*/
boolean equivalentConstructState(ConstructState state);
@Override
AssemblyResolvedPatterns shift(int shamt);
/**
* Create a copy of this resolution with a new description
* *
* @param cop the context operation * @param desc the new description
* @return the copy
*/
AssemblyResolvedPatterns withDescription(String description);
/**
* Create a copy of this resolution with a sibling to the right
*
* <p>
* The right sibling is a mechanism for collecting children of a parent yet to be created. See
* {@link #parent(String, int)}.
*
* @param right the right sibling
* @return the new resolution
*/
AssemblyResolvedPatterns withRight(AssemblyResolution right);
/**
* Create a copy of this resolution with a replaced constructor
*
* @param cons the new constructor
* @return the copy
*/
AssemblyResolvedPatterns withConstructor(Constructor cons);
/**
* Combine the encodings and backfills of the given resolution into this one
*
* <p>
* This combines corresponding pattern blocks (assuming they agree), collects backfill records,
* and collects forbidden patterns.
*
* @param that the other resolution
* @return the result if successful, or null
*/
AssemblyResolvedPatterns combine(AssemblyResolvedPatterns pat);
/**
* Combine the given backfill record into this resolution
*
* @param bf the backfill record
* @return the result * @return the result
* @see AssemblyPatternBlock#maskOut(ContextOp)
*/ */
public AssemblyResolvedPatterns maskOut(ContextOp cop) { AssemblyResolvedPatterns combine(AssemblyResolvedBackfill bf);
AssemblyPatternBlock newCtx = this.ctx.maskOut(cop);
return new AssemblyResolvedPatterns(description, cons, children, right, ins, newCtx, /**
backfills, forbids); * Combine a backfill result
} *
* <p>
* When a backfill is successful, the result should be combined with the owning resolution. In
* addition, for bookkeeping's sake, the resolved record should be removed from the list of
* backfills.
*
* @param that the result from backfilling
* @param bf the resolved backfilled record
* @return the result if successful, or null
*/
AssemblyResolvedPatterns combineLessBackfill(AssemblyResolvedPatterns that,
AssemblyResolvedBackfill bf);
@Override
AssemblyResolvedPatterns parent(String description, int opCount);
/** /**
* Apply as many backfill records as possible * Apply as many backfill records as possible
@ -422,52 +222,29 @@ public class AssemblyResolvedPatterns extends AssemblyResolution {
* @param vals the values. * @param vals the values.
* @return the result, or an error. * @return the result, or an error.
*/ */
public AssemblyResolution backfill(RecursiveDescentSolver solver, Map<String, Long> vals) { AssemblyResolution backfill(RecursiveDescentSolver solver, Map<String, Long> vals);
if (!hasBackfills()) {
return this;
}
AssemblyResolvedPatterns res = this;
loop: while (true) {
for (AssemblyResolvedBackfill bf : res.backfills) {
AssemblyResolution ar = bf.solve(solver, vals, this);
if (ar.isError()) {
continue;
}
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar;
AssemblyResolvedPatterns check = res.combineLessBackfill(rc, bf);
if (check == null) {
return AssemblyResolution.error("Conflict: Backfill " + bf.description, res);
}
res = check;
continue loop;
}
return res;
}
}
@Override
public String lineToString() {
return dumpConstructorTree() + ":" + INS + ins + SEP + CTX + ctx + " (" + description + ")";
}
/** /**
* Check if this resolution has pending backfills to apply * Check if the current encoding is forbidden by one of the attached patterns
* *
* @return true if there are backfills * <p>
* The pattern becomes forbidden if this encoding's known bits are an overset of any forbidden
* pattern's known bits.
*
* @return false if the pattern is forbidden (and thus in error), true if permitted
*/ */
public boolean hasBackfills() { AssemblyResolution checkNotForbidden();
return !backfills.isEmpty();
}
/** /**
* Check if this resolution includes forbidden patterns * Generate a new nop right this resolution to its right.
* *
* @return true if there are forbidden patterns * <p>
* Alternatively phrased: append a nop to the left of this list of siblings, returning the new
* head.
*
* @return the nop resolution
*/ */
private boolean hasForbids() { AssemblyResolvedPatterns nopLeftSibling();
return !forbids.isEmpty();
}
/** /**
* Solve and apply context changes in reverse to forbidden patterns * Solve and apply context changes in reverse to forbidden patterns
@ -484,193 +261,8 @@ public class AssemblyResolvedPatterns extends AssemblyResolution {
* @return the result * @return the result
* @see AssemblyConstructorSemantic#solveContextChanges(AssemblyResolvedPatterns, Map) * @see AssemblyConstructorSemantic#solveContextChanges(AssemblyResolvedPatterns, Map)
*/ */
public AssemblyResolvedPatterns solveContextChangesForForbids( AssemblyResolvedPatterns solveContextChangesForForbids(AssemblyConstructorSemantic sem,
AssemblyConstructorSemantic sem, Map<String, Long> vals) { Map<String, Long> vals);
if (!hasForbids()) {
return this;
}
Set<AssemblyResolvedPatterns> newForbids = new HashSet<>();
for (AssemblyResolvedPatterns f : this.forbids) {
AssemblyResolution t = sem.solveContextChanges(f, vals);
if (!(t instanceof AssemblyResolvedPatterns)) {
// Can't be solved, so it can be dropped
continue;
}
newForbids.add((AssemblyResolvedPatterns) t);
}
return new AssemblyResolvedPatterns(description, cons, children, right, ins, ctx,
backfills, Collections.unmodifiableSet(newForbids));
}
/**
* Get the length of the instruction encoding
*
* <p>
* This is used to ensure each operand is encoded at the correct offset
*
* <p>
* <b>NOTE:</b> this DOES include the offset<br>
* <b>NOTE:</b> this DOES include pending backfills
*
* @return the length of the instruction block
*/
public int getInstructionLength() {
int inslen = ins.length();
for (AssemblyResolvedBackfill bf : backfills) {
inslen = Math.max(inslen, bf.getInstructionLength());
}
return inslen;
}
/**
* Get the length of the instruction encoding, excluding trailing undefined bytes
*
* <p>
* <b>NOTE:</b> this DOES include the offset<br>
* <b>NOTE:</b> this DOES NOT include pending backfills
*
* @return the length of the defined bytes in the instruction block
*/
public int getDefinedInstructionLength() {
byte[] imsk = ins.getMask();
int i;
for (i = imsk.length - 1; i >= 0; i--) {
if (imsk[i] != 0) {
break;
}
}
return ins.getOffset() + i + 1;
}
/**
* Get the instruction block
*
* @return the instruction block
*/
public AssemblyPatternBlock getInstruction() {
return ins;
}
/**
* Get the context block
*
* @return the context block
*/
public AssemblyPatternBlock getContext() {
return ctx;
}
/**
* Decode a portion of the instruction block
*
* @param start the first byte to decode
* @param len the number of bytes to decode
* @return the read masked value
* @see AssemblyPatternBlock#readBytes(int, int)
*/
public MaskedLong readInstruction(int start, int len) {
return ins.readBytes(start, len);
}
/**
* Decode a portion of the context block
*
* @param start the first byte to decode
* @param len the number of bytes to decode
* @return the read masked value
* @see AssemblyPatternBlock#readBytes(int, int)
*/
public MaskedLong readContext(int start, int len) {
return ctx.readBytes(start, len);
}
@Override
public boolean isError() {
return false;
}
@Override
public boolean isBackfill() {
return false;
}
@Override
public boolean hasChildren() {
return super.hasChildren() || hasBackfills() || hasForbids();
}
@Override
protected String childrenToString(String indent) {
StringBuilder sb = new StringBuilder();
if (super.hasChildren()) {
sb.append(super.childrenToString(indent) + "\n");
}
for (AssemblyResolvedBackfill bf : backfills) {
sb.append(indent);
sb.append("backfill: " + bf + "\n");
}
for (AssemblyResolvedPatterns f : forbids) {
sb.append(indent);
sb.append("forbidden: " + f + "\n");
}
return sb.substring(0, sb.length() - 1);
}
protected static final Pattern pat = Pattern.compile("line(\\d*)");
/**
* Used for testing and diagnostics: list the constructor line numbers used to resolve this
* encoding
*
* <p>
* This includes braces to describe the tree structure
*
* @see ConstructState#dumpConstructorTree()
* @return the constructor tree
*/
public String dumpConstructorTree() {
StringBuilder sb = new StringBuilder();
if (cons == null) {
return null;
}
sb.append(cons.getSourceFile() + ":" + cons.getLineno());
if (children == null) {
return sb.toString();
}
List<String> subs = new ArrayList<>();
for (AssemblyResolution c : children) {
if (c instanceof AssemblyResolvedPatterns) {
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) c;
String s = rc.dumpConstructorTree();
if (s != null) {
subs.add(s);
}
}
}
if (subs.isEmpty()) {
return sb.toString();
}
sb.append('[');
sb.append(StringUtils.join(subs, ","));
sb.append(']');
return sb.toString();
}
/**
* Count the number of bits specified in the resolution patterns
*
* <p>
* Totals the specificity of the instruction and context pattern blocks.
*
* @return the number of bits in the resulting patterns
* @see AssemblyPatternBlock#getSpecificity()
*/
public int getSpecificity() {
return ins.getSpecificity() + ctx.getSpecificity();
}
/** /**
* Get an iterable over all the possible fillings of the instruction pattern given a context * Get an iterable over all the possible fillings of the instruction pattern given a context
@ -700,107 +292,58 @@ public class AssemblyResolvedPatterns extends AssemblyResolution {
* @param forCtx the context at the assembly address * @param forCtx the context at the assembly address
* @return the iterable * @return the iterable
*/ */
public Iterable<byte[]> possibleInsVals(AssemblyPatternBlock forCtx) { Iterable<byte[]> possibleInsVals(AssemblyPatternBlock forCtx);
AssemblyPatternBlock ctxCompat = ctx.combine(forCtx);
if (ctxCompat == null) {
return List.of();
}
Predicate<byte[]> removeForbidden = (byte[] val) -> {
for (AssemblyResolvedPatterns f : forbids) {
// If the forbidden length is larger than us, we can ignore it
if (f.getDefinedInstructionLength() > val.length) {
continue;
}
// Check if the context matches, if not, we can let it pass
if (null == f.getContext().combine(forCtx)) {
continue;
}
// If the context matches, now check the instruction
AssemblyPatternBlock i = f.getInstruction();
AssemblyPatternBlock vi =
AssemblyPatternBlock.fromBytes(ins.length() - val.length, val);
if (null == i.combine(vi)) {
continue;
}
return false;
}
return true;
};
return new Iterable<byte[]>() {
@Override
public Iterator<byte[]> iterator() {
return IteratorUtils.filteredIterator(ins.possibleVals().iterator(),
removeForbidden);
}
};
}
protected static int getOpIndex(String piece) {
if (piece.charAt(0) != '\n') {
return -1;
}
return piece.charAt(1) - 'A';
}
/** /**
* If the construct state is a {@code ^instruction} or other purely-recursive constructor, get * Used for testing and diagnostics: list the constructor line numbers used to resolve this
* its single child. * encoding
* *
* @param state the parent state * <p>
* @return the child state if recursive, or null * This includes braces to describe the tree structure
*
* @see ConstructState#dumpConstructorTree()
* @return the constructor tree
*/ */
protected static ConstructState getPureRecursion(ConstructState state) { String dumpConstructorTree();
// NB. There can be other operands, but only one can be printed
// Furthermore, nothing else can be printed, whether an operand or not
List<String> pieces = state.getConstructor().getPrintPieces();
if (pieces.size() != 1) {
return null;
}
int opIdx = getOpIndex(pieces.get(0));
if (opIdx < 0) {
return null;
}
ConstructState sub = state.getSubState(opIdx);
if (sub == null || sub.getConstructor() == null ||
sub.getConstructor().getParent() != state.getConstructor().getParent()) {
// not recursive
return null;
}
return sub;
}
public boolean equivalentConstructState(ConstructState state) { /**
ConstructState rec = getPureRecursion(state); * Truncate (unshift) the resolved instruction pattern from the left
if (rec != null) { *
if (state.getConstructor() == cons) { * <b>NOTE:</b> This drops all backfill and forbidden pattern records, since this method is
assert children.size() == 1; * typically used to read token fields rather than passed around for resolution.
AssemblyResolvedPatterns recRes = (AssemblyResolvedPatterns) children.get(0); *
return recRes.equivalentConstructState(rec); * @param amt the number of bytes to remove from the left
} * @return the result
return equivalentConstructState(rec); */
} AssemblyResolvedPatterns truncate(int shamt);
if (state.getConstructor() != cons) {
return false; /**
} * Create a new resolution from this one with the given forbidden patterns recorded
int opCount = cons.getNumOperands(); *
for (int opIdx = 0; opIdx < opCount; opIdx++) { * @param more the additional forbidden patterns to record
OperandSymbol opSym = cons.getOperand(opIdx); * @return the new resolution
Set<Integer> printed = */
Arrays.stream(cons.getOpsPrintOrder()).boxed().collect(Collectors.toSet()); AssemblyResolvedPatterns withForbids(Set<AssemblyResolvedPatterns> more);
if (!(opSym.getDefiningSymbol() instanceof SubtableSymbol)) {
AssemblyTreeResolver.DBG.println("Operand " + opSym + " is not a sub-table"); /**
continue; * Set all bits read by a given context operation to unknown
} *
if (!printed.contains(opIdx)) { * @param cop the context operation
AssemblyTreeResolver.DBG.println("Operand " + opSym + " is hidden"); * @return the result
continue; * @see AssemblyPatternBlock#maskOut(ContextOp)
} */
AssemblyResolvedPatterns child = (AssemblyResolvedPatterns) children.get(opIdx); AssemblyResolvedPatterns maskOut(ContextOp cop);
ConstructState subState = state.getSubState(opIdx);
if (!child.equivalentConstructState(subState)) { /**
return false; * Encode the given value into the context block as specified by an operation
} *
} * <p>
return true; * This is the forward (as in disassembly) direction of applying context operations. The pattern
} * expression is evaluated, and the result is written as specified.
*
* @param cop the context operation specifying the location of the value to encode
* @param val the masked value to encode
* @return the result
*/
AssemblyResolvedPatterns writeContextOp(ContextOp cop, MaskedLong val);
} }

View file

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

View file

@ -15,550 +15,16 @@
*/ */
package ghidra.app.plugin.assembler.sleigh.sem; package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.*; import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch;
import java.util.stream.Collectors; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import java.util.stream.Stream;
import ghidra.app.plugin.assembler.sleigh.SleighAssemblerBuilder;
import ghidra.app.plugin.assembler.sleigh.expr.*;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyProduction;
import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyStateGenerator.GeneratorContext;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolutionResults.Applicator;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblyNonTerminal;
import ghidra.app.plugin.assembler.sleigh.tree.*;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
import ghidra.app.plugin.processors.sleigh.symbol.*;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.lang.InsufficientBytesException;
import ghidra.program.model.lang.UnknownInstructionException;
import ghidra.program.model.mem.ByteMemBufferImpl;
import ghidra.program.model.mem.MemBuffer;
/** public class AssemblyTreeResolver extends AbstractAssemblyTreeResolver<AssemblyResolvedPatterns> {
* The workhorse of semantic resolution for the assembler
*
* <p>
* This class takes a parse tree and some additional information (start address, context, etc.) and
* attempts to determine possible encodings using the semantics associated with each branch of the
* given parse tree. Details of this process are described in {@link SleighAssemblerBuilder}.
*
* @see SleighAssemblerBuilder
*/
public class AssemblyTreeResolver {
protected static final RecursiveDescentSolver SOLVER = RecursiveDescentSolver.getSolver();
protected static final DbgTimer DBG = DbgTimer.INACTIVE;
public static final String INST_START = "inst_start"; public AssemblyTreeResolver(
public static final String INST_NEXT = "inst_next"; AbstractAssemblyResolutionFactory<AssemblyResolvedPatterns, ?> factory,
public static final String INST_NEXT2 = "inst_next2"; SleighLanguage lang, Address at, AssemblyParseBranch tree, AssemblyPatternBlock context,
AssemblyContextGraph ctxGraph) {
protected final SleighLanguage lang; super(factory, lang, at, tree, context, ctxGraph);
protected final Address at;
protected final Map<String, Long> vals = new HashMap<>();
protected final AssemblyParseBranch tree;
protected final AssemblyGrammar grammar;
protected final AssemblyPatternBlock context;
protected final AssemblyContextGraph ctxGraph;
/**
* Construct a resolver for the given parse tree
*
* @param lang
* @param at the address where the instruction will start
* @param tree the parse tree
* @param context the context expected at {@code instStart}
* @param ctxGraph the context transition graph used to resolve purely-recursive productions
*/
public AssemblyTreeResolver(SleighLanguage lang, Address at, AssemblyParseBranch tree,
AssemblyPatternBlock context, AssemblyContextGraph ctxGraph) {
this.lang = lang;
this.at = at;
this.vals.put(INST_START, at.getAddressableWordOffset());
this.tree = tree;
this.grammar = tree.getGrammar();
this.context = context.fillMask();
this.ctxGraph = ctxGraph;
}
/**
* Resolve the tree for the given parameters
*
* @return a set of resolutions (encodings and errors)
*/
public AssemblyResolutionResults resolve() {
AssemblyResolvedPatterns empty = AssemblyResolution.nop("Empty");
AssemblyConstructStateGenerator rootGen =
new AssemblyConstructStateGenerator(this, tree, empty);
Collection<AssemblyResolvedError> errors = new ArrayList<>();
Stream<AssemblyGeneratedPrototype> protStream =
rootGen.generate(new GeneratorContext(List.of(), 0));
if (DBG == DbgTimer.ACTIVE) {
try (DbgCtx dc = DBG.start("Prototypes:")) {
protStream = protStream.map(prot -> {
DBG.println(prot);
return prot;
}).collect(Collectors.toList()).stream();
}
}
Stream<AssemblyResolvedPatterns> patStream =
protStream.map(p -> p.state).distinct().flatMap(s -> s.resolve(empty, errors));
AssemblyResolutionResults results = new AssemblyResolutionResults();
patStream.forEach(results::add);
results = resolveRootRecursion(results);
results = selectContext(results);
results = resolvePendingBackfills(results);
// TODO: Remove this? It's subsumed by filterByDisassembly, and more accurately....
results = filterForbidden(results);
results = filterByDisassembly(results);
results.addAll(errors);
return results;
}
/**
* If applicable, get the {@code I => I} production of the grammar
*
* @return the production
*/
protected AssemblyProduction getRootRecursion() {
assert tree.getParent() == null;
AssemblyProduction rootProd = tree.getProduction();
AssemblyNonTerminal start = rootProd.getLHS();
AssemblyProduction rec = grammar.getPureRecursion(start);
return rec;
}
/**
* If necessary, resolve recursive constructors at the root, usually for prefixes
*
* <p>
* If there are no pure recursive constructors at the root, then this simply returns
* {@code temp} unmodified.
*
* @param temp the resolved root results
* @return the results with pure recursive constructors applied to obtain a compatible context
*/
// Ugh, public so I can refer to it in javadocs...
public AssemblyResolutionResults resolveRootRecursion(AssemblyResolutionResults temp) {
AssemblyProduction rootRec = getRootRecursion();
if (rootRec == null) {
return temp;
}
try (DbgCtx dc = DBG.start("Resolving root recursion:")) {
AssemblyResolutionResults result = new AssemblyResolutionResults();
for (AssemblyResolution ar : temp) {
if (ar.isError()) {
result.add(ar);
continue;
}
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar;
AssemblyPatternBlock dst = rc.getContext();
// TODO: The desired context may need to be passed in. For now, just take start.
AssemblyPatternBlock src = context; // NOTE: This is only correct for "instruction"
String table = "instruction";
DBG.println("Finding paths from " + src + " to " + ar.lineToString());
Collection<Deque<AssemblyConstructorSemantic>> paths =
ctxGraph.computeOptimalApplications(src, table, dst, table);
DBG.println("Found " + paths.size());
for (Deque<AssemblyConstructorSemantic> path : paths) {
DBG.println(" " + path);
result.absorb(applyRecursionPath(path, tree, rootRec, rc));
}
}
return result;
}
}
/**
* Attempt a second time to solve operands and context changes
*
* <p>
* Backfills that depended on {@code inst_next} should now easily be solved, since the
* instruction length is now known.
*
* @param temp the resolved results, with backfill pending
* @return the results without backfill, possible with new errors
*/
protected AssemblyResolutionResults resolvePendingBackfills(AssemblyResolutionResults temp) {
return temp.apply(rc -> {
if (!rc.hasBackfills()) {
return rc;
}
vals.put(INST_NEXT, at.add(rc.getInstructionLength()).getAddressableWordOffset());
// inst_next2 use not really supported
vals.put(INST_NEXT2, at.add(rc.getInstructionLength()).getAddressableWordOffset());
DBG.println("Backfilling: " + rc);
AssemblyResolution ar = rc.backfill(SOLVER, vals);
DBG.println("Backfilled final: " + ar);
return ar;
}).apply(rc -> {
if (rc.hasBackfills()) {
return AssemblyResolution.error("Solution is incomplete", "failed backfill",
List.of(rc), null);
}
return rc;
});
}
/**
* Filter out results whose context do not match that requested
*
* @param temp the results whose contexts have not yet been checked
* @return the results that pass. Those that do not are replaced with errors.
*/
protected AssemblyResolutionResults selectContext(AssemblyResolutionResults temp) {
AssemblyResolvedPatterns ctx =
AssemblyResolution.contextOnly(context, "Selecting context");
return temp.apply(rc -> {
AssemblyResolvedPatterns check = rc.combine(ctx);
if (null == check) {
return AssemblyResolution.error("Incompatible context", "resolving", List.of(rc),
null);
}
return check;
});
}
/**
* Filter out results that would certainly be disassembled differently than assembled
*
* <p>
* Because of constructor precedence rules, it is possible to assemble a pattern from a
* prototype that would not result in equivalent disassembly. This can be detected in some cases
* via the "forbids" mechanism, where more specific constructors are recorded with the result.
* If the generated pattern matches on of those more-specific constructors, it is forbidden.
*
* @param temp the results whose forbids have not yet been checked
* @return the results that pass. Those that do not are replaced with errors.
*/
protected AssemblyResolutionResults filterForbidden(AssemblyResolutionResults temp) {
return temp.apply(rc -> rc.checkNotForbidden());
}
/**
* Filter out results that get disassembled differently than assembled
*
* <p>
* The forbids mechanism is not perfect, so as a final fail safe, we disassemble the result and
* compare the prototypes.
*
* @param temp the results whose disassemblies have not yet been checked
* @return the results that pass. Those that do not are replaced with errors.
*/
protected AssemblyResolutionResults filterByDisassembly(AssemblyResolutionResults temp) {
AssemblyDefaultContext asmCtx = new AssemblyDefaultContext(lang);
asmCtx.setContextRegister(context);
return temp.apply(rc -> {
MemBuffer buf =
new ByteMemBufferImpl(at, rc.getInstruction().getVals(), lang.isBigEndian());
try {
SleighInstructionPrototype ip =
(SleighInstructionPrototype) lang.parse(buf, asmCtx, false);
if (!rc.equivalentConstructState(ip.getRootState())) {
return AssemblyResolution.error("Disassembly prototype mismatch", rc);
}
return rc;
}
catch (InsufficientBytesException | UnknownInstructionException e) {
return AssemblyResolution.error("Disassembly failed: " + e.getMessage(), rc);
}
});
}
/**
* Get the state generator for a given operand and parse tree node
*
* @param opSym the operand symbol
* @param node the corresponding parse tree node, possibly null indicating a hidden operand
* @param fromLeft the accumulated patterns from the left sibling or parent
* @return the generator
*/
protected AbstractAssemblyStateGenerator<?> getStateGenerator(OperandSymbol opSym,
AssemblyParseTreeNode node, AssemblyResolvedPatterns fromLeft) {
if (node == null) {
return getHiddenStateGenerator(opSym, fromLeft);
}
if (node.isNumeric()) {
return new AssemblyOperandStateGenerator(this, (AssemblyParseNumericToken) node, opSym,
fromLeft);
}
if (node.isConstructor()) {
return new AssemblyConstructStateGenerator(this, (AssemblyParseBranch) node, fromLeft);
}
throw new AssertionError();
}
/**
* Get the state generator for a hidden operand
*
* @param opSym the operand symbol
* @param fromLeft the accumulated patterns from the left sibling or parent
* @return the generator
*/
protected AbstractAssemblyStateGenerator<?> getHiddenStateGenerator(OperandSymbol opSym,
AssemblyResolvedPatterns fromLeft) {
TripleSymbol defSym = opSym.getDefiningSymbol();
if (defSym instanceof SubtableSymbol) {
return new AssemblyHiddenConstructStateGenerator(this, (SubtableSymbol) defSym,
fromLeft);
}
return new AssemblyNopStateGenerator(this, opSym, fromLeft);
}
/**
* Apply a constructor pattern
*
* <p>
* TODO: This is currently used only for resolving recursion. Could this be factored with
* {@link AssemblyConstructState#resolve(AssemblyResolvedPatterns, Collection)}?
*
* @param sem the SLEIGH constructor
* @param shift the shift
* @param fromChildren the results from the single resolved child
* @return the results
*/
protected AssemblyResolutionResults resolvePatterns(AssemblyConstructorSemantic sem, int shift,
AssemblyResolutionResults fromChildren) {
AssemblyResolutionResults results = fromChildren;
results = applyMutations(sem, results);
results = applyPatterns(sem, shift, results);
results = tryResolveBackfills(results);
return results;
}
/**
* TODO: Can this be factored?
*/
protected AssemblyResolutionResults parent(String description, AssemblyResolutionResults temp,
int opCount) {
return temp.stream()
.map(r -> r.parent(description, opCount))
.collect(Collectors.toCollection(AssemblyResolutionResults::new));
}
/**
* TODO: This is currently used only for resolving recursion. Could this be factored with
* {@link AssemblyConstructState#resolveMutations(AssemblyResolvedPatterns, Collection)}?
*/
protected AssemblyResolutionResults applyMutations(AssemblyConstructorSemantic sem,
AssemblyResolutionResults temp) {
DBG.println("Applying context mutations:");
return temp.apply(rc -> {
DBG.println("Current: " + rc.lineToString());
AssemblyResolution backctx = sem.solveContextChanges(rc, vals);
DBG.println("Mutated: " + backctx.lineToString());
return backctx;
}).apply(rc -> {
return rc.solveContextChangesForForbids(sem, vals);
});
}
/**
* TODO: This is currently used only for resolving recursion. Could this be factored with
* {@link AssemblyConstructState#resolvePatterns(AssemblyResolvedPatterns, Collection)}?
*/
protected AssemblyResolutionResults applyPatterns(AssemblyConstructorSemantic sem, int shift,
AssemblyResolutionResults temp) {
DBG.println("Applying patterns:");
Collection<AssemblyResolvedPatterns> patterns =
sem.getPatterns().stream().map(p -> p.shift(shift)).collect(Collectors.toList());
return temp.apply(new Applicator() {
@Override
public Iterable<? extends AssemblyResolution> getPatterns(
AssemblyResolvedPatterns cur) {
return patterns;
}
@Override
public AssemblyResolvedPatterns setRight(AssemblyResolvedPatterns res,
AssemblyResolvedPatterns cur) {
// This is typically applied by parent, so don't insert sibling
return res;
}
@Override
public String describeError(AssemblyResolvedPatterns rc, AssemblyResolution pat) {
return "The patterns conflict " + pat.lineToString();
}
@Override
public AssemblyResolvedPatterns combineBackfill(AssemblyResolvedPatterns cur,
AssemblyResolvedBackfill bf) {
throw new AssertionError();
}
@Override
public AssemblyResolution finish(AssemblyResolvedPatterns resolved) {
return resolved.checkNotForbidden();
}
});
}
/**
* Apply constructors as indicated by a path returned by the context resolution graph
*
* <p>
* <b>NOTE:</b> The given path will be emptied during processing.
*
* @param path the path to apply
* @param branch the branch corresponding to the production whose LHS has a purely-recursive
* definition.
* @param rec the purely-recursive production
* @param child the intermediate result to apply the constructors to
* @return the results
*/
protected AssemblyResolutionResults applyRecursionPath(Deque<AssemblyConstructorSemantic> path,
AssemblyParseBranch branch, AssemblyProduction rec, AssemblyResolvedPatterns child) {
/*
* A constructor may have multiple patterns, so I cannot assume I will get at most one
* output at each constructor in the path. Start (1) collecting all the results, then (2)
* filter out and report the errors, then (3) feed successful resolutions into the next
* constructor in the path (or finish).
*/
AssemblyResolutionResults results = new AssemblyResolutionResults();
results.add(child);
while (!path.isEmpty()) {
AssemblyConstructorSemantic sem = path.pollLast();
int opIdx = sem.getOperandIndex(0);
Constructor cons = sem.getConstructor();
OperandSymbol opSym = cons.getOperand(opIdx);
if (-1 != opSym.getOffsetBase()) {
throw new AssertionError("TODO");
}
int offset = opSym.getRelativeOffset();
results = parent("Resolving recursive constructor: " + cons.getSourceFile() + ":" +
cons.getLineno(), results, 1);
results = results.apply(rc -> rc.shift(offset));
results = resolvePatterns(sem, 0, results).apply(rc -> rc.withConstructor(cons));
}
return results;
}
/**
* TODO: This is currently used only for resolving recursion. It seems its missing from the
* refactor?
*/
protected AssemblyResolutionResults tryResolveBackfills(AssemblyResolutionResults results) {
AssemblyResolutionResults res = new AssemblyResolutionResults();
next_ar: for (AssemblyResolution ar : results) {
if (ar.isError()) {
res.add(ar);
continue;
}
while (true) {
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar;
if (!rc.hasBackfills()) {
// finish: The complete solution is known
res.add(rc);
continue next_ar;
}
ar = rc.backfill(SOLVER, vals);
if (ar.isError() || ar.isBackfill()) {
// fail: It is now known that the solution doesn't exist
res.add(ar);
continue next_ar;
}
if (ar.equals(rc)) {
// fail: The solution is /still/ not known, and we made no progress
res.add(ar);
continue next_ar;
}
// Some progress was made, continue trying until we finish or fail
}
}
return res;
}
/**
* Compute the offset of an operand encoded in the instruction block
*
* <p>
* TODO: Currently, there are duplicate mechanisms for resolving a constructor: 1) The newer
* mechanism implemented in {@link AssemblyConstructState}, and 2) the older one implemented in
* {@link #applyPatterns(AssemblyConstructorSemantic, int, AssemblyResolutionResults)}. The
* latter seems to require this method, since it does not have pre-computed shifts as in the
* former. We should probably remove the latter in favor of the former....
*
* @param opsym the operand symbol
* @param cons the constructor containing the operand
* @return the offset (right shift) to apply to the encoded operand
*/
public static int computeOffset(OperandSymbol opsym, Constructor cons) {
int offset = opsym.getRelativeOffset();
int baseidx = opsym.getOffsetBase();
if (baseidx != -1) {
OperandSymbol baseop = cons.getOperand(baseidx);
offset += baseop.getMinimumLength();
offset += computeOffset(baseop, cons);
}
return offset;
}
/**
* Attempt to solve an expression
*
* @param exp the expression to solve
* @param goal the desired value of the expression
* @param vals any defined symbols
* @param cur the resolved constructor so far
* @param description a description of the result
* @return the encoded solution, or a backfill record
*/
protected static AssemblyResolution solveOrBackfill(PatternExpression exp, MaskedLong goal,
Map<String, Long> vals, AssemblyResolvedPatterns cur, String description) {
try {
return SOLVER.solve(exp, goal, vals, cur, description);
}
catch (NeedsBackfillException bf) {
int fieldLength = SOLVER.getInstructionLength(exp);
return AssemblyResolution.backfill(exp, goal, fieldLength, description);
}
}
/**
* Attempt to solve an expression
*
* <p>
* Converts the given goal to a fully-defined {@link MaskedLong} and then solves as before.
*
* @see #solveOrBackfill(PatternExpression, MaskedLong, Map, AssemblyResolvedPatterns, String)
*/
protected static AssemblyResolution solveOrBackfill(PatternExpression exp, long goal,
Map<String, Long> vals, AssemblyResolvedPatterns cur, String description) {
return solveOrBackfill(exp, MaskedLong.fromLong(goal), vals, cur, description);
}
/**
* Attempt to solve an expression
*
* <p>
* Converts the given goal and bits count to a {@link MaskedLong} and then solves as before. As
* a special case, if {@code bits == 0}, the goal is considered fully-defined (as if
* {@code bits == 64}).
*
* @see #solveOrBackfill(PatternExpression, MaskedLong, Map, AssemblyResolvedPatterns, String)
*/
protected static AssemblyResolution solveOrBackfill(PatternExpression exp, long goal, int bits,
Map<String, Long> vals, AssemblyResolvedPatterns cur, String description) {
long msk;
if (bits == 0 || bits >= 64) {
msk = -1L;
}
else {
msk = ~(-1L << bits);
}
return solveOrBackfill(exp, MaskedLong.fromMaskAndValue(msk, goal), vals, cur, description);
} }
} }

View file

@ -0,0 +1,30 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.assembler.sleigh.sem;
public class DefaultAssemblyResolutionFactory extends
AbstractAssemblyResolutionFactory<AssemblyResolvedPatterns, AssemblyResolvedBackfill> {
@Override
public DefaultAssemblyResolvedPatternBuilder newPatternsBuilder() {
return new DefaultAssemblyResolvedPatternBuilder();
}
@Override
public DefaultAssemblyResolvedBackfillBuilder newBackfillBuilder() {
return new DefaultAssemblyResolvedBackfillBuilder();
}
}

View file

@ -0,0 +1,158 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.Map;
import java.util.Objects;
import ghidra.app.plugin.assembler.sleigh.expr.*;
import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolvedBackfillBuilder;
import ghidra.app.plugin.processors.sleigh.expression.PatternExpression;
/**
* A {@link AssemblyResolution} indicating the need to solve an expression in the future
*
* <p>
* Such records are collected within a {@link AssemblyResolvedPatterns} and then solved just before
* the final result(s) are assembled. This is typically required by instructions that refer to the
* {@code inst_next} symbol.
*
* <p>
* <b>NOTE:</b> These are used internally. The user ought never to see these from the assembly API.
*/
public class DefaultAssemblyResolvedBackfill extends AbstractAssemblyResolution
implements AssemblyResolvedBackfill {
protected final PatternExpression exp;
protected final MaskedLong goal;
protected final int inslen;
protected final int offset;
/**
* @see {@link AssemblyResolution#backfill(PatternExpression, MaskedLong, Map, int, String)}
*/
protected DefaultAssemblyResolvedBackfill(AbstractAssemblyResolutionFactory<?, ?> factory,
String description, PatternExpression exp, MaskedLong goal, int inslen, int offset) {
super(factory, description, null, null);
this.exp = Objects.requireNonNull(exp);
this.goal = Objects.requireNonNull(goal);
this.inslen = inslen;
this.offset = offset;
}
@Override
protected int computeHash() {
int result = 0;
result += exp.hashCode();
result *= 31;
result += goal.hashCode();
result *= 31;
result += inslen;
result *= 31;
result += offset;
return result;
}
protected AbstractAssemblyResolvedBackfillBuilder<?> copyBuilder() {
var builder = factory.newBackfillBuilder();
builder.description = description;
builder.exp = exp;
builder.goal = goal;
builder.inslen = inslen;
builder.offset = offset;
return builder;
}
/**
* Duplicate this record
*
* @return the duplicate
*/
protected AssemblyResolvedBackfill copy() {
return copyBuilder().build();
}
@Override
public AssemblyResolvedBackfill withRight(AssemblyResolution right) {
throw new AssertionError();
}
@Override
public int getInstructionLength() {
return offset + inslen;
}
@Override
public boolean isError() {
return false;
}
@Override
public boolean isBackfill() {
return true;
}
@Override
public String lineToString() {
return "Backfill (len:" + inslen + ",off:" + offset + ") " + goal + " := " + exp + " (" +
description + ")";
}
protected AbstractAssemblyResolvedBackfillBuilder<?> shiftBuilder(int amt) {
var builder = factory.newBackfillBuilder();
builder.description = description;
builder.exp = exp;
builder.goal = goal;
builder.inslen = inslen;
builder.offset = offset + amt;
return builder;
}
@Override
public AssemblyResolvedBackfill shift(int amt) {
return shiftBuilder(amt).build();
}
@Override
public AssemblyResolution parent(String description, int opCount) {
throw new AssertionError();
}
@Override
public AssemblyResolution solve(RecursiveDescentSolver solver, Map<String, Long> vals,
AssemblyResolvedPatterns cur) {
try {
AssemblyResolution ar =
solver.solve(factory, exp, goal, vals, cur.truncate(offset), description);
if (ar.isError()) {
return ar;
}
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar;
return rc.shift(offset);
}
catch (NeedsBackfillException e) {
return factory.newErrorBuilder()
.error("Solution still requires backfill")
.description(description)
.build();
}
catch (UnsupportedOperationException e) {
return factory.newErrorBuilder()
.error("Unsupported: " + e.getMessage())
.description(description)
.build();
}
}
}

View file

@ -0,0 +1,111 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.List;
/**
* A {@link AssemblyResolution} indicating the occurrence of a (usually semantic) error
*
* <p>
* The description should indicate where the error occurred. The error message should explain the
* actual error. To help the user diagnose the nature of the error, errors in sub-constructors
* should be placed as children of an error given by the parent constructor.
*/
public class DefaultAssemblyResolvedError extends AbstractAssemblyResolution
implements AssemblyResolvedError {
protected final String error;
@Override
protected int computeHash() {
return error.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
DefaultAssemblyResolvedError that = (DefaultAssemblyResolvedError) obj;
if (!this.error.equals(that.error)) {
return false;
}
return true;
}
/**
* @see AssemblyResolution#error(String, String, List)
*/
protected DefaultAssemblyResolvedError(AbstractAssemblyResolutionFactory<?, ?> factory,
String description, List<? extends AssemblyResolution> children,
AssemblyResolution right, String error) {
super(factory, description, children, right);
AbstractAssemblyTreeResolver.DBG.println(error);
this.error = error;
}
@Override
public boolean isError() {
return true;
}
@Override
public boolean isBackfill() {
return false;
}
/**
* Get a description of the error
*
* @return the description
*/
@Override
public String getError() {
return error;
}
@Override
public String lineToString() {
return error + " (" + description + ")";
}
@Override
public AssemblyResolution shift(int amt) {
return this;
}
@Override
public AssemblyResolution withRight(AssemblyResolution right) {
return factory.newErrorBuilder()
.error(error)
.description(description)
.children(children)
.right(right)
.build();
}
@Override
public AssemblyResolution parent(String description, int opCount) {
return factory.newErrorBuilder()
.error(error)
.description(description)
.children(getAllRight())
.build();
}
}

View file

@ -0,0 +1,775 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.assembler.sleigh.sem;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.collections4.Predicate;
import org.apache.commons.lang3.StringUtils;
import ghidra.app.plugin.assembler.AssemblySelector;
import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong;
import ghidra.app.plugin.assembler.sleigh.expr.RecursiveDescentSolver;
import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolutionBuilder;
import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory.AbstractAssemblyResolvedPatternsBuilder;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.plugin.processors.sleigh.symbol.OperandSymbol;
import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol;
/**
* A {@link AssemblyResolution} indicating successful application of a constructor
*
* <p>
* This is almost analogous to {@link ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern
* DisjointPattern}, in that is joins an instruction {@link AssemblyPatternBlock} with a
* corresponding context {@link AssemblyPatternBlock}. However, this object is mutable, and it
* collects backfill records, as well as forbidden patterns.
*
* <p>
* When the applied constructor is from the "instruction" subtable, this represents a fully-
* constructed instruction with required context. All backfill records ought to be resolved and
* applied before the final result is given to the user, i.e., passed into the
* {@link AssemblySelector}. If at any time during the resolution or backfill process, the result
* becomes confined to one of the forbidden patterns, it must be dropped, since the encoding will
* actually invoke a more specific SLEIGH constructor.
*/
public class DefaultAssemblyResolvedPatterns extends AbstractAssemblyResolution
implements AssemblyResolvedPatterns {
protected static final String INS = AbstractAssemblyResolutionFactory.INS;
protected static final String CTX = AbstractAssemblyResolutionFactory.CTX;
protected static final String SEP = AbstractAssemblyResolutionFactory.SEP;
protected final Constructor cons;
protected final AssemblyPatternBlock ins;
protected final AssemblyPatternBlock ctx;
protected final Set<AssemblyResolvedBackfill> backfills;
protected final Set<AssemblyResolvedPatterns> forbids;
/**
* @see AssemblyResolution#resolved(AssemblyPatternBlock, AssemblyPatternBlock, String,
* Constructor, List, AssemblyResolution)
*/
protected DefaultAssemblyResolvedPatterns(AbstractAssemblyResolutionFactory<?, ?> factory,
String description, Constructor cons, List<? extends AssemblyResolution> children,
AssemblyResolution right, AssemblyPatternBlock ins, AssemblyPatternBlock ctx,
Set<AssemblyResolvedBackfill> backfills, Set<AssemblyResolvedPatterns> forbids) {
super(factory, description, children, right);
this.cons = cons;
this.ins = ins == null ? AssemblyPatternBlock.nop() : ins;
this.ctx = ctx == null ? AssemblyPatternBlock.nop() : ctx;
this.backfills = backfills == null ? Set.of() : Collections.unmodifiableSet(backfills);
this.forbids = forbids == null ? Set.of() : Collections.unmodifiableSet(forbids);
}
@Override
protected int computeHash() {
int result = 0;
result += ins.hashCode();
result *= 31;
result += ctx.hashCode();
result *= 31;
result += backfills.hashCode();
result *= 31;
result += forbids.hashCode();
return result;
}
protected boolean partsEqual(DefaultAssemblyResolvedPatterns that) {
if (!this.ins.equals(that.ins)) {
return false;
}
if (!this.ctx.equals(that.ctx)) {
return false;
}
if (!this.backfills.equals(that.backfills)) {
return false;
}
if (!this.forbids.equals(that.forbids)) {
return false;
}
return true;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (this.getClass() != obj.getClass()) {
return false;
}
DefaultAssemblyResolvedPatterns that = (DefaultAssemblyResolvedPatterns) obj;
return partsEqual(that);
}
protected AbstractAssemblyResolvedPatternsBuilder<?> shiftBuilder(int amt) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins.shift(amt);
builder.ctx = ctx;
// Also shift the attached backfills and forbidden patterns
builder.backfills = new HashSet<>();
for (AssemblyResolvedBackfill bf : this.backfills) {
builder.backfills.add(bf.shift(amt));
}
builder.forbids = new HashSet<>();
for (AssemblyResolvedPatterns f : this.forbids) {
builder.forbids.add(f.shift(amt));
}
return builder;
}
@Override
public AssemblyResolvedPatterns shift(int amt) {
if (amt == 0) {
return this;
}
return shiftBuilder(amt).build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> truncateBuilder(int amt) {
var builder = factory.newPatternsBuilder();
builder.description = "Truncated: " + description;
builder.cons = cons;
builder.right = right;
builder.ins = ins.truncate(amt);
builder.ctx = ctx;
return builder;
}
@Override
public AssemblyResolvedPatterns truncate(int amt) {
if (amt == 0) {
return this;
}
return truncateBuilder(amt).build();
}
protected AbstractAssemblyResolutionBuilder<?, ?> checkNotForbiddenBuilder() {
var builder = factory.newPatternsBuilder();
builder.forbids = new HashSet<>();
for (AssemblyResolvedPatterns f : forbids) {
AssemblyResolvedPatterns check = this.combine(f);
if (null == check) {
continue;
}
builder.forbids.add(f);
if (check.bitsEqual(this)) {
// The result would be disassembled by a more-specific constructor.
return factory.errorBuilder("The result is forbidden by " + f, this);
}
}
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins;
builder.ctx = ctx;
builder.backfills = backfills;
return builder;
}
@Override
public AssemblyResolution checkNotForbidden() {
return checkNotForbiddenBuilder().build();
}
@Override
public boolean bitsEqual(AssemblyResolvedPatterns that) {
return this.ins.equals(that.getInstruction()) && this.ctx.equals(that.getContext());
}
protected AbstractAssemblyResolvedPatternsBuilder<?> combineBuilder(
AssemblyResolvedPatterns that) {
var builder = factory.newPatternsBuilder();
builder.ins = ins.combine(that.getInstruction());
if (builder.ins == null) {
return null;
}
builder.ctx = ctx.combine(that.getContext());
if (builder.ctx == null) {
return null;
}
builder.backfills = new HashSet<>(this.backfills);
builder.backfills.addAll(that.getBackfills());
builder.forbids = new HashSet<>(this.forbids);
builder.forbids.addAll(that.getForbids());
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
return builder;
}
protected AbstractAssemblyResolvedPatternsBuilder<?> combineLessBackfillBuilder(
AssemblyResolvedPatterns that, AssemblyResolvedBackfill bf) {
var builder = combineBuilder(that);
builder.backfills.remove(bf);
return builder;
}
@Override
public AssemblyResolvedPatterns combine(AssemblyResolvedPatterns that) {
var builder = combineBuilder(that);
return builder == null ? null : builder.build();
}
/**
* Combine a backfill result
*
* <p>
* When a backfill is successful, the result should be combined with the owning resolution. In
* addition, for bookkeeping's sake, the resolved record should be removed from the list of
* backfills.
*
* @param that the result from backfilling
* @param bf the resolved backfilled record
* @return the result if successful, or null
*/
@Override
public AssemblyResolvedPatterns combineLessBackfill(AssemblyResolvedPatterns that,
AssemblyResolvedBackfill bf) {
var builder = combineLessBackfillBuilder(that, bf);
return builder == null ? null : builder.build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> combineBuilder(
AssemblyResolvedBackfill bf) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins;
builder.ctx = ctx;
builder.backfills = new HashSet<>(backfills);
builder.backfills.add(bf);
builder.forbids = forbids;
return builder;
}
@Override
public AssemblyResolvedPatterns combine(AssemblyResolvedBackfill bf) {
return combineBuilder(bf).build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> withForbidsBuilder(
Set<AssemblyResolvedPatterns> more) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins;
builder.ctx = ctx;
builder.backfills = backfills;
builder.forbids = new HashSet<>(forbids);
builder.forbids.addAll(more);
return builder;
}
@Override
public AssemblyResolvedPatterns withForbids(Set<AssemblyResolvedPatterns> more) {
return withForbidsBuilder(more).build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> withDescriptionBuilder(
String description) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins;
builder.ctx = ctx;
builder.backfills = backfills;
builder.forbids = forbids;
return builder;
}
@Override
public AssemblyResolvedPatterns withDescription(String description) {
return withDescriptionBuilder(description).build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> withConstructorBuilder(Constructor cons) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins;
builder.ctx = ctx;
builder.backfills = backfills;
builder.forbids = forbids;
return builder;
}
@Override
public AssemblyResolvedPatterns withConstructor(Constructor cons) {
return withConstructorBuilder(cons).build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> writeContextOpBuilder(ContextOp cop,
MaskedLong val) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins;
builder.ctx = ctx.writeContextOp(cop, val);
builder.backfills = backfills;
builder.forbids = forbids;
return builder;
}
@Override
public AssemblyResolvedPatterns writeContextOp(ContextOp cop, MaskedLong val) {
return writeContextOpBuilder(cop, val).build();
}
@Override
public MaskedLong readContextOp(ContextOp cop) {
return ctx.readContextOp(cop);
}
protected AbstractAssemblyResolvedPatternsBuilder<?> copyAppendDescriptionBuilder(
String append) {
var builder = factory.newPatternsBuilder();
builder.description = description + ": " + append;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins.copy();
builder.ctx = ctx.copy();
builder.backfills = backfills;
builder.forbids = forbids;
return builder;
}
/**
* Duplicate this resolution, with additional description text appended
*
* @param append the text to append
* @return the duplicate NOTE: An additional separator {@code ": "} is inserted
*/
public AssemblyResolvedPatterns copyAppendDescription(String append) {
return copyAppendDescriptionBuilder(append).build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> withRightBuilder(
AssemblyResolution right) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins.copy();
builder.ctx = ctx.copy();
builder.backfills = backfills;
builder.forbids = forbids;
return builder;
}
@Override
public AssemblyResolvedPatterns withRight(AssemblyResolution right) {
return withRightBuilder(right).build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> nopLeftSiblingBuilder() {
var builder = factory.newPatternsBuilder();
builder.description = "nop-left";
builder.right = this;
builder.ins = ins.copy();
builder.ctx = ctx.copy();
builder.backfills = backfills;
builder.forbids = forbids;
return builder;
}
@Override
public AssemblyResolvedPatterns nopLeftSibling() {
return nopLeftSiblingBuilder().build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> parentBuilder(String description,
int opCount) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
List<AssemblyResolution> allRight = getAllRight();
builder.children = allRight.subList(0, opCount);
builder.right = allRight.get(opCount);
builder.ins = ins;
builder.ctx = ctx;
builder.backfills = backfills;
builder.forbids = forbids;
return builder;
}
@Override
public AssemblyResolvedPatterns parent(String description, int opCount) {
return parentBuilder(description, opCount).build();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> maskOutBuilder(ContextOp cop) {
var builder = factory.newPatternsBuilder();
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins;
builder.ctx = ctx.maskOut(cop);
builder.backfills = backfills;
builder.forbids = forbids;
return builder;
}
@Override
public AssemblyResolvedPatterns maskOut(ContextOp cop) {
return maskOutBuilder(cop).build();
}
@Override
public AssemblyResolution backfill(RecursiveDescentSolver solver, Map<String, Long> vals) {
if (!hasBackfills()) {
return this;
}
AssemblyResolvedPatterns res = this;
loop: while (true) {
for (AssemblyResolvedBackfill bf : res.getBackfills()) {
AssemblyResolution ar = bf.solve(solver, vals, this);
if (ar.isError()) {
continue;
}
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) ar;
AssemblyResolvedPatterns check = res.combineLessBackfill(rc, bf);
if (check == null) {
return factory.error("Conflict: Backfill " + bf.getDescription(), res);
}
res = check;
continue loop;
}
return res;
}
}
@Override
public String lineToString() {
return dumpConstructorTree() + ":" + INS + ins + SEP + CTX + ctx + " (" + description + ")";
}
@Override
public boolean hasBackfills() {
return !backfills.isEmpty();
}
/**
* Check if this resolution includes forbidden patterns
*
* @return true if there are forbidden patterns
*/
private boolean hasForbids() {
return !forbids.isEmpty();
}
protected AbstractAssemblyResolvedPatternsBuilder<?> solveContextChangesForForbidsBuilder(
AssemblyConstructorSemantic sem, Map<String, Long> vals) {
var builder = factory.newPatternsBuilder();
builder.forbids = new HashSet<>();
for (AssemblyResolvedPatterns f : forbids) {
AssemblyResolution t = sem.solveContextChanges(f, vals);
if (!(t instanceof AssemblyResolvedPatterns rp)) {
// Can't be solved, so it can be dropped
continue;
}
builder.forbids.add(rp);
}
builder.description = description;
builder.cons = cons;
builder.children = children;
builder.right = right;
builder.ins = ins;
builder.ctx = ctx;
builder.backfills = backfills;
return builder;
}
@Override
public AssemblyResolvedPatterns solveContextChangesForForbids(
AssemblyConstructorSemantic sem, Map<String, Long> vals) {
if (!hasForbids()) {
return this;
}
return solveContextChangesForForbidsBuilder(sem, vals).build();
}
@Override
public int getInstructionLength() {
int inslen = ins.length();
for (AssemblyResolvedBackfill bf : backfills) {
inslen = Math.max(inslen, bf.getInstructionLength());
}
return inslen;
}
@Override
public int getDefinedInstructionLength() {
byte[] imsk = ins.getMask();
int i;
for (i = imsk.length - 1; i >= 0; i--) {
if (imsk[i] != 0) {
break;
}
}
return ins.getOffset() + i + 1;
}
@Override
public AssemblyPatternBlock getInstruction() {
return ins;
}
@Override
public AssemblyPatternBlock getContext() {
return ctx;
}
@Override
public MaskedLong readInstruction(int start, int len) {
return ins.readBytes(start, len);
}
@Override
public MaskedLong readContext(int start, int len) {
return ctx.readBytes(start, len);
}
@Override
public boolean isError() {
return false;
}
@Override
public boolean isBackfill() {
return false;
}
@Override
public boolean hasChildren() {
return super.hasChildren() || hasBackfills() || hasForbids();
}
@Override
protected String childrenToString(String indent) {
StringBuilder sb = new StringBuilder();
if (super.hasChildren()) {
sb.append(super.childrenToString(indent) + "\n");
}
for (AssemblyResolvedBackfill bf : backfills) {
sb.append(indent);
sb.append("backfill: " + bf + "\n");
}
for (AssemblyResolvedPatterns f : forbids) {
sb.append(indent);
sb.append("forbidden: " + f + "\n");
}
return sb.substring(0, sb.length() - 1);
}
protected static final Pattern pat = Pattern.compile("line(\\d*)");
@Override
public String dumpConstructorTree() {
StringBuilder sb = new StringBuilder();
if (cons == null) {
return null;
}
sb.append(cons.getSourceFile() + ":" + cons.getLineno());
if (children == null) {
return sb.toString();
}
List<String> subs = new ArrayList<>();
for (AssemblyResolution c : children) {
if (c instanceof AssemblyResolvedPatterns) {
AssemblyResolvedPatterns rc = (AssemblyResolvedPatterns) c;
String s = rc.dumpConstructorTree();
if (s != null) {
subs.add(s);
}
}
}
if (subs.isEmpty()) {
return sb.toString();
}
sb.append('[');
sb.append(StringUtils.join(subs, ","));
sb.append(']');
return sb.toString();
}
/**
* Count the number of bits specified in the resolution patterns
*
* <p>
* Totals the specificity of the instruction and context pattern blocks.
*
* @return the number of bits in the resulting patterns
* @see AssemblyPatternBlock#getSpecificity()
*/
public int getSpecificity() {
return ins.getSpecificity() + ctx.getSpecificity();
}
@Override
public Iterable<byte[]> possibleInsVals(AssemblyPatternBlock forCtx) {
AssemblyPatternBlock ctxCompat = ctx.combine(forCtx);
if (ctxCompat == null) {
return List.of();
}
Predicate<byte[]> removeForbidden = (byte[] val) -> {
for (AssemblyResolvedPatterns f : forbids) {
// If the forbidden length is larger than us, we can ignore it
if (f.getDefinedInstructionLength() > val.length) {
continue;
}
// Check if the context matches, if not, we can let it pass
if (null == f.getContext().combine(forCtx)) {
continue;
}
// If the context matches, now check the instruction
AssemblyPatternBlock i = f.getInstruction();
AssemblyPatternBlock vi =
AssemblyPatternBlock.fromBytes(ins.length() - val.length, val);
if (null == i.combine(vi)) {
continue;
}
return false;
}
return true;
};
return new Iterable<byte[]>() {
@Override
public Iterator<byte[]> iterator() {
return IteratorUtils.filteredIterator(ins.possibleVals().iterator(),
removeForbidden);
}
};
}
protected static int getOpIndex(String piece) {
if (piece.charAt(0) != '\n') {
return -1;
}
return piece.charAt(1) - 'A';
}
/**
* If the construct state is a {@code ^instruction} or other purely-recursive constructor, get
* its single child.
*
* @param state the parent state
* @return the child state if recursive, or null
*/
protected static ConstructState getPureRecursion(ConstructState state) {
// NB. There can be other operands, but only one can be printed
// Furthermore, nothing else can be printed, whether an operand or not
List<String> pieces = state.getConstructor().getPrintPieces();
if (pieces.size() != 1) {
return null;
}
int opIdx = getOpIndex(pieces.get(0));
if (opIdx < 0) {
return null;
}
ConstructState sub = state.getSubState(opIdx);
if (sub == null || sub.getConstructor() == null ||
sub.getConstructor().getParent() != state.getConstructor().getParent()) {
// not recursive
return null;
}
return sub;
}
@Override
public boolean equivalentConstructState(ConstructState state) {
ConstructState rec = getPureRecursion(state);
if (rec != null) {
if (state.getConstructor() == cons) {
assert children.size() == 1;
AssemblyResolvedPatterns recRes = (AssemblyResolvedPatterns) children.get(0);
return recRes.equivalentConstructState(rec);
}
return equivalentConstructState(rec);
}
if (state.getConstructor() != cons) {
return false;
}
int opCount = cons.getNumOperands();
for (int opIdx = 0; opIdx < opCount; opIdx++) {
OperandSymbol opSym = cons.getOperand(opIdx);
Set<Integer> printed =
Arrays.stream(cons.getOpsPrintOrder()).boxed().collect(Collectors.toSet());
if (!(opSym.getDefiningSymbol() instanceof SubtableSymbol)) {
AssemblyTreeResolver.DBG.println("Operand " + opSym + " is not a sub-table");
continue;
}
if (!printed.contains(opIdx)) {
AssemblyTreeResolver.DBG.println("Operand " + opSym + " is hidden");
continue;
}
AssemblyResolvedPatterns child = (AssemblyResolvedPatterns) children.get(opIdx);
ConstructState subState = state.getSubState(opIdx);
if (!child.equivalentConstructState(subState)) {
return false;
}
}
return true;
}
public Constructor getConstructor() {
return cons;
}
@Override
public Set<AssemblyResolvedBackfill> getBackfills() {
return backfills;
}
@Override
public Set<AssemblyResolvedPatterns> getForbids() {
return forbids;
}
}

View file

@ -28,7 +28,7 @@ import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseNumericToken;
* value. * value.
*/ */
public class AssemblyFixedNumericTerminal extends AssemblyNumericTerminal { public class AssemblyFixedNumericTerminal extends AssemblyNumericTerminal {
private final long val; protected final long val;
/** /**
* Construct a terminal that accepts only the given numeric value * Construct a terminal that accepts only the given numeric value
@ -65,4 +65,8 @@ public class AssemblyFixedNumericTerminal extends AssemblyNumericTerminal {
} }
return toks; return toks;
} }
public long getVal() {
return val;
}
} }

View file

@ -69,4 +69,8 @@ public class AssemblyNumericMapTerminal extends AssemblyNumericTerminal {
} }
return result; return result;
} }
public Map<Long, Integer> getMap() {
return map;
}
} }

View file

@ -289,4 +289,8 @@ public class AssemblyNumericTerminal extends AssemblyTerminal {
public int getBitSize() { public int getBitSize() {
return bitsize; return bitsize;
} }
public AddressSpace getSpace() {
return space;
}
} }

View file

@ -15,7 +15,8 @@
*/ */
package ghidra.app.plugin.assembler.sleigh.symbol; package ghidra.app.plugin.assembler.sleigh.symbol;
import java.util.*; import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.MultiValuedMap;
@ -66,4 +67,8 @@ public class AssemblyStringMapTerminal extends AssemblyTerminal {
public String toString() { public String toString() {
return "[list:" + name + "]"; return "[list:" + name + "]";
} }
public MultiValuedMap<String, Integer> getMap() {
return map;
}
} }

View file

@ -15,25 +15,29 @@
*/ */
package ghidra.app.plugin.assembler.sleigh.symbol; package ghidra.app.plugin.assembler.sleigh.symbol;
import java.util.*; import java.util.Collection;
import java.util.Collections;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar; import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseToken; import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseToken;
import ghidra.app.plugin.processors.sleigh.symbol.VarnodeSymbol;
/** /**
* A terminal that accepts only a particular string * A terminal that accepts only a particular string
*/ */
public class AssemblyStringTerminal extends AssemblyTerminal { public class AssemblyStringTerminal extends AssemblyTerminal {
protected final String str; protected final String str;
protected final VarnodeSymbol defsym;
/** /**
* Construct a terminal that accepts only the given string * Construct a terminal that accepts only the given string
* *
* @param str the string to accept * @param str the string to accept
*/ */
public AssemblyStringTerminal(String str) { public AssemblyStringTerminal(String str, VarnodeSymbol defsym) {
super("\"" + str + "\""); super("\"" + str + "\"");
this.str = str; this.str = str;
this.defsym = defsym;
} }
@Override @Override
@ -57,6 +61,18 @@ public class AssemblyStringTerminal extends AssemblyTerminal {
@Override @Override
public boolean takesOperandIndex() { public boolean takesOperandIndex() {
return defsym != null;
}
public VarnodeSymbol getDefiningSymbol() {
return defsym;
}
public String getString() {
return str;
}
public boolean isWhiteSpace() {
return false; return false;
} }
} }

View file

@ -166,9 +166,8 @@ public class AssemblyParseBranch extends AssemblyParseTreeNode
return substs.get(i); return substs.get(i);
} }
@Override
public boolean isConstructor() { public boolean isConstructor() {
return true; return prod.isConstructor();
} }
@Override @Override

View file

@ -0,0 +1,42 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.assembler.sleigh.tree;
import java.io.PrintStream;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
import ghidra.app.plugin.assembler.sleigh.symbol.AssemblySymbol;
public class AssemblyParseHiddenNode extends AssemblyParseTreeNode {
public AssemblyParseHiddenNode(AssemblyGrammar grammar) {
super(grammar);
}
@Override
public AssemblySymbol getSym() {
return null;
}
@Override
protected void print(PrintStream out, String indent) {
out.print("<hidden>");
}
@Override
public String generateString() {
return "";
}
}

View file

@ -84,9 +84,4 @@ public class AssemblyParseNumericToken extends AssemblyParseToken {
public long getNumericValue() { public long getNumericValue() {
return val; return val;
} }
@Override
public boolean isNumeric() {
return true;
}
} }

View file

@ -83,24 +83,6 @@ public abstract class AssemblyParseTreeNode {
*/ */
protected abstract void print(PrintStream out, String indent); protected abstract void print(PrintStream out, String indent);
/**
* Check if this node yields a subconstructor resolution
*
* @return true if this node yields a subconstructor resolution
*/
public boolean isConstructor() {
return false;
}
/**
* Check if this node yields a numeric value
*
* @return true if this node yields a numeric value
*/
public boolean isNumeric() {
return false;
}
/** /**
* Get the grammar used to parse the tree * Get the grammar used to parse the tree
* *

View file

@ -55,7 +55,12 @@ public class ARMAssemblyTest extends AbstractAssemblyTest {
} }
@Test @Test
public void testAssembly_T_add_w_pc_r0_r7_asr_0xf() { public void testAssemble_mov_pc_n0x0() {
assertOneCompatRestExact("mov pc,#0x0", "00:f0:a0:e3");
}
@Test
public void testAssemble_T_add_w_pc_r0_r7_asr_0xf() {
assertOneCompatRestExact("add.w pc,r0,r7, asr #0xf", "00:eb:e7:3f", THUMB, 0x0000c3b8, assertOneCompatRestExact("add.w pc,r0,r7, asr #0xf", "00:eb:e7:3f", THUMB, 0x0000c3b8,
"add.w pc,r0,r7, asr #0xf"); "add.w pc,r0,r7, asr #0xf");
} }

View file

@ -204,8 +204,8 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest {
* @param ctxstr a string describing the input context pattern * @param ctxstr a string describing the input context pattern
* @see AssemblyPatternBlock#fromString(String) * @see AssemblyPatternBlock#fromString(String)
*/ */
protected void checkAllExact(AssemblyResolutionResults rr, Collection<String> disassembly, protected void checkAllExact(AssemblyResolutionResults rr,
long addr, String ctxstr) { Collection<String> disassembly, long addr, String ctxstr) {
Address address = lang.getDefaultSpace().getAddress(addr); Address address = lang.getDefaultSpace().getAddress(addr);
final AssemblyPatternBlock ctx = (ctxstr == null ? context.getDefaultAt(address) final AssemblyPatternBlock ctx = (ctxstr == null ? context.getDefaultAt(address)
: AssemblyPatternBlock.fromString(ctxstr)).fillMask(); : AssemblyPatternBlock.fromString(ctxstr)).fillMask();
@ -221,10 +221,10 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest {
errs.add((AssemblyResolvedError) ar); errs.add((AssemblyResolvedError) ar);
continue; continue;
} }
AssemblyResolvedPatterns rcon = (AssemblyResolvedPatterns) ar; AssemblyResolvedPatterns rp = (AssemblyResolvedPatterns) ar;
try { try {
dbg.println(" " + rcon.lineToString()); dbg.println(" " + rp.lineToString());
for (byte[] ins : rcon.possibleInsVals(ctx)) { for (byte[] ins : rp.possibleInsVals(ctx)) {
dbg.println(" " + NumericUtilities.convertBytesToString(ins)); dbg.println(" " + NumericUtilities.convertBytesToString(ins));
PseudoInstruction pi = disassemble(addr, ins, ctx.getVals()); PseudoInstruction pi = disassemble(addr, ins, ctx.getVals());
String cons = dumpConstructorTree(pi); String cons = dumpConstructorTree(pi);
@ -233,7 +233,7 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest {
if (!disassembly.contains(dis.trim())) { if (!disassembly.contains(dis.trim())) {
failedOne = true; failedOne = true;
misTxtToCons.put(dis, cons); misTxtToCons.put(dis, cons);
misTxtConsToRes.put(dis + cons, rcon); misTxtConsToRes.put(dis + cons, rp);
} }
gotOne = true; gotOne = true;
} }
@ -364,8 +364,8 @@ public abstract class AbstractAssemblyTest extends AbstractGenericTest {
} }
@Override @Override
public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx)
AssemblyPatternBlock ctx) throws AssemblySemanticException { throws AssemblySemanticException {
if (checkOneCompat) { if (checkOneCompat) {
checkOneCompat(instr, rr); checkOneCompat(instr, rr);
} }

View file

@ -15,7 +15,7 @@
*/ */
package ghidra.app.plugin.assembler.sleigh; package ghidra.app.plugin.assembler.sleigh;
import static org.junit.Assert.*; import static org.junit.Assert.fail;
import java.util.*; import java.util.*;
@ -365,8 +365,8 @@ public abstract class AssemblyTestCase extends AbstractGenericTest {
} }
@Override @Override
public AssemblyResolvedPatterns select(AssemblyResolutionResults rr, public Selection select(AssemblyResolutionResults rr, AssemblyPatternBlock ctx)
AssemblyPatternBlock ctx) throws AssemblySemanticException { throws AssemblySemanticException {
if (checkOneCompat) { if (checkOneCompat) {
checkOneCompat(instr, rr); checkOneCompat(instr, rr);
} }

View file

@ -15,9 +15,13 @@
*/ */
package ghidra.app.plugin.assembler.sleigh; package ghidra.app.plugin.assembler.sleigh;
import java.math.BigInteger;
import org.junit.Test; import org.junit.Test;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
import ghidra.program.model.lang.LanguageID; import ghidra.program.model.lang.LanguageID;
import ghidra.program.model.lang.RegisterValue;
public class MIPSAssemblyTest extends AbstractAssemblyTest { public class MIPSAssemblyTest extends AbstractAssemblyTest {
@ -30,4 +34,15 @@ public class MIPSAssemblyTest extends AbstractAssemblyTest {
public void testAssemble_jal_0x00420fa0() { public void testAssemble_jal_0x00420fa0() {
assertOneCompatRestExact("jal 0x00420fa0", "0c:10:83:e8", 0x00400d4); assertOneCompatRestExact("jal 0x00420fa0", "0c:10:83:e8", 0x00400d4);
} }
@Test
public void testAssembly_restore_0x1b8_ra_s0_s1() {
RegisterValue ctxVal = new RegisterValue(lang.getContextBaseRegister());
ctxVal = ctxVal.assign(lang.getRegister("ISA_MODE"), BigInteger.ONE);
ctxVal = ctxVal.assign(lang.getRegister("PAIR_INSTRUCTION_FLAG"), BigInteger.ZERO);
ctxVal = ctxVal.assign(lang.getRegister("RELP"), BigInteger.ONE);
assertOneCompatRestExact("restore 0x1b8,ra,s0-s1", "f0:30:64:77",
AssemblyPatternBlock.fromRegisterValue(ctxVal).fillMask().toString(), 0x0040000,
"restore 0x1b8,ra,s0-s1");
}
} }

View file

@ -36,13 +36,13 @@ import ghidra.app.plugin.processors.sleigh.template.ConstructTpl;
import ghidra.app.plugin.processors.sleigh.template.HandleTpl; import ghidra.app.plugin.processors.sleigh.template.HandleTpl;
import ghidra.framework.Application; import ghidra.framework.Application;
import ghidra.framework.ApplicationConfiguration; import ghidra.framework.ApplicationConfiguration;
import ghidra.program.model.lang.*; import ghidra.program.model.lang.LanguageID;
import ghidra.program.util.DefaultLanguageService;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.xml.XmlPullParser; import ghidra.xml.XmlPullParser;
import ghidra.xml.XmlPullParserFactory; import ghidra.xml.XmlPullParserFactory;
public class SolverTest { public class SolverTest {
static final DefaultAssemblyResolutionFactory FACTORY = new DefaultAssemblyResolutionFactory();
private static final MaskedLong nil = MaskedLong.ZERO; private static final MaskedLong nil = MaskedLong.ZERO;
private static final MaskedLong unk = MaskedLong.UNKS; private static final MaskedLong unk = MaskedLong.UNKS;
@ -165,29 +165,26 @@ public class SolverTest {
@Test @Test
public void testCatOrSolver() throws SAXException, NeedsBackfillException { public void testCatOrSolver() throws SAXException, NeedsBackfillException {
XmlPullParser parser = XmlPullParserFactory.create("" + // XmlPullParser parser = XmlPullParserFactory.create("""
// <or_exp>
"<or_exp>\n" + // <lshift_exp>
" <lshift_exp>\n" + // <tokenfield bigendian='false' signbit='false' bitstart='0' bitend='3'
" <tokenfield bigendian='false' signbit='false' bitstart='0' bitend='3' bytestart='0' byteend='0' shift='0'/>\n" + // bytestart='0' byteend='0' shift='0'/>
" <intb val='4'/>\n" + // <intb val='4'/>
" </lshift_exp>\n" + // </lshift_exp>
" <tokenfield bigendian='false' signbit='false' bitstart='8' bitend='11' bytestart='1' byteend='1' shift='0'/>\n" + // <tokenfield bigendian='false' signbit='false' bitstart='8' bitend='11'
"</or_exp>\n" + // bytestart='1' byteend='1' shift='0'/>
"", "Test", null, true); </or_exp>
""",
"Test", null, true);
PatternExpression exp = PatternExpression.restoreExpression(parser, null); PatternExpression exp = PatternExpression.restoreExpression(parser, null);
RecursiveDescentSolver solver = RecursiveDescentSolver.getSolver(); RecursiveDescentSolver solver = RecursiveDescentSolver.getSolver();
AssemblyResolution res = AssemblyResolution res =
solver.solve(exp, MaskedLong.fromLong(0x78), Collections.emptyMap(), solver.solve(FACTORY, exp, MaskedLong.fromLong(0x78), Collections.emptyMap(),
AssemblyResolution.nop("NOP"), "Test"); FACTORY.nop("NOP"), "Test");
AssemblyResolution e = AssemblyResolvedPatterns.fromString("ins:X7:X8", "Test", null); AssemblyResolution e = FACTORY.fromString("ins:X7:X8", "Test", null);
assertEquals(e, res); assertEquals(e, res);
} }
private static Language getLanguage(String languageName) throws LanguageNotFoundException {
LanguageService languageService = DefaultLanguageService.getLanguageService();
return languageService.getLanguage(new LanguageID(languageName));
}
public static Constructor findConstructor(String langId, String subtableName, String patternStr) public static Constructor findConstructor(String langId, String subtableName, String patternStr)
throws Exception { throws Exception {
@ -329,4 +326,90 @@ public class SolverTest {
assertTrue(MaskedLong.fromLong(-0x800000000000000L).isInRange(0xffffffffffffffffL, true)); assertTrue(MaskedLong.fromLong(-0x800000000000000L).isInRange(0xffffffffffffffffL, true));
// NOTE: -0x8000000000000001L will wrap around and appear positive // NOTE: -0x8000000000000001L will wrap around and appear positive
} }
@Test
public void testAssemblyPatternBlockMaskOut() {
AssemblyPatternBlock base = AssemblyPatternBlock.fromString("8C:45:00:00");
AssemblyPatternBlock extraMask = AssemblyPatternBlock.fromString("XX:X5:XX:XX");
AssemblyPatternBlock expectedAnswer = AssemblyPatternBlock.fromString("8C:4X:00:00");
AssemblyPatternBlock computed = base.maskOut(extraMask);
assertEquals(expectedAnswer, computed);
base = AssemblyPatternBlock.fromString("8C:45:00:00");
extraMask = AssemblyPatternBlock.fromString("XX:X5:XX:XX");
expectedAnswer = AssemblyPatternBlock.fromString("8C:4X:00:00");
computed = base.maskOut(extraMask);
assertEquals(expectedAnswer, computed);
base = AssemblyPatternBlock.fromString("8C:45:67:89");
byte[] z = new byte[2];
z[0] = 0x44;
z[1] = 0x77;
extraMask = AssemblyPatternBlock.fromBytes(2, z);
expectedAnswer = AssemblyPatternBlock.fromString("8C:45:XX:XX");
computed = base.maskOut(extraMask);
assertEquals(expectedAnswer, computed);
extraMask = AssemblyPatternBlock.fromBytes(1, z);
expectedAnswer = AssemblyPatternBlock.fromString("8C:XX:XX:89");
computed = base.maskOut(extraMask);
assertEquals(expectedAnswer, computed);
base = AssemblyPatternBlock.fromString("01:02:03:04:05:06:07:08");
extraMask = AssemblyPatternBlock.fromBytes(1, z);
expectedAnswer = AssemblyPatternBlock.fromString("01:XX:XX:04:05:06:07:08");
computed = base.maskOut(extraMask);
assertEquals(expectedAnswer, computed);
byte[] z3 = new byte[3];
z3[0] = 0x44;
z3[1] = 0x77;
z3[2] = 0x78;
base = AssemblyPatternBlock.fromBytes(4, z3);
extraMask = AssemblyPatternBlock.fromBytes(1, z);
expectedAnswer = AssemblyPatternBlock.fromBytes(4, z3);
computed = base.maskOut(extraMask);
assertEquals(expectedAnswer, computed);
extraMask = AssemblyPatternBlock.fromBytes(4, z);
byte[] z4 = new byte[1];
z4[0] = 0x78;
expectedAnswer = AssemblyPatternBlock.fromBytes(6, z4);
computed = base.maskOut(extraMask);
assertEquals(expectedAnswer, computed);
}
@Test
public void testAssemblyPatternBlockTrim() {
AssemblyPatternBlock base = AssemblyPatternBlock.fromString("XC:45:00:0X");
AssemblyPatternBlock expectedAnswer = AssemblyPatternBlock.fromString("c4:50:00");
var computed = base.trim();
assertEquals(expectedAnswer, computed);
base = base.shift(5);
computed = base.trim();
assertEquals(expectedAnswer, computed);
base = AssemblyPatternBlock.fromString("XX:XX:00:XX:10:XX");
expectedAnswer = AssemblyPatternBlock.fromString("00:XX:10");
computed = base.trim();
assertEquals(expectedAnswer, computed);
base = base.shift(2);
expectedAnswer = AssemblyPatternBlock.fromString("00:XX:10");
computed = base.trim();
assertEquals(expectedAnswer, computed);
base = AssemblyPatternBlock.fromString("[x1xx]X");
expectedAnswer = AssemblyPatternBlock.fromString("X[xxx1]");
computed = base.trim();
assertEquals(expectedAnswer, computed);
// The "f" here has the "sign bit" set... we wan't to make sure it's treated as
// unsigned
base = AssemblyPatternBlock.fromString("F[x1xx]").shift(3);
expectedAnswer = AssemblyPatternBlock.fromString("[xx11][11x1]");
computed = base.trim();
assertEquals(expectedAnswer, computed);
}
} }

View file

@ -15,7 +15,8 @@
*/ */
package ghidra.app.plugin.assembler.sleigh.parse; package ghidra.app.plugin.assembler.sleigh.parse;
import static org.junit.Assert.*; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.PrintStream; import java.io.PrintStream;
import java.util.*; import java.util.*;
@ -25,19 +26,22 @@ import org.apache.commons.lang3.StringUtils;
import org.junit.Test; import org.junit.Test;
import ghidra.app.plugin.assembler.sleigh.grammars.*; import ghidra.app.plugin.assembler.sleigh.grammars.*;
import ghidra.app.plugin.assembler.sleigh.sem.DefaultAssemblyResolutionFactory;
import ghidra.app.plugin.assembler.sleigh.symbol.*; import ghidra.app.plugin.assembler.sleigh.symbol.*;
import ghidra.app.plugin.assembler.sleigh.tree.*; import ghidra.app.plugin.assembler.sleigh.tree.*;
import ghidra.app.plugin.assembler.sleigh.util.AsmUtil; import ghidra.app.plugin.assembler.sleigh.util.AsmUtil;
import ghidra.util.NullOutputStream; import ghidra.util.NullOutputStream;
public class ParserTest { public class ParserTest {
private static final DefaultAssemblyResolutionFactory FACTORY =
new DefaultAssemblyResolutionFactory();
private boolean tracing = false; private boolean tracing = false;
private PrintStream out = tracing ? System.out : new PrintStream(new NullOutputStream()); private PrintStream out = tracing ? System.out : new PrintStream(new NullOutputStream());
@Test @Test
public void testFirstFollow() throws Exception { public void testFirstFollow() throws Exception {
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal E = new AssemblyNonTerminal("E"); AssemblyNonTerminal E = new AssemblyNonTerminal("E");
AssemblyNonTerminal T = new AssemblyNonTerminal("T"); AssemblyNonTerminal T = new AssemblyNonTerminal("T");
AssemblyNonTerminal F = new AssemblyNonTerminal("F"); AssemblyNonTerminal F = new AssemblyNonTerminal("F");
@ -102,7 +106,7 @@ public class ParserTest {
@Test @Test
public void testLRStates() throws Exception { public void testLRStates() throws Exception {
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal Sp = new AssemblyNonTerminal("S'"); AssemblyNonTerminal Sp = new AssemblyNonTerminal("S'");
AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal S = new AssemblyNonTerminal("S");
AssemblyNonTerminal X = new AssemblyNonTerminal("X"); AssemblyNonTerminal X = new AssemblyNonTerminal("X");
@ -148,7 +152,7 @@ public class ParserTest {
public void testLALRWithEpsilon37() throws Exception { public void testLALRWithEpsilon37() throws Exception {
// This comes from page 37 of http://digital.cs.usu.edu/~allan/Compilers/Notes/LRParsing.pdf // This comes from page 37 of http://digital.cs.usu.edu/~allan/Compilers/Notes/LRParsing.pdf
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal Ep = new AssemblyNonTerminal("E'"); AssemblyNonTerminal Ep = new AssemblyNonTerminal("E'");
AssemblyNonTerminal E = new AssemblyNonTerminal("E"); AssemblyNonTerminal E = new AssemblyNonTerminal("E");
AssemblyNonTerminal T = new AssemblyNonTerminal("T"); AssemblyNonTerminal T = new AssemblyNonTerminal("T");
@ -226,7 +230,7 @@ public class ParserTest {
public void testLALRWithEpsilon33999() throws Exception { public void testLALRWithEpsilon33999() throws Exception {
// This comes from http://cs.stackexchange.com/questions/33999/lalr1-parsers-and-the-epsilon-transition // This comes from http://cs.stackexchange.com/questions/33999/lalr1-parsers-and-the-epsilon-transition
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal S = new AssemblyNonTerminal("S");
AssemblyNonTerminal A = new AssemblyNonTerminal("A"); AssemblyNonTerminal A = new AssemblyNonTerminal("A");
AssemblyNonTerminal B = new AssemblyNonTerminal("B"); AssemblyNonTerminal B = new AssemblyNonTerminal("B");
@ -283,7 +287,7 @@ public class ParserTest {
public void testLALRFromTutorial() throws Exception { public void testLALRFromTutorial() throws Exception {
// http://web.cs.dal.ca/~sjackson/lalr1.html // http://web.cs.dal.ca/~sjackson/lalr1.html
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal S = new AssemblyNonTerminal("S");
AssemblyNonTerminal N = new AssemblyNonTerminal("N"); AssemblyNonTerminal N = new AssemblyNonTerminal("N");
AssemblyNonTerminal E = new AssemblyNonTerminal("E"); AssemblyNonTerminal E = new AssemblyNonTerminal("E");
@ -378,7 +382,7 @@ public class ParserTest {
@Test @Test
public void testListsFromARM() throws Exception { public void testListsFromARM() throws Exception {
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal S = new AssemblyNonTerminal("S");
@ -480,7 +484,7 @@ public class ParserTest {
@Test @Test
public void testEndsOptionalWhitespaceEpsilon() { public void testEndsOptionalWhitespaceEpsilon() {
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal S = new AssemblyNonTerminal("S");
AssemblyNonTerminal E = new AssemblyNonTerminal("E"); AssemblyNonTerminal E = new AssemblyNonTerminal("E");
@ -505,7 +509,7 @@ public class ParserTest {
@Test @Test
public void testExpectsPastWhitespace() { public void testExpectsPastWhitespace() {
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal S = new AssemblyNonTerminal("S");
addProduction(S, g, "a", " ", "b"); addProduction(S, g, "a", " ", "b");
@ -525,7 +529,7 @@ public class ParserTest {
@Test @Test
public void testExpectsPastMissingWhitespace() { public void testExpectsPastMissingWhitespace() {
AssemblyGrammar g = new AssemblyGrammar(); AssemblyGrammar g = new AssemblyGrammar(FACTORY);
AssemblyNonTerminal S = new AssemblyNonTerminal("S"); AssemblyNonTerminal S = new AssemblyNonTerminal("S");
addProduction(S, g, "a", " ", "b"); addProduction(S, g, "a", " ", "b");
@ -565,7 +569,7 @@ public class ParserTest {
rhs.addWS(); rhs.addWS();
} }
else { else {
rhs.addSymbol(new AssemblyStringTerminal((String) o)); rhs.addSymbol(new AssemblyStringTerminal((String) o, null));
} }
} }
else { else {

View file

@ -0,0 +1,30 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.assembler.sleigh.sem;
import static org.junit.Assert.assertArrayEquals;
import org.junit.Test;
public class AssemblyPatternBlockTest {
@Test
public void testBitShiftRightByteArray() {
assertArrayEquals(new byte[] { 1, 1, 1, 1 },
AssemblyPatternBlock.bitShiftRightByteArray(new byte[] { 2, 2, 2, 2 }, 1));
assertArrayEquals(new byte[] { 1, 7, (byte) 0x81, 1 },
AssemblyPatternBlock.bitShiftRightByteArray(new byte[] { 2, 0xf, 2, 2 }, 1));
}
}

View file

@ -38,4 +38,14 @@ public class x86AssemblyTest extends AbstractAssemblyTest {
assertOneCompatRestExact("ADD ECX,dword ptr [-0x8 + EDX]", "03:4a:f8"); assertOneCompatRestExact("ADD ECX,dword ptr [-0x8 + EDX]", "03:4a:f8");
} }
} }
@Test
public void testAssemble_CALL_0x00401234() {
assertOneCompatRestExact("CALL 0x00401234", "e8:2f:12:40:c0");
}
@Test
public void testAssemble_CALL_0x00401234_at0() {
assertOneCompatRestExact("CALL 0x00401234", "e8:2f:12:40:00", 0);
}
} }