mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-04 10:19:23 +02:00
Merge remote-tracking branch 'origin/Ghidra_11.3'
This commit is contained in:
commit
c49f60366b
16 changed files with 636 additions and 61 deletions
|
@ -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:
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -30,22 +32,27 @@ import ghidra.program.model.listing.Program;
|
||||||
* An assembler implementation that allows for wildcard operands
|
* An assembler implementation that allows for wildcard operands
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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.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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -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();
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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
|
* @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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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.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
|
||||||
*
|
*
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
|
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,9 @@
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
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 new ProgramUserDataDB(program);
|
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 {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue