Merge remote-tracking branch

'origin/GP-5288_Dan_PR-7195_plucia-mitre_patch-4' into Ghidra_11.3
(Closes #7195)
This commit is contained in:
Ryan Kurtz 2025-01-22 13:15:43 -05:00
commit d27a9c0bd0
10 changed files with 538 additions and 47 deletions

View file

@ -4,9 +4,9 @@
* 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.
@ -15,14 +15,16 @@
*/
package ghidra.asm.wild;
import java.util.Set;
import ghidra.app.plugin.assembler.AssemblySelector;
import ghidra.app.plugin.assembler.sleigh.AbstractSleighAssembler;
import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParseResult;
import ghidra.app.plugin.assembler.sleigh.parse.AssemblyParser;
import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.asm.wild.sem.WildAssemblyResolvedPatterns;
import ghidra.asm.wild.sem.WildAssemblyTreeResolver;
import ghidra.asm.wild.sem.*;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
@ -30,22 +32,27 @@ import ghidra.program.model.listing.Program;
* An assembler implementation that allows for wildcard operands
*
* <p>
* Construct these using {@link WildSleighAssemblerBuilder}.
* Construct these using {@link WildSleighAssemblerBuilder}.
*/
public class WildSleighAssembler extends AbstractSleighAssembler<WildAssemblyResolvedPatterns> {
protected final Set<AssemblyPatternBlock> inputContexts;
protected WildSleighAssembler(
AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> factory,
AssemblySelector selector, SleighLanguage lang, AssemblyParser parser,
AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) {
AssemblyDefaultContext defaultContext, Set<AssemblyPatternBlock> inputContexts,
AssemblyContextGraph ctxGraph) {
super(factory, selector, lang, parser, defaultContext, ctxGraph);
this.inputContexts = inputContexts;
}
protected WildSleighAssembler(
AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> factory,
AssemblySelector selector, Program program, AssemblyParser parser,
AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) {
AssemblyDefaultContext defaultContext, Set<AssemblyPatternBlock> inputContexts,
AssemblyContextGraph ctxGraph) {
super(factory, selector, program, parser, defaultContext, ctxGraph);
this.inputContexts = inputContexts;
}
@Override
@ -53,4 +60,33 @@ public class WildSleighAssembler extends AbstractSleighAssembler<WildAssemblyRes
AssemblyPatternBlock ctx) {
return new WildAssemblyTreeResolver(factory, lang, at, tree, ctx, ctxGraph);
}
@Override
public AssemblyResolutionResults resolveTree(
AssemblyParseResult parse, Address at, AssemblyPatternBlock ctx) {
AssemblyResolutionResults allResults = new AssemblyResolutionResults();
if (inputContexts.isEmpty()) {
absorbWithContext(allResults, super.resolveTree(parse, at, ctx), ctx);
return allResults;
}
for (AssemblyPatternBlock inputCtx : inputContexts) {
AssemblyPatternBlock combinedCtx = inputCtx.assign(ctx);
absorbWithContext(allResults, super.resolveTree(parse, at, combinedCtx), combinedCtx);
}
return allResults;
}
protected static void absorbWithContext(AssemblyResolutionResults allResults,
AssemblyResolutionResults results, AssemblyPatternBlock ctx) {
// Unspecified context bits are destroyed during assembly; restore them
for (AssemblyResolution res : results) {
allResults.add(switch (res) {
case DefaultWildAssemblyResolvedPatterns rp -> rp.withContext(ctx);
default -> res;
});
}
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -22,11 +22,11 @@ import ghidra.app.plugin.assembler.sleigh.AbstractSleighAssemblerBuilder;
import ghidra.app.plugin.assembler.sleigh.SleighAssemblerBuilder;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblyGrammar;
import ghidra.app.plugin.assembler.sleigh.grammars.AssemblySentential;
import ghidra.app.plugin.assembler.sleigh.sem.AbstractAssemblyResolutionFactory;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyResolvedBackfill;
import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.symbol.*;
import ghidra.app.plugin.processors.sleigh.Constructor;
import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.app.plugin.assembler.sleigh.util.DbgTimer.DbgCtx;
import ghidra.app.plugin.languages.sleigh.InputContextScraper;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern;
import ghidra.asm.wild.grammars.WildAssemblyProduction;
import ghidra.asm.wild.sem.WildAssemblyResolutionFactory;
@ -49,6 +49,7 @@ public class WildSleighAssemblerBuilder
extends AbstractSleighAssemblerBuilder<WildAssemblyResolvedPatterns, WildSleighAssembler> {
protected final Map<AssemblySymbol, AssemblyNonTerminal> wildNTs = new HashMap<>();
protected Set<AssemblyPatternBlock> inputContexts;
/**
* Construct a builder for the given language
@ -64,6 +65,19 @@ public class WildSleighAssemblerBuilder
super(lang);
}
@Override
protected void generateAssembler() throws SleighException {
super.generateAssembler();
buildInputContexts();
}
protected void buildInputContexts() {
try (DbgCtx dc = dbg.start("Building input contexts")) {
InputContextScraper scraper = new InputContextScraper(lang);
this.inputContexts = scraper.scrapeInputContexts();
}
}
@Override
protected AbstractAssemblyResolutionFactory< //
WildAssemblyResolvedPatterns, AssemblyResolvedBackfill> newResolutionFactory() {
@ -139,12 +153,13 @@ public class WildSleighAssemblerBuilder
@Override
protected WildSleighAssembler newAssembler(AssemblySelector selector) {
return new WildSleighAssembler(factory, selector, lang, parser, defaultContext, ctxGraph);
return new WildSleighAssembler(factory, selector, lang, parser, defaultContext,
inputContexts, ctxGraph);
}
@Override
protected WildSleighAssembler newAssembler(AssemblySelector selector, Program program) {
return new WildSleighAssembler(factory, selector, program, parser, defaultContext,
ctxGraph);
inputContexts, ctxGraph);
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -240,4 +240,11 @@ public class DefaultWildAssemblyResolvedPatterns extends DefaultAssemblyResolved
builder.opInfo = opInfo;
return builder;
}
@Override
protected WildAssemblyResolvedPatternsBuilder withContextBuilder(AssemblyPatternBlock ctx) {
var builder = cast(super.withContextBuilder(ctx));
builder.opInfo = opInfo;
return builder;
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -16,6 +16,7 @@
package ghidra.asm.wild;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
import java.util.*;
@ -73,7 +74,10 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
}
ProgramBuilder armProgramBuilder = new ProgramBuilder("arm_le_test", "ARM:LE:32:v8");
armProgramBuilder.setBytes(String.format("0x%08X", 0x0),
"00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 fb ff ff eb 00 00 a0 e1");
"00 00 a0 e1 00 00 a0 e1 00 00 a0 e1 fb ff ff eb 00 00 a0 e1 01 10 81 e0 49 18");
// This line sets the binary at addresses 0x18-0x1A to be ARM Thumb
armProgramBuilder.setRegisterValue("TMode", "0x18", "0x1A", 1);
armProgramBuilder.disassemble("0x00000000", 0x1A);
Program armProgram = armProgramBuilder.getProgram();
arm = (SleighLanguage) armProgram.getLanguage();
WildSleighAssemblerBuilder builderArm = new WildSleighAssemblerBuilder(arm);
@ -90,12 +94,19 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
// 0x00000004: nop
// 0x00000008: restore 0x1b8,ra,s0-s1
// 0x0000000c: nop
// 0x00000010: restore 0x38,ra,s0-s1
mipsProgramBuilder.setBytes("0x00000000",
"0c 00 00 08 00 00 00 00 f0 30 64 77 00 00 00 00");
// This line sets the binary at addresses 0x8-0xc to be MIPS 16 (e.g. the
"0c 00 00 08 00 00 00 00 f0 30 64 77 00 00 00 00 64 77");
// This line sets the binary at addresses 0x8-0x12 to be MIPS 16 (e.g. the
// restore instruction above)
mipsProgramBuilder.setRegisterValue("ISA_MODE", "0x8", "0xc", 1);
mipsProgramBuilder.disassemble("0x00000000", 0x10);
mipsProgramBuilder.setRegisterValue("ISA_MODE", "0x8", "0x12", 1);
// We're cheating (slightly), since a transient context variable should never be set globally
// Disables ^instruction parse phase responsible for 32-bit extensions
mipsProgramBuilder.setRegisterValue("ext_done", "0x10", "0x12", 1);
mipsProgramBuilder.disassemble("0x00000000", 0x12);
// Sets the binary at address 0x100-0x10c to be MIPS (not MIPS16)
mipsProgramBuilder.setRegisterValue("ISA_MODE", "0x100", "0x10c", 0);
var mipsProgram = mipsProgramBuilder.getProgram();
mips = (SleighLanguage) mipsProgram.getLanguage();
WildSleighAssemblerBuilder mipsBuilder = new WildSleighAssemblerBuilder(mips);
@ -423,8 +434,8 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
dumpResults(results);
allValidEncodings.addAll(getInstructionValuesHex(results));
}
assertTrue("Expect to have one valid encoding", allValidEncodings.size() == 1);
// In this case, wildcard assembler now returns identical encodings with different contexts
assertTrue("Expect to have at least one valid encoding", allValidEncodings.size() >= 1);
assertTrue("Expect to have 02:1c:41:e2 as an encoding",
allValidEncodings.contains("02:1c:41:e2"));
}
@ -449,14 +460,86 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
}
@Test
public void testRestore_mips() throws Exception {
public void testAdd_arm() throws Exception {
arm();
Collection<AssemblyParseResult> parses = asmArm.parseLine("add r1,r1,r1");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
// Based on the context, we should only get the 32-bit encoding(s)
Address addr14 = arm.getAddressFactory().getDefaultAddressSpace().getAddress(0x14);
for (AssemblyParseResult r : allResults) {
AssemblyResolutionResults results = asmArm.resolveTree(r, addr14);
dumpResults(results);
allValidEncodings.addAll(getInstructionValuesHex(results));
}
assertThat(allValidEncodings, hasItem("01:10:81:e0"));
assertThat(allValidEncodings, not(hasItem("49:18")));
}
@Test
public void testAdd_armThumb() throws Exception {
arm();
Collection<AssemblyParseResult> parses = asmArm.parseLine("add r1,r1,r1");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
// Based on the context, we should only get the 16-bit encoding(s)
Address addr18 = arm.getAddressFactory().getDefaultAddressSpace().getAddress(0x18);
for (AssemblyParseResult r : allResults) {
AssemblyResolutionResults results = asmArm.resolveTree(r, addr18);
dumpResults(results);
allValidEncodings.addAll(getInstructionValuesHex(results));
}
assertThat(allValidEncodings, hasItem("49:18"));
assertThat(allValidEncodings, not(hasItem("01:10:81:e0")));
}
@Test
public void testAdd_armAll() throws Exception {
arm();
Collection<AssemblyParseResult> parses = asmArm.parseLine("add r1,r1,r1");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
// Based on the context, we should get the 32-bit and 16-bit encodings
Address addr14 = arm.getAddressFactory().getDefaultAddressSpace().getAddress(0x14);
AssemblyPatternBlock unspecifiedCtx =
AssemblyPatternBlock.fromLength(arm.getContextBaseRegister().getNumBytes());
for (AssemblyParseResult r : allResults) {
AssemblyResolutionResults results = asmArm.resolveTree(r, addr14, unspecifiedCtx);
dumpResults(results);
allValidEncodings.addAll(getInstructionValuesHex(results));
}
assertThat(allValidEncodings, hasItem("01:10:81:e0"));
assertThat(allValidEncodings, hasItem("49:18"));
}
@Test
public void testRestoreExtended_mips16() throws Exception {
mips();
Collection<AssemblyParseResult> parses = asmMips.parseLine("restore 0x1b8,ra,s0-s1");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new ArrayList<String>();
var allValidEncodings = new HashSet<String>();
// The restore instruction is only valid when ISA_MODE is equal to one (MIPS16)
// Based on the operands, we should only get the 32-bit encodings
Address addr8 = mips.getAddressFactory().getDefaultAddressSpace().getAddress(8);
for (AssemblyParseResult r : allResults) {
@ -466,6 +549,56 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
}
assertThat(allValidEncodings, hasItem("f0:30:64:77"));
assertThat(allValidEncodings, not(hasItem("64:77")));
}
@Test
public void testRestore_mips16() throws Exception {
mips();
Collection<AssemblyParseResult> parses = asmMips.parseLine("restore 0x38,ra,s0-s1");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new HashSet<String>();
// The restore instruction is only valid when ISA_MODE is equal to one (MIPS16)
// Notice ext_done is also set to one at the address we provide
// Based on the operands and context, we should only get the 16-bit encodings
Address addr10 = mips.getAddressFactory().getDefaultAddressSpace().getAddress(0x10);
for (AssemblyParseResult r : allResults) {
AssemblyResolutionResults results = asmMips.resolveTree(r, addr10);
dumpResults(results);
allValidEncodings.addAll(getInstructionValuesHex(results));
}
assertThat(allValidEncodings, hasItem("64:77"));
assertThat(allValidEncodings, not(hasItem("f0:0c:64:77")));
}
@Test
public void testRestoreAll_mips16() throws Exception {
mips();
Collection<AssemblyParseResult> parses = asmMips.parseLine("restore 0x38,ra,s0-s1");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new HashSet<String>();
// The restore instruction is only valid when ISA_MODE is equal to one (MIPS16)
// Based on the operands, we should get 32-bit and 16-bit encodings
Address addr8 = mips.getAddressFactory().getDefaultAddressSpace().getAddress(8);
for (AssemblyParseResult r : allResults) {
AssemblyResolutionResults results = asmMips.resolveTree(r, addr8);
dumpResults(results);
allValidEncodings.addAll(getInstructionValuesHex(results));
}
assertThat(allValidEncodings, hasItem("f0:0c:64:77"));
assertThat(allValidEncodings, hasItem("64:77"));
}
@Test
@ -484,12 +617,18 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
allValidResults.addAll(getValidResults(results));
}
// I expect at least an encoding like 0xf0306477 (see "testRestore_mips" test)
// I expect at least an encoding like 0x6477 (see "testRestoreAll_mips16" test)
assertFalse(allValidResults.isEmpty());
}
/**
* Tests assembling a MIPS 'lw' instruction in MIPS (not MIPS16)
*
* This test is similar to {@link #testLw_mips16_32()} except it DOES constrain context such
* that we do NOT get MIPS16 encodings.
*/
@Test
public void testLw_mips() throws Exception {
public void testLw_mips32() throws Exception {
mips();
Collection<AssemblyParseResult> parses = asmMips.parseLine("lw `Q1`,0x0(a0)");
AssemblyParseResult[] allResults = parses.stream()
@ -519,6 +658,53 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
assertEquals(32, allValidEncodings.size());
}
/**
* Tests assembling a MIPS 'lw' instruction in MIPS and MIPS16
*
* This test is similar to {@link #testLw_mips32()} except the address we assemble at has a
* non-constrained context so we get both MIPS and MIPS16 encodings.
*/
@Test
public void testLw_mips16_32() throws Exception {
mips();
Collection<AssemblyParseResult> parses = asmMips.parseLine("lw `Q1`,0x0(a0)");
AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new);
var allValidEncodings = new HashSet<String>();
// Note here, be sure to go past both the mips16 code at the start of our fake
// program and the mips32 at 0x100-0x10c
Address addr0 = mips.getAddressFactory().getDefaultAddressSpace().getAddress(0x110);
for (AssemblyParseResult r : allResults) {
AssemblyResolutionResults results = asmMips.resolveTree(r, addr0);
dumpResults(results);
allValidEncodings.addAll(getInstructionValuesHex(results));
}
// Build all 32 mips32 encodings (one per target register, Q1) and verify they're in
// the results
byte[] expected = NumericUtilities.convertStringToBytes("8c800000");
for (var i = 1; i < 32; i++) {
expected[1] = (byte) (0x80 + i);
String expectedHex = NumericUtilities.convertBytesToString(expected, ":");
assertTrue("Expected to have " + expectedHex + " as an encoding",
allValidEncodings.contains(expectedHex));
}
// Build all 8 mips16 encodings (one per target register, Q1) and verify they're in
// the results
expected = NumericUtilities.convertStringToBytes("9c00");
for (var i = 1; i < 8; i++) {
expected[1] = (byte) (0x20 * i);
String expectedHex = NumericUtilities.convertBytesToString(expected, ":");
assertTrue("Expected to have " + expectedHex + " as an encoding",
allValidEncodings.contains(expectedHex));
}
assertEquals(48, allValidEncodings.size());
}
@Test
public void testCall_x86() throws Exception {
x86();

View file

@ -4,9 +4,9 @@
* 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.
@ -86,10 +86,6 @@ public abstract class AbstractSleighAssemblerBuilder< //
* @throws SleighException if there's an issue accessing the language
*/
protected void generateAssembler() throws SleighException {
if (generated) {
return;
}
generated = true;
try {
buildGrammar();
grammar.verify();
@ -106,15 +102,23 @@ public abstract class AbstractSleighAssemblerBuilder< //
}
}
private void checkGenerateAssembler() throws SleighException {
if (generated) {
return;
}
generated = true;
generateAssembler();
}
@Override
public A getAssembler(AssemblySelector selector) {
generateAssembler();
checkGenerateAssembler();
return newAssembler(selector);
}
@Override
public A getAssembler(AssemblySelector selector, Program program) {
generateAssembler();
checkGenerateAssembler();
return newAssembler(selector, program);
}

View file

@ -4,9 +4,9 @@
* 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.
@ -17,12 +17,14 @@ package ghidra.app.plugin.assembler.sleigh.sem;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.Arrays;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
import ghidra.app.plugin.assembler.sleigh.expr.MaskedLong;
import ghidra.app.plugin.assembler.sleigh.expr.SolverException;
import ghidra.app.plugin.assembler.sleigh.util.AsmUtil;
import ghidra.app.plugin.processors.sleigh.ContextCommit;
import ghidra.app.plugin.processors.sleigh.ContextOp;
import ghidra.app.plugin.processors.sleigh.expression.ContextField;
import ghidra.app.plugin.processors.sleigh.expression.TokenField;
@ -403,6 +405,60 @@ public class AssemblyPatternBlock implements Comparable<AssemblyPatternBlock> {
return new AssemblyPatternBlock(newOffset, newMask, newVals);
}
/**
* Combine this pattern block with another given block
*
* <p>
* The two blocks are combined regardless if their corresponding defined bits agree. When blocks
* are combined, their bytes are aligned according to their shifts, and the defined bits are
* taken from either block. If neither block defines a bit (i.e., the mask bit at that position
* is 0 for both input blocks), then the output has an undefined bit in the corresponding
* position. If both blocks define the bit, but they have opposite values, then the value from
* <code>that</code> takes precedence.
*
* @see RegisterValue#combineValues(RegisterValue)
*
* @param that the other block
* @return the new combined block
*/
public AssemblyPatternBlock assign(AssemblyPatternBlock that) {
int newOffset = Math.min(this.offset, that.offset);
int bufLen = Math.max(this.length(), that.length()) - newOffset;
byte[] newMask = new byte[bufLen];
byte[] newVals = new byte[bufLen];
int diff = this.offset - newOffset;
for (int i = 0; i < this.mask.length; i++) {
newMask[diff + i] = this.mask[i];
newVals[diff + i] = this.vals[i];
}
diff = that.offset - newOffset;
for (int i = 0; i < that.mask.length; i++) {
byte mask = that.mask[i];
byte clearMask = (byte) ~mask;
newMask[diff + i] |= mask;
newVals[diff + i] = (byte) ((that.vals[i] & mask) | (newVals[diff + i] & clearMask));
}
return new AssemblyPatternBlock(newOffset, newMask, newVals);
}
/**
* Invert the mask bits of this pattern block
*
* @return a copy of this pattern block with mask bits inverted
*/
public AssemblyPatternBlock invertMask() {
int maskLen = this.mask.length;
byte[] newMask = new byte[maskLen];
for (int i = 0; i < maskLen; i++) {
newMask[i] = (byte) ~this.mask[i];
}
return new AssemblyPatternBlock(this.offset, newMask, this.vals);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
@ -575,6 +631,30 @@ public class AssemblyPatternBlock implements Comparable<AssemblyPatternBlock> {
return MaskedLong.fromMaskAndValue(rmsk >>> cop.getShift(), rval >>> cop.getShift());
}
/**
* Write mask bits from context commit to mask array of block
*
* @implNote This is used when scraping for valid input contexts to determine which context variables
* are passed to the <code>globalset</code> directive.
*
* @param cc the context commit
* @return the result
*/
public AssemblyPatternBlock writeContextCommitMask(ContextCommit cc) {
byte[] newMask = Arrays.copyOf(this.mask, this.mask.length);
int idx = cc.getWordIndex();
int imsk = cc.getMask();
for (int i = 3; i >= 0; i--) {
int index = idx * 4 + i - this.offset;
if (index < newMask.length && index >= 0) {
newMask[index] |= imsk;
}
imsk >>= 8;
}
return new AssemblyPatternBlock(this.offset, newMask, this.vals);
}
/**
* Set all bits read by a given context operation to unknown
*

View file

@ -4,9 +4,9 @@
* 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.
@ -37,6 +37,14 @@ public interface AssemblyResolvedPatterns extends AssemblyResolution {
*/
AssemblyPatternBlock getContext();
/**
* Create a copy of this resolution with a new context
*
* @param ctx the new context
* @return the copy
*/
AssemblyResolvedPatterns withContext(AssemblyPatternBlock ctx);
/**
* Get the length of the instruction encoding
*

View file

@ -4,9 +4,9 @@
* 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.
@ -560,6 +560,25 @@ public class DefaultAssemblyResolvedPatterns extends AbstractAssemblyResolution
return ctx;
}
protected AbstractAssemblyResolvedPatternsBuilder<?> withContextBuilder(
AssemblyPatternBlock ctx) {
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 withContext(AssemblyPatternBlock ctx) {
return withContextBuilder(ctx).build();
}
@Override
public MaskedLong readInstruction(int start, int len) {
return ins.readBytes(start, len);

View file

@ -0,0 +1,129 @@
/* ###
* 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.languages.sleigh;
import java.util.*;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyDefaultContext;
import ghidra.app.plugin.assembler.sleigh.sem.AssemblyPatternBlock;
import ghidra.app.plugin.processors.sleigh.*;
import ghidra.app.plugin.processors.sleigh.pattern.DisjointPattern;
import ghidra.app.plugin.processors.sleigh.symbol.SubtableSymbol;
/**
* A class for scraping input contexts from a SLEIGH language to get all of the valid input contexts
* that affect constructor selection
*
*/
public class InputContextScraper {
private final SleighLanguage language;
public InputContextScraper(SleighLanguage language) {
this.language = language;
}
/**
* Get set of all valid input contexts that affect constructor selection.
*
* <ol>
* <li>Start with mask of the language's default context
* <li>Scrape language for <code>globalset</code> context variables and OR their masks into our
* mask
* <li>Flip bits of our mask to get mask of context variables not used as input
* (local/transient)
* <li>Check constructor constraints and use mask to get values of relevant input context
* variables
* </ol>
*/
public Set<AssemblyPatternBlock> scrapeInputContexts() {
// We don't care about the actual default values, just if a context variable HAS a default
// value. It's possible for a local context variable to be set in the default context, but
// doing so is questionable. It could be an input context variable in that case, so to
// account for it, we start with the default context mask. Doing so ensures those variables
// are included
AssemblyPatternBlock defaultCtx = new AssemblyDefaultContext(language).getDefault();
// Erase the values for posterity; we don't care about them at this point
Arrays.fill(defaultCtx.getVals(), (byte) 0);
GlobalSetScraper globalSetScraper = new GlobalSetScraper(defaultCtx);
SleighLanguages.traverseConstructors(language, globalSetScraper);
AssemblyPatternBlock nonInputCtxMask = globalSetScraper.getContextMask().invertMask();
ConstraintScraper constraintScraper =
new ConstraintScraper(nonInputCtxMask, language.getContextBaseRegister().getNumBytes());
SleighLanguages.traverseConstructors(language, constraintScraper);
return constraintScraper.getInputContexts();
}
private static class GlobalSetScraper implements ConstructorEntryVisitor {
private AssemblyPatternBlock contextMask;
GlobalSetScraper(AssemblyPatternBlock contextMask) {
this.contextMask = contextMask;
}
public AssemblyPatternBlock getContextMask() {
return contextMask;
}
@Override
public int visit(SubtableSymbol subtable, DisjointPattern pattern, Constructor cons) {
for (ContextChange chg : cons.getContextChanges()) {
if (chg instanceof ContextCommit cc) {
contextMask = contextMask.writeContextCommitMask(cc);
}
}
return CONTINUE;
}
}
private static class ConstraintScraper implements ConstructorEntryVisitor {
private final AssemblyPatternBlock nonInputMask;
private final AssemblyPatternBlock blankContext;
private final Set<AssemblyPatternBlock> inputContexts;
ConstraintScraper(AssemblyPatternBlock mask, int contextRegLen) {
nonInputMask = mask;
blankContext = AssemblyPatternBlock.fromLength(contextRegLen);
inputContexts = new HashSet<>();
}
public Set<AssemblyPatternBlock> getInputContexts() {
return inputContexts;
}
@Override
public int visit(SubtableSymbol subtable, DisjointPattern pattern, Constructor cons) {
AssemblyPatternBlock contextConstraint =
AssemblyPatternBlock.fromPattern(pattern, pattern.getLength(true), true);
if (contextConstraint.getMask().length > 0) {
// Combine constraint with blank context to ensure generated context has no shifts
AssemblyPatternBlock inputCtx =
blankContext.combine(contextConstraint).maskOut(nonInputMask);
// Filter out entirely undefined context
if (inputCtx.getSpecificity() > 0) {
inputContexts.add(inputCtx);
}
}
return CONTINUE;
}
}
}

View file

@ -4,9 +4,9 @@
* 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.
@ -51,4 +51,11 @@ public class ContextCommit implements ContextChange {
decoder.closeElement(el);
}
public int getWordIndex() {
return num;
}
public int getMask() {
return mask;
}
}