Merge remote-tracking branch 'origin/Ghidra_11.3'

This commit is contained in:
Ryan Kurtz 2025-01-22 13:21:54 -05:00
commit c49f60366b
16 changed files with 636 additions and 61 deletions

View file

@ -69,7 +69,8 @@ Ghidra, as well as a Gradle task to export the module as a distributable Ghidra
The "_Edit Script with Visual Studio Code_" button in the Script Manager enables quick editing and The "_Edit Script with Visual Studio Code_" button in the Script Manager enables quick editing and
debugging of the selected script in a Visual Studio Code workspace that is automatically created debugging of the selected script in a Visual Studio Code workspace that is automatically created
behind the scenes in Ghidra's user settings directory. This provides a much snappier and modern behind the scenes in Ghidra's user settings directory. This provides a much snappier and modern
alternative to Eclipse, while maintaining all of the core fuctionality you would expect from an IDE (auto complete, hover, navigation, etc). alternative to Eclipse, while maintaining all of the core fuctionality you would expect from an IDE
(auto complete, hover, navigation, etc).
Ghidra will do its best to automatically locate your Visual Studio Code installation, but if cannot Ghidra will do its best to automatically locate your Visual Studio Code installation, but if cannot
find it, it can be set via the Front-End GUI at _Edit -> Tool Options -> Visual Studio Code find it, it can be set via the Front-End GUI at _Edit -> Tool Options -> Visual Studio Code
@ -99,10 +100,11 @@ Source information can be viewed in the _"Source Map"_ Listing Field or the
_Window -> Source Files and Transforms_. _Window -> Source Files and Transforms_.
The scripts `OpenSourceFileAtLineInEclipseScript.java` and `OpenSourceFileAtLineinVSCodeScript.java` The scripts `OpenSourceFileAtLineInEclipseScript.java` and `OpenSourceFileAtLineinVSCodeScript.java`
open a source file at the appropriate line in Eclipse or Visual Studio Code when run on an address provide proof-of-concept IDE integration. These scripts open a source file at the appropriate line
in Ghidra with source file information (consider keybinding your preferred script). The in Eclipse or Visual Studio Code when run on an address in Ghidra with source file information
SourceFilesTablePlugin can be used to modify the source file paths stored in the SourceFileManager (consider keybinding your preferred script). The SourceFilesTablePlugin can be used to modify the
before sending them to Eclipse or Visual Studio Code. source file paths stored in the SourceFileManager before sending them to Eclipse or Visual Studio
Code.
## Function Graph ## Function Graph
The Function Graph has had a number of improvements: The Function Graph has had a number of improvements:

View file

@ -24,8 +24,7 @@ import ghidra.app.util.importer.MessageLog;
import ghidra.framework.store.LockException; import ghidra.framework.store.LockException;
import ghidra.program.database.sourcemap.SourceFile; import ghidra.program.database.sourcemap.SourceFile;
import ghidra.program.database.sourcemap.SourceFileIdType; import ghidra.program.database.sourcemap.SourceFileIdType;
import ghidra.program.model.address.Address; import ghidra.program.model.address.*;
import ghidra.program.model.address.AddressOverflowException;
import ghidra.program.model.listing.*; import ghidra.program.model.listing.*;
import ghidra.program.model.sourcemap.SourceFileManager; import ghidra.program.model.sourcemap.SourceFileManager;
import ghidra.util.Msg; import ghidra.util.Msg;
@ -387,6 +386,12 @@ public class PdbSourceLinesApplicator {
//============================================================================================== //==============================================================================================
private void applyRecord(SourceFile sourceFile, Address address, int start, int length) { private void applyRecord(SourceFile sourceFile, Address address, int start, int length) {
// Throw out values that do not make sense
if (!address.isMemoryAddress() || start < 0 || length < 0) {
log.appendMsg("PDB", "Invalid source map info: %s, %d, %s, %d"
.formatted(sourceFile.getPath(), start, address.toString(), length));
return;
}
// Need to use getCodeUnitContaining(address) instead of getCodeUnitAt(address) because // Need to use getCodeUnitContaining(address) instead of getCodeUnitAt(address) because
// there is a situation where the PDB associates a line number with the base part of an // there is a situation where the PDB associates a line number with the base part of an
// instructions instead of the prefix part, such as with MSFT tool-chain emits a // instructions instead of the prefix part, such as with MSFT tool-chain emits a
@ -414,7 +419,12 @@ public class PdbSourceLinesApplicator {
catch (IllegalArgumentException e) { catch (IllegalArgumentException e) {
// thrown by SourceFileManager.addSourceMapEntry if the new entry conflicts // thrown by SourceFileManager.addSourceMapEntry if the new entry conflicts
// with an existing entry or if sourceFile is not associated with manager // with an existing entry or if sourceFile is not associated with manager
log.appendMsg("PDB", e.getMessage()); log.appendMsg("PDB", "IllegalArgumentException for source map info: %s, %d, %s, %d"
.formatted(sourceFile.getPath(), start, address.toString(), length));
}
catch (AddressOutOfBoundsException e) {
log.appendMsg("PDB", "AddressOutOfBoundsException for source map info: %s, %d, %s, %d"
.formatted(sourceFile.getPath(), start, address.toString(), length));
} }
} }

View file

@ -15,14 +15,16 @@
*/ */
package ghidra.asm.wild; package ghidra.asm.wild;
import java.util.Set;
import ghidra.app.plugin.assembler.AssemblySelector; import ghidra.app.plugin.assembler.AssemblySelector;
import ghidra.app.plugin.assembler.sleigh.AbstractSleighAssembler; 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.parse.AssemblyParser;
import ghidra.app.plugin.assembler.sleigh.sem.*; import ghidra.app.plugin.assembler.sleigh.sem.*;
import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch; import ghidra.app.plugin.assembler.sleigh.tree.AssemblyParseBranch;
import ghidra.app.plugin.processors.sleigh.SleighLanguage; import ghidra.app.plugin.processors.sleigh.SleighLanguage;
import ghidra.asm.wild.sem.WildAssemblyResolvedPatterns; import ghidra.asm.wild.sem.*;
import ghidra.asm.wild.sem.WildAssemblyTreeResolver;
import ghidra.program.model.address.Address; import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program; import ghidra.program.model.listing.Program;
@ -33,19 +35,24 @@ import ghidra.program.model.listing.Program;
* Construct these using {@link WildSleighAssemblerBuilder}. * Construct these using {@link WildSleighAssemblerBuilder}.
*/ */
public class WildSleighAssembler extends AbstractSleighAssembler<WildAssemblyResolvedPatterns> { public class WildSleighAssembler extends AbstractSleighAssembler<WildAssemblyResolvedPatterns> {
protected final Set<AssemblyPatternBlock> inputContexts;
protected WildSleighAssembler( protected WildSleighAssembler(
AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> factory, AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> factory,
AssemblySelector selector, SleighLanguage lang, AssemblyParser parser, AssemblySelector selector, SleighLanguage lang, AssemblyParser parser,
AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) { AssemblyDefaultContext defaultContext, Set<AssemblyPatternBlock> inputContexts,
AssemblyContextGraph ctxGraph) {
super(factory, selector, lang, parser, defaultContext, ctxGraph); super(factory, selector, lang, parser, defaultContext, ctxGraph);
this.inputContexts = inputContexts;
} }
protected WildSleighAssembler( protected WildSleighAssembler(
AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> factory, AbstractAssemblyResolutionFactory<WildAssemblyResolvedPatterns, ?> factory,
AssemblySelector selector, Program program, AssemblyParser parser, AssemblySelector selector, Program program, AssemblyParser parser,
AssemblyDefaultContext defaultContext, AssemblyContextGraph ctxGraph) { AssemblyDefaultContext defaultContext, Set<AssemblyPatternBlock> inputContexts,
AssemblyContextGraph ctxGraph) {
super(factory, selector, program, parser, defaultContext, ctxGraph); super(factory, selector, program, parser, defaultContext, ctxGraph);
this.inputContexts = inputContexts;
} }
@Override @Override
@ -53,4 +60,33 @@ public class WildSleighAssembler extends AbstractSleighAssembler<WildAssemblyRes
AssemblyPatternBlock ctx) { AssemblyPatternBlock ctx) {
return new WildAssemblyTreeResolver(factory, lang, at, tree, ctx, ctxGraph); 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

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

View file

@ -240,4 +240,11 @@ public class DefaultWildAssemblyResolvedPatterns extends DefaultAssemblyResolved
builder.opInfo = opInfo; builder.opInfo = opInfo;
return builder; return builder;
} }
@Override
protected WildAssemblyResolvedPatternsBuilder withContextBuilder(AssemblyPatternBlock ctx) {
var builder = cast(super.withContextBuilder(ctx));
builder.opInfo = opInfo;
return builder;
}
} }

View file

@ -16,6 +16,7 @@
package ghidra.asm.wild; package ghidra.asm.wild;
import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import java.util.*; import java.util.*;
@ -73,7 +74,10 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
} }
ProgramBuilder armProgramBuilder = new ProgramBuilder("arm_le_test", "ARM:LE:32:v8"); ProgramBuilder armProgramBuilder = new ProgramBuilder("arm_le_test", "ARM:LE:32:v8");
armProgramBuilder.setBytes(String.format("0x%08X", 0x0), 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(); Program armProgram = armProgramBuilder.getProgram();
arm = (SleighLanguage) armProgram.getLanguage(); arm = (SleighLanguage) armProgram.getLanguage();
WildSleighAssemblerBuilder builderArm = new WildSleighAssemblerBuilder(arm); WildSleighAssemblerBuilder builderArm = new WildSleighAssemblerBuilder(arm);
@ -90,12 +94,19 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
// 0x00000004: nop // 0x00000004: nop
// 0x00000008: restore 0x1b8,ra,s0-s1 // 0x00000008: restore 0x1b8,ra,s0-s1
// 0x0000000c: nop // 0x0000000c: nop
// 0x00000010: restore 0x38,ra,s0-s1
mipsProgramBuilder.setBytes("0x00000000", mipsProgramBuilder.setBytes("0x00000000",
"0c 00 00 08 00 00 00 00 f0 30 64 77 00 00 00 00"); "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-0xc to be MIPS 16 (e.g. the // This line sets the binary at addresses 0x8-0x12 to be MIPS 16 (e.g. the
// restore instruction above) // restore instruction above)
mipsProgramBuilder.setRegisterValue("ISA_MODE", "0x8", "0xc", 1); mipsProgramBuilder.setRegisterValue("ISA_MODE", "0x8", "0x12", 1);
mipsProgramBuilder.disassemble("0x00000000", 0x10); // 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(); var mipsProgram = mipsProgramBuilder.getProgram();
mips = (SleighLanguage) mipsProgram.getLanguage(); mips = (SleighLanguage) mipsProgram.getLanguage();
WildSleighAssemblerBuilder mipsBuilder = new WildSleighAssemblerBuilder(mips); WildSleighAssemblerBuilder mipsBuilder = new WildSleighAssemblerBuilder(mips);
@ -423,8 +434,8 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
dumpResults(results); dumpResults(results);
allValidEncodings.addAll(getInstructionValuesHex(results)); allValidEncodings.addAll(getInstructionValuesHex(results));
} }
// In this case, wildcard assembler now returns identical encodings with different contexts
assertTrue("Expect to have one valid encoding", allValidEncodings.size() == 1); assertTrue("Expect to have at least one valid encoding", allValidEncodings.size() >= 1);
assertTrue("Expect to have 02:1c:41:e2 as an encoding", assertTrue("Expect to have 02:1c:41:e2 as an encoding",
allValidEncodings.contains("02:1c:41:e2")); allValidEncodings.contains("02:1c:41:e2"));
} }
@ -449,14 +460,86 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
} }
@Test @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(); mips();
Collection<AssemblyParseResult> parses = asmMips.parseLine("restore 0x1b8,ra,s0-s1"); Collection<AssemblyParseResult> parses = asmMips.parseLine("restore 0x1b8,ra,s0-s1");
AssemblyParseResult[] allResults = parses.stream() AssemblyParseResult[] allResults = parses.stream()
.filter(p -> !p.isError()) .filter(p -> !p.isError())
.toArray(AssemblyParseResult[]::new); .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); Address addr8 = mips.getAddressFactory().getDefaultAddressSpace().getAddress(8);
for (AssemblyParseResult r : allResults) { for (AssemblyParseResult r : allResults) {
@ -466,6 +549,56 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
} }
assertThat(allValidEncodings, hasItem("f0:30:64:77")); 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 @Test
@ -484,12 +617,18 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
allValidResults.addAll(getValidResults(results)); 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()); 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 @Test
public void testLw_mips() throws Exception { public void testLw_mips32() throws Exception {
mips(); mips();
Collection<AssemblyParseResult> parses = asmMips.parseLine("lw `Q1`,0x0(a0)"); Collection<AssemblyParseResult> parses = asmMips.parseLine("lw `Q1`,0x0(a0)");
AssemblyParseResult[] allResults = parses.stream() AssemblyParseResult[] allResults = parses.stream()
@ -519,6 +658,53 @@ public class WildSleighAssemblerTest extends AbstractGhidraHeadlessIntegrationTe
assertEquals(32, allValidEncodings.size()); 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 @Test
public void testCall_x86() throws Exception { public void testCall_x86() throws Exception {
x86(); x86();

View file

@ -583,12 +583,16 @@ public abstract class DomainObjectAdapterDB extends DomainObjectAdapter implemen
} }
DomainObjectAdapterDB userData = getUserData(); DomainObjectAdapterDB userData = getUserData();
if (userData != null && userData.isChanged() && (getDomainFile() instanceof GhidraFile)) { if (canSave() && userData != null && userData.isChanged() &&
(getDomainFile() instanceof GhidraFile)) {
// Only save user data if this domain object was open for update and the
// user data was modified.
try { try {
userData.prepareToSave(); userData.prepareToSave();
userData.save(null, TaskMonitor.DUMMY); userData.save(null, TaskMonitor.DUMMY);
} }
catch (CancelledException e) { catch (CancelledException e) {
// ignore
} }
catch (IOException e) { catch (IOException e) {
Msg.warn(this, "Failed to save user data for: " + getDomainFile().getName()); Msg.warn(this, "Failed to save user data for: " + getDomainFile().getName());

View file

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

View file

@ -17,12 +17,14 @@ 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.*; import java.util.Arrays;
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;
import ghidra.app.plugin.assembler.sleigh.expr.SolverException; import ghidra.app.plugin.assembler.sleigh.expr.SolverException;
import ghidra.app.plugin.assembler.sleigh.util.AsmUtil; 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.ContextOp;
import ghidra.app.plugin.processors.sleigh.expression.ContextField; import ghidra.app.plugin.processors.sleigh.expression.ContextField;
import ghidra.app.plugin.processors.sleigh.expression.TokenField; import ghidra.app.plugin.processors.sleigh.expression.TokenField;
@ -403,6 +405,60 @@ public class AssemblyPatternBlock implements Comparable<AssemblyPatternBlock> {
return new AssemblyPatternBlock(newOffset, newMask, newVals); 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 @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@ -575,6 +631,30 @@ public class AssemblyPatternBlock implements Comparable<AssemblyPatternBlock> {
return MaskedLong.fromMaskAndValue(rmsk >>> cop.getShift(), rval >>> cop.getShift()); 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 * Set all bits read by a given context operation to unknown
* *

View file

@ -37,6 +37,14 @@ public interface AssemblyResolvedPatterns extends AssemblyResolution {
*/ */
AssemblyPatternBlock getContext(); 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 * Get the length of the instruction encoding
* *

View file

@ -560,6 +560,25 @@ public class DefaultAssemblyResolvedPatterns extends AbstractAssemblyResolution
return ctx; 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 @Override
public MaskedLong readInstruction(int start, int len) { public MaskedLong readInstruction(int start, int len) {
return ins.readBytes(start, 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

@ -51,4 +51,11 @@ public class ContextCommit implements ContextChange {
decoder.closeElement(el); decoder.closeElement(el);
} }
public int getWordIndex() {
return num;
}
public int getMask() {
return mask;
}
} }

View file

@ -28,6 +28,7 @@ import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject; import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*; import ghidra.framework.store.*;
import ghidra.framework.store.local.LocalDatabaseItem; import ghidra.framework.store.local.LocalDatabaseItem;
import ghidra.program.model.lang.LanguageNotFoundException;
import ghidra.util.InvalidNameException; import ghidra.util.InvalidNameException;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;
@ -232,9 +233,25 @@ public class ProgramContentHandler extends DBWithUserDataContentHandler<ProgramD
DBHandle userDbh = DBHandle userDbh =
openAssociatedUserFile(programItem.getFileID(), PROGRAM_CONTENT_TYPE, userfs, monitor); openAssociatedUserFile(programItem.getFileID(), PROGRAM_CONTENT_TYPE, userfs, monitor);
if (userDbh != null) { if (userDbh != null) {
return new ProgramUserDataDB(userDbh, program, monitor); boolean success = false;
try {
ProgramUserDataDB data = new ProgramUserDataDB(userDbh, program, monitor);
success = true;
return data;
} }
return new ProgramUserDataDB(program); catch (LanguageNotFoundException | IllegalStateException e) {
// Ignore - delete to make way for new one
}
finally {
if (!success) {
userDbh.close();
Msg.debug(this, "Removing incompatible program user data file for " +
programItem.getPathName());
removeUserDataFile(programItem, userfs);
}
}
}
return null; // will be created by ProgramDB when modified
} }
private void recoverChangeSet(ProgramDB program, DBHandle dbh) throws IOException { private void recoverChangeSet(ProgramDB program, DBHandle dbh) throws IOException {

View file

@ -1910,8 +1910,12 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
} }
@Override @Override
protected void setChanged(boolean b) { protected void setChanged(boolean state) {
super.setChanged(b); super.setChanged(state);
if (!state && !dbh.isChanged()) {
// language upgrade has already been completed
languageUpgradeTranslator = null;
}
} }
void setChangeSet(ProgramDBChangeSet changeSet) { void setChangeSet(ProgramDBChangeSet changeSet) {
@ -2369,6 +2373,12 @@ public class ProgramDB extends DomainObjectAdapterDB implements Program, ChangeM
@Override @Override
protected void close() { protected void close() {
if (changed && languageUpgradeTranslator != null) {
// Prevent user data from being saved if program and user data
// have gone through a major language upgrade and the program
// was not saved.
programUserData.setChanged(false);
}
super.close(); super.close();
intRangePropertyMap.clear(); intRangePropertyMap.clear();
addrSetPropertyMap.clear(); addrSetPropertyMap.clear();

View file

@ -119,6 +119,11 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
return program.getName() + "_UserData"; return program.getName() + "_UserData";
} }
/**
* Create a new program user data store.
* @param program related program
* @throws IOException if an IO error occurs
*/
public ProgramUserDataDB(ProgramDB program) throws IOException { public ProgramUserDataDB(ProgramDB program) throws IOException {
super(new DBHandle(), getName(program), 500, program); super(new DBHandle(), getName(program), 500, program);
this.program = program; this.program = program;
@ -157,8 +162,22 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
} }
} }
/**
* Open existing program user data store.
* If a major language change is detected the instance will automatically attempt to upgrade
* its internal address map.
* @param dbh user data storage DB handle
* @param program related program
* @param monitor task monitor
* @throws IOException if an IO error occurs
* @throws VersionException if a DB version error occurs
* @throws LanguageNotFoundException if language was not found
* @throws CancelledException if instantiation was cancelled
* @throws IllegalStateException if data store is bad or incmopatible with program
*/
public ProgramUserDataDB(DBHandle dbh, ProgramDB program, TaskMonitor monitor) public ProgramUserDataDB(DBHandle dbh, ProgramDB program, TaskMonitor monitor)
throws IOException, VersionException, LanguageNotFoundException, CancelledException { throws IOException, VersionException, LanguageNotFoundException, CancelledException,
IllegalStateException {
super(dbh, getName(program), 500, program); super(dbh, getName(program), 500, program);
this.program = program; this.program = program;
@ -200,6 +219,9 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
upgradeDatabase(); upgradeDatabase();
if (languageVersionExc != null) { if (languageVersionExc != null) {
if (languageUpgradeTranslator == null) {
throw new LanguageNotFoundException(languageID + ":" + languageVersion);
}
try { try {
setLanguage(languageUpgradeTranslator, monitor); setLanguage(languageUpgradeTranslator, monitor);
addressMap.memoryMapChanged(program.getMemory()); addressMap.memoryMapChanged(program.getMemory());
@ -212,6 +234,16 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
} }
} }
if (!program.getLanguageID().equals(languageID)) {
throw new IllegalStateException(
"User data and program have inconsistent language ID");
}
if (program.getLanguage().getVersion() != languageVersion) {
throw new IllegalStateException(
"User data language version does not match program's");
}
endTransaction(id, true); endTransaction(id, true);
changed = false; changed = false;
clearUndo(false); clearUndo(false);
@ -431,6 +463,10 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
record.setString(VALUE_COL, languageID.getIdAsString()); record.setString(VALUE_COL, languageID.getIdAsString());
table.putRecord(record); table.putRecord(record);
record = SCHEMA.createRecord(new StringField(LANGUAGE_VERSION));
record.setString(VALUE_COL, Integer.toString(languageVersion));
table.putRecord(record);
setChanged(true); setChanged(true);
clearCache(true); clearCache(true);
@ -451,6 +487,11 @@ class ProgramUserDataDB extends DomainObjectAdapterDB implements ProgramUserData
return dbh.canUpdate(); return dbh.canUpdate();
} }
@Override
protected void setChanged(boolean b) {
super.setChanged(b);
}
private PropertyMap<?> getPropertyMap(String owner, String propertyName, int propertyType, private PropertyMap<?> getPropertyMap(String owner, String propertyName, int propertyType,
Class<?> saveableClass, boolean create) throws PropertyTypeMismatchException { Class<?> saveableClass, boolean create) throws PropertyTypeMismatchException {