diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/EntryPointAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/EntryPointAnalyzer.java index 85c1452612..aed7a11393 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/EntryPointAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/disassembler/EntryPointAnalyzer.java @@ -31,6 +31,7 @@ import ghidra.framework.options.Options; import ghidra.program.disassemble.Disassembler; import ghidra.program.model.address.*; import ghidra.program.model.data.PointerDataType; +import ghidra.program.model.lang.AddressLabelInfo; import ghidra.program.model.listing.*; import ghidra.program.model.symbol.*; import ghidra.program.model.util.*; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java index d9f4b373f9..bd5f7e9f08 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/opinion/AbstractProgramLoader.java @@ -37,7 +37,6 @@ import ghidra.program.model.listing.*; import ghidra.program.model.mem.InvalidAddressException; import ghidra.program.model.mem.MemoryConflictException; import ghidra.program.model.symbol.*; -import ghidra.program.model.util.AddressLabelInfo; import ghidra.program.util.DefaultLanguageService; import ghidra.program.util.GhidraProgramUtilities; import ghidra.util.*; @@ -519,9 +518,7 @@ public abstract class AbstractProgramLoader implements Loader { for (Register reg : lang.getRegisters()) { Address addr = reg.getAddress(); if (addr.isMemoryAddress()) { - AddressLabelInfo info = new AddressLabelInfo(addr, reg.getName(), - reg.isBaseRegister(), SourceType.IMPORTED); - createSymbol(program, info, true); + createSymbol(program, reg.getName(), addr, false, true, true); } } // optionally create default symbols defined by pspec @@ -529,7 +526,7 @@ public abstract class AbstractProgramLoader implements Loader { boolean anchorSymbols = shouldAnchorSymbols(options); List labels = lang.getDefaultSymbols(); for (AddressLabelInfo info : labels) { - createSymbol(program, info, anchorSymbols); + createSymbol(program, info.getLabel(), info.getAddress(), info.isEntry(), info.isPrimary(), anchorSymbols); } } GhidraProgramUtilities.removeAnalyzedFlag(program); @@ -539,42 +536,24 @@ public abstract class AbstractProgramLoader implements Loader { } } - private void createSymbol(Program program, AddressLabelInfo info, boolean anchorSymbols) { + private static void createSymbol(Program program, String labelname, Address address, boolean isEntry, boolean isPrimary, boolean anchorSymbols) { SymbolTable symTable = program.getSymbolTable(); - Address addr = info.getAddress(); + Address addr = address; Symbol s = symTable.getPrimarySymbol(addr); try { - if (s == null || s.getSource() == SourceType.IMPORTED) { - Namespace namespace = program.getGlobalNamespace(); - if (info.getScope() != null) { - namespace = info.getScope(); - } - s = symTable.createLabel(addr, info.getLabel(), namespace, info.getSource()); - if (info.isEntry()) { - symTable.addExternalEntryPoint(addr); - } - if (info.isPrimary()) { - s.setPrimary(); - } - if (anchorSymbols) { - s.setPinned(true); - } + Namespace namespace = program.getGlobalNamespace(); + s = symTable.createLabel(addr, labelname, namespace, SourceType.IMPORTED); + if (isEntry) { + symTable.addExternalEntryPoint(addr); } - else if (s.getSource() == SourceType.DEFAULT) { - String labelName = info.getLabel(); - if (s.getSymbolType() == SymbolType.FUNCTION) { - Function f = (Function) s.getObject(); - f.setName(labelName, SourceType.IMPORTED); - } - else { - s.setName(labelName, SourceType.IMPORTED); - } - if (anchorSymbols) { - s.setPinned(true); - } + if (isPrimary) { + s.setPrimary(); + } + if (anchorSymbols) { + s.setPinned(true); } } - catch (DuplicateNameException | InvalidInputException e) { + catch (InvalidInputException e) { // Nothing to do } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/database/ProgramBuilder.java b/Ghidra/Features/Base/src/main/java/ghidra/program/database/ProgramBuilder.java index 8fdec47e9c..9186bc7367 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/database/ProgramBuilder.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/database/ProgramBuilder.java @@ -139,6 +139,22 @@ public class ProgramBuilder { setAnalyzed(true); program.setTemporary(true); // ignore changes } + + /** + * Construct program builder using a full language object rather than a language id string + * @param name program name + * @param language Language object + * @param compilerSpecID compiler specification ID (if null default spec will be used) + * @param consumer program consumer (if null this builder will be used as consumer and must be disposed to release program) + * @throws Exception if there is an exception creating the program + */ + public ProgramBuilder(String name, Language language) + throws Exception { + CompilerSpec compilerSpec = language.getDefaultCompilerSpec(); + program = new ProgramDB(name, language, compilerSpec, this); + setAnalyzed(true); + program.setTemporary(true); // ignore changes + } /** * Perform complete analysis on the built program. diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/symbol/PinnedSymbolTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/symbol/PinnedSymbolTest.java index a8ec823379..38a9935a7a 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/symbol/PinnedSymbolTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/program/database/symbol/PinnedSymbolTest.java @@ -26,12 +26,12 @@ import ghidra.framework.store.LockException; import ghidra.program.database.ProgramDB; import ghidra.program.database.function.OverlappingFunctionException; import ghidra.program.model.address.*; +import ghidra.program.model.lang.AddressLabelInfo; import ghidra.program.model.lang.Language; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.*; import ghidra.program.model.symbol.*; -import ghidra.program.model.util.AddressLabelInfo; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.util.exception.InvalidInputException; import ghidra.util.exception.NotFoundException; diff --git a/Ghidra/Features/Base/src/test/java/ghidra/program/database/ProgramVolatilityTest.java b/Ghidra/Features/Base/src/test/java/ghidra/program/database/ProgramVolatilityTest.java new file mode 100644 index 0000000000..b598e6155a --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/program/database/ProgramVolatilityTest.java @@ -0,0 +1,190 @@ +/* ### + * 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.program.database; + +import org.junit.Assert; +import org.junit.Test; + +import ghidra.app.plugin.processors.sleigh.SleighLanguageVolatilityTest; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Data; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; + +public class ProgramVolatilityTest extends SleighLanguageVolatilityTest { + private Program prog; + String PORTFAddressString = "mem:0x31"; + String PORTGAddressString = "mem:0x34"; + + boolean isPORTFVolatile; + boolean isPORTFMemoryVolatile; + boolean isPORTGVolatile; + boolean isPORTGMemoryVolatile; + boolean isPORTFDataVolatile; + boolean isPORTGDataVolatile; + + public void setUp(Boolean symbolVolatile, Integer symbolSize, Boolean memoryVolatile, boolean reverse) throws Exception { + super.setUp(symbolVolatile, symbolSize, memoryVolatile, reverse); + + ProgramBuilder builder = new ProgramBuilder("test", lang); + builder.createMemory("PORTF", "mem:0x31", 2); //last parameter is block length + builder.createMemory("PORTG", "mem:0x34", 2); + + prog = builder.getProgram(); + + Address PORTFAddress = prog.getAddressFactory().getAddress(PORTFAddressString); + Address PORTGAddress = prog.getAddressFactory().getAddress(PORTGAddressString); + MemoryBlock PORTFMemoryBlock = prog.getMemory().getBlock(PORTFAddress); + MemoryBlock PORTGMemoryBlock = prog.getMemory().getBlock(PORTGAddress); + Data PORTFData = prog.getListing().getDataAt(PORTGAddress); + Data PORTGData = prog.getListing().getDataAt(PORTGAddress); + + isPORTFVolatile = lang.isVolatile(PORTFAddress); + isPORTFMemoryVolatile = PORTFMemoryBlock.isVolatile(); + isPORTFDataVolatile = PORTFData.isVolatile(); + + isPORTGVolatile = lang.isVolatile(PORTGAddress); + isPORTGMemoryVolatile = PORTGMemoryBlock.isVolatile(); + isPORTGDataVolatile = PORTGData.isVolatile(); + } + + @Test + public void testProgramPORTFDefined() throws Exception { + setUp(null, null, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + + setUp(false, null, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + + setUp(true, null, null, false); + + Assert.assertTrue(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + } + + @Test + public void testProgramPORTFSizeDefined() throws Exception { + setUp(null, 1, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + Assert.assertFalse(isPORTGVolatile); + Assert.assertFalse(isPORTGMemoryVolatile); + Assert.assertFalse(isPORTGDataVolatile); + + setUp(false, 1, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + Assert.assertFalse(isPORTGVolatile); + Assert.assertFalse(isPORTGMemoryVolatile); + Assert.assertFalse(isPORTGDataVolatile); + + setUp(true, 1, null, false); + + Assert.assertTrue(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + Assert.assertFalse(isPORTGVolatile); + Assert.assertFalse(isPORTGMemoryVolatile); + Assert.assertFalse(isPORTGDataVolatile); + + setUp(null, 4, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + Assert.assertFalse(isPORTGVolatile); + Assert.assertFalse(isPORTGMemoryVolatile); + Assert.assertFalse(isPORTGDataVolatile); + + setUp(false, 4, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + Assert.assertFalse(isPORTGVolatile); + Assert.assertFalse(isPORTGMemoryVolatile); + Assert.assertFalse(isPORTGDataVolatile); + + setUp(true, 4, null, false); // setting portf to size 4 overwrites portg as well + + Assert.assertTrue(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + Assert.assertTrue(isPORTGVolatile); + Assert.assertFalse(isPORTGMemoryVolatile); + Assert.assertFalse(isPORTGDataVolatile); + } + + @Test + public void testProgramMemoryDefinedVolatile() throws Exception { + setUp(null, null, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + + setUp(null, null, false, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + + setUp(null, null, true, false); + + Assert.assertTrue(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + } + + @Test + public void testProgramPORTFandMemoryDefined() throws Exception { + setUp(true, null, true, false); + + Assert.assertTrue(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + + setUp(false, null, true, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + + setUp(true, null, false, false); + + Assert.assertTrue(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + + setUp(false, null, false, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTFMemoryVolatile); + Assert.assertFalse(isPORTFDataVolatile); + } + +} diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FillOutStructureCmd.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FillOutStructureCmd.java index f696c755d7..63a724ffee 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FillOutStructureCmd.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/plugin/core/decompile/actions/FillOutStructureCmd.java @@ -559,12 +559,16 @@ public class FillOutStructureCmd extends BackgroundCommand { return currentProgram.getDataTypeManager().getUniqueName(new CategoryPath(category), base); } - private boolean sanityCheck(long offset) { + private boolean sanityCheck(long offset, long existingSize) { + if (offset < 0) { return false; // offsets shouldn't be negative } + if (offset < existingSize) { + return true; // we have room in the structure + } if (offset > 0x1000) { - return false; // Arbitrary size cut-off to prevent creating huge structures + return false; // bigger than existing size; arbitrary cut-off to prevent huge structures } return true; } @@ -648,7 +652,7 @@ public class FillOutStructureCmd extends BackgroundCommand { long value = getSigned(inputs[1]); newOff = currentRef.offset + ((pcodeOp.getOpcode() == PcodeOp.INT_ADD) ? value : (-value)); - if (sanityCheck(newOff)) { // should this offset create a location in the structure? + if (sanityCheck(newOff, componentMap.getSize())) { // should this offset create a location in the structure? putOnList(output, newOff, todoList, doneList); // Don't do componentMap.addDataType() as data-type info here is likely uninformed componentMap.setMinimumSize(newOff); @@ -659,7 +663,7 @@ public class FillOutStructureCmd extends BackgroundCommand { break; } newOff = currentRef.offset + getSigned(inputs[1]) * inputs[2].getOffset(); - if (sanityCheck(newOff)) { // should this offset create a location in the structure? + if (sanityCheck(newOff, componentMap.getSize())) { // should this offset create a location in the structure? putOnList(output, newOff, todoList, doneList); // Don't do componentMap.addReference() as data-type info here is likely uninformed componentMap.setMinimumSize(newOff); @@ -670,7 +674,7 @@ public class FillOutStructureCmd extends BackgroundCommand { break; } long subOff = currentRef.offset + getSigned(inputs[1]); - if (sanityCheck(subOff)) { // should this offset create a location in the structure? + if (sanityCheck(subOff, componentMap.getSize())) { // should this offset create a location in the structure? putOnList(output, subOff, todoList, doneList); // Don't do componentMap.addReference() as data-type info here is likely uninformed componentMap.setMinimumSize(subOff); diff --git a/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerPspecVolatilityTest.java b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerPspecVolatilityTest.java new file mode 100644 index 0000000000..03de1a251e --- /dev/null +++ b/Ghidra/Features/Decompiler/src/test.slow/java/ghidra/app/plugin/core/decompile/DecompilerPspecVolatilityTest.java @@ -0,0 +1,171 @@ +/* ### + * 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.core.decompile; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import ghidra.app.decompiler.DecompInterface; +import ghidra.app.decompiler.DecompileOptions; +import ghidra.app.decompiler.DecompileResults; +import ghidra.app.plugin.processors.sleigh.SleighLanguageVolatilityTest; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressFormatException; +import ghidra.program.model.listing.Function; +import ghidra.program.model.listing.Program; +import ghidra.program.database.ProgramBuilder; +import ghidra.util.task.TaskMonitor; + +public class DecompilerPspecVolatilityTest extends SleighLanguageVolatilityTest { + private Program prog; + private DecompInterface decompiler; + + private String functionBytes = "84 ff 02 c0 8d 9a 01 c0 8d 98 85 ff 02 c0 a4 9a 01 c0 a4 98 2f " + + "b7 86 ff 05 c0 f8 94 90 91 02 01 90 68 04 c0 f8 94 90 91 02 01 9f 77 90 93 02 01" + + " 2f bf 87 ff 02 c0 a3 9a 01 c0 a3 98 8f 9a 85 e0 8a 95 f1 f7 00 00 8f 98 08"; + + private int functionLength = 27; + private String addressString = "0x1000"; + private String decompilation; + private String decompilerPORTFNotVolatileString = "DAT_mem_0031 = DAT_mem_0031"; + private String decompilerPORTGNotVolatileString = "DAT_mem_0034 = DAT_mem_0034"; + private boolean decompilerPORTFVolatile; + private boolean decompilerPORTGVolatile; + + public void setUp(Boolean symbolVolatile, Integer symbolSize, Boolean memoryVolatile, boolean reverse) throws Exception { + super.setUp(symbolVolatile, symbolSize, memoryVolatile, reverse); + + ProgramBuilder builder = new ProgramBuilder("test", lang); + + builder.setBytes(addressString, functionBytes); + builder.disassemble(addressString, functionLength, false); + builder.createFunction(addressString); + + prog = builder.getProgram(); + + if (decompiler != null) { + decompiler.dispose(); + } + + decompiler = new DecompInterface(); + decompiler.openProgram(prog); + + decompilation = getDecompilationString(addressString); + + decompilerPORTFVolatile = !decompilation.contains(decompilerPORTFNotVolatileString); + decompilerPORTGVolatile = !decompilation.contains(decompilerPORTGNotVolatileString); + } + + private String getDecompilationString(String address) throws AddressFormatException + { + Address addr = prog.getAddressFactory().getDefaultAddressSpace().getAddress(address); + Function func = prog.getListing().getFunctionAt(addr); + DecompileResults decompResults = decompiler.decompileFunction(func, + DecompileOptions.SUGGESTED_DECOMPILE_TIMEOUT_SECS, TaskMonitor.DUMMY); + String decompilation = decompResults.getDecompiledFunction().getC(); + return decompilation; + } + + @After + public void tearDown() throws Exception { + if (decompiler != null) { + decompiler.dispose(); + } + } + + @Test + public void testDecompileInterfaceReturnsAFunction() throws Exception { + setUp(null, null, false, false); + + Assert.assertNotNull(decompilation); + } + + @Test + public void testDecompilePORTFSymbolPspecSettings() throws Exception { + setUp(null, null, null, false); + + //Decompiler should indicate mem:0x31 is not volatile + Assert.assertFalse(decompilerPORTFVolatile); + + setUp(false, null, null, false); + + //Decompiler should indicate mem:0x31 is not volatile + Assert.assertFalse(decompilerPORTFVolatile); + + setUp(true, null, null, false); + + //Decompiler should indicate mem:0x31 is volatile because the symbol element in the language + //pspec file defined the symbol at mem:0x31 to be volatile. + Assert.assertTrue(decompilerPORTFVolatile); + } + + @Test + public void testDecompilePORTFMemoryPspecSettings() throws Exception { + setUp(null, null, true, false); + + //Decompiler should indicate mem:0x31 is volatile because the pspec file includes a volatile + //element that defines the memory location that includes 0x31 as volatile. + Assert.assertTrue(decompilerPORTFVolatile); + + setUp(null, null, false, false); + + //Decompiler should indicate mem:0x31 is not volatile + Assert.assertFalse(decompilerPORTFVolatile); + + setUp(null, null, null, false); + + //Decompiler should indicate mem:0x31 is not volatile + Assert.assertFalse(decompilerPORTFVolatile); + + setUp(false, null, true, false); + + //Decompiler should indicate mem:0x31 is not volatile because the pspec file defines the + //symbol element PORTF as not volatile and that takes precedence over the pspec's volatile + //element. + Assert.assertFalse(decompilerPORTFVolatile); + + setUp(true, null, true, false); + + //Decompiler should indicate mem:0x31 is volatile + Assert.assertTrue(decompilerPORTFVolatile); + + setUp(false, null, true, true); + + //Decompiler should indicate mem:0x31 is not volatile + Assert.assertFalse(decompilerPORTFVolatile); + } + + @Test + public void testDecompilePORFSizeOverwritesPORTG() throws Exception { + setUp(true, 1, null, false); + + //Decompiler should indicate mem:0x31 and mem:0x34 are volatile + Assert.assertTrue(decompilerPORTFVolatile); + Assert.assertFalse(decompilerPORTGVolatile); + + setUp(false, 4, true, false); //size of 4 addressable units 0x31, 0x32, 0x33 0x34 + + //Decompiler should indicate mem:0x31 and mem:0x34 are not volatile + Assert.assertFalse(decompilerPORTFVolatile); + Assert.assertFalse(decompilerPORTGVolatile); + + setUp(true, 4, null, false); + + //Decompiler should indicate mem:0x31 and mem:0x34 are volatile + Assert.assertTrue(decompilerPORTFVolatile); + Assert.assertTrue(decompilerPORTGVolatile); + } +} diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/util/xml/SpecXmlUtils.java b/Ghidra/Framework/Generic/src/main/java/ghidra/util/xml/SpecXmlUtils.java index 9c5d4d7194..fadf2b4786 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/util/xml/SpecXmlUtils.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/util/xml/SpecXmlUtils.java @@ -39,7 +39,7 @@ import org.xml.sax.*; */ public class SpecXmlUtils { - static public boolean decodeBoolean(String val) { + static public Boolean decodeNullableBoolean(String val) { if (val!=null && val.length()!=0) { switch(val.charAt(0)) { case 'y': @@ -53,7 +53,15 @@ public class SpecXmlUtils { default: } } - return false; // Should we throw an exception for bad encodings? + return null; // Should we throw an exception for bad encodings? + } + + static public boolean decodeBoolean(String val) { + Boolean returnValue = decodeNullableBoolean(val); + if (returnValue != null) { + return returnValue; + } + return false; } static public String encodeBoolean(boolean val) { diff --git a/Ghidra/Framework/SoftwareModeling/data/languages/processor_spec.rxg b/Ghidra/Framework/SoftwareModeling/data/languages/processor_spec.rxg index cc7d032433..03b3ef8704 100644 --- a/Ghidra/Framework/SoftwareModeling/data/languages/processor_spec.rxg +++ b/Ghidra/Framework/SoftwareModeling/data/languages/processor_spec.rxg @@ -128,6 +128,14 @@ + + + + + + + + diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java index d298f027ec..7013378c6f 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguage.java @@ -45,8 +45,6 @@ import ghidra.program.model.mem.MemBuffer; import ghidra.program.model.mem.MemoryAccessException; import ghidra.program.model.pcode.ElementId; import ghidra.program.model.pcode.Encoder; -import ghidra.program.model.symbol.SourceType; -import ghidra.program.model.util.AddressLabelInfo; import ghidra.program.model.util.ProcessorSymbolType; import ghidra.sleigh.grammar.SleighPreprocessor; import ghidra.sleigh.grammar.SourceFileIndexer; @@ -93,9 +91,11 @@ public class SleighLanguage implements Language { /** * Non-null if a space should yes segmented addressing */ - String segmentedspace = ""; - String segmentType = ""; - AddressSet volatileAddresses; + private String segmentedspace = ""; + private String segmentType = ""; + private AddressSet volatileAddresses; + private AddressSet volatileSymbolAddresses; + private AddressSet nonVolatileSymbolAddresses; private ContextCache contextcache = null; /** * Cached instruction prototypes @@ -156,7 +156,7 @@ public class SleighLanguage implements Language { registerBuilder = new RegisterBuilder(); loadRegisters(registerBuilder); readRemainingSpecification(); - + buildVolatileSymbolAddresses(); xrefRegisters(); instructProtoMap = new LinkedHashMap<>(); @@ -164,6 +164,18 @@ public class SleighLanguage implements Language { initParallelHelper(); } + private void buildVolatileSymbolAddresses() { + if (volatileAddresses == null) { + volatileAddresses = new AddressSet(); + } + if (volatileSymbolAddresses != null) { + volatileAddresses.add(volatileSymbolAddresses); + } + if (nonVolatileSymbolAddresses != null) { + volatileAddresses.delete(nonVolatileSymbolAddresses); + } + } + private boolean isSLAWrongVersion(ResourceFile slaFile) { XmlPullParser parser = null; try { @@ -383,10 +395,7 @@ public class SleighLanguage implements Language { @Override public boolean isVolatile(Address addr) { - if (volatileAddresses != null) { - return volatileAddresses.contains(addr); - } - return false; + return volatileAddresses.contains(addr); } @Override @@ -798,15 +807,38 @@ public class SleighLanguage implements Language { String typeString = symbol.getAttribute("type"); ProcessorSymbolType type = ProcessorSymbolType.getType(typeString); boolean isEntry = SpecXmlUtils.decodeBoolean(symbol.getAttribute("entry")); - Address address = addressFactory.getAddress(addressString); - if (address == null) { + Address startAddress = addressFactory.getAddress(addressString); + int rangeSize = SpecXmlUtils.decodeInt(symbol.getAttribute("size")); + Boolean isVolatile = SpecXmlUtils.decodeNullableBoolean( + symbol.getAttribute("volatile")); + if (startAddress == null) { Msg.error(this, "invalid symbol address \"" + addressString + "\": " + description.getSpecFile()); } else { - AddressLabelInfo info = new AddressLabelInfo(address, labelName, false, - null, SourceType.IMPORTED, isEntry, type); + AddressLabelInfo info; + try { + info = new AddressLabelInfo(startAddress, rangeSize, labelName, false, + isEntry, type, isVolatile); + } catch (AddressOverflowException e) { + throw new XmlParseException("invalid symbol definition: " + labelName, e); + } defaultSymbols.add(info); + if (isVolatile != null) { + Address endAddress = info.getEndAddress(); + if (isVolatile) { + if (volatileSymbolAddresses == null) { + volatileSymbolAddresses = new AddressSet(); + } + volatileSymbolAddresses.addRange(startAddress, endAddress); + } else { + if (nonVolatileSymbolAddresses == null) { + nonVolatileSymbolAddresses = new AddressSet(); + } + // punch a hole in the volatile address space. + nonVolatileSymbolAddresses.addRange(startAddress, endAddress); + } + } } // skip the end tag parser.end(symbol); @@ -864,7 +896,7 @@ public class SleighLanguage implements Language { read(parser); } catch (XmlParseException e) { - Msg.error(this, e.getMessage()); + Msg.error(this, "Failed to parse Sleigh Specification ("+ specFile.getName() + "): " + e.getMessage()); } finally { parser.dispose(); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageProvider.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageProvider.java index 0176e77225..bbd1d505ef 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageProvider.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/app/plugin/processors/sleigh/SleighLanguageProvider.java @@ -67,21 +67,31 @@ public class SleighLanguageProvider implements LanguageProvider { public static synchronized SleighLanguageProvider getSleighLanguageProvider() { if (instance == null) { instance = new SleighLanguageProvider(); - try { - instance.createLanguages(); - } - catch (Exception e) { - Msg.error(SleighLanguageProvider.class, - "Sleigh language provider initiailization failed", e); - } } return instance; } /** - * Construct sleigh language provider (singleton) + * Construct sleigh language provider (singleton use) */ private SleighLanguageProvider() { + try { + instance.createLanguages(); + } + catch (Exception e) { + Msg.error(SleighLanguageProvider.class, + "Sleigh language provider initiailization failed", e); + } + } + + /** + * Construct language provider (intended for test use only) + * @param ldefsFile language definitions file + * @throws SAXException if parse error occurs + * @throws IOException if IO error occurs + */ + SleighLanguageProvider(ResourceFile ldefsFile) throws SAXException, IOException { + createLanguages(ldefsFile); } private void createLanguages() throws Exception { @@ -91,7 +101,7 @@ public class SleighLanguageProvider implements LanguageProvider { } } - private void createLanguages(ResourceFile file) throws Exception { + private void createLanguages(ResourceFile file) throws SAXException, IOException { try { SleighLanguageValidator.validateLdefsFile(file); createLanguageDescriptions(file); @@ -168,7 +178,8 @@ public class SleighLanguageProvider implements LanguageProvider { return d; } - private void createLanguageDescriptions(final ResourceFile specFile) throws Exception { + private void createLanguageDescriptions(final ResourceFile specFile) + throws SAXException, IOException { ErrorHandler errHandler = new ErrorHandler() { @Override public void error(SAXParseException exception) throws SAXException { diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/util/AddressLabelInfo.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/AddressLabelInfo.java similarity index 57% rename from Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/util/AddressLabelInfo.java rename to Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/AddressLabelInfo.java index 8f3834d2f1..d85e85ddcb 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/util/AddressLabelInfo.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/AddressLabelInfo.java @@ -13,63 +13,54 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package ghidra.program.model.util; +package ghidra.program.model.lang; import ghidra.program.model.address.Address; -import ghidra.program.model.symbol.*; +import ghidra.program.model.address.AddressOverflowException; +import ghidra.program.model.util.ProcessorSymbolType; /** * AddressLabelInfo is a utility class for storing - * an Address and a corresponding label or alias together. + * an Address together with a corresponding language-defined + * label or alias that is within the global namespace which is + * established with a SourceType of IMPORTED within a program. */ public class AddressLabelInfo implements Comparable { private Address addr; + private Address endAddr; private String label; private boolean isPrimary; - private Namespace scope; - private SourceType symbolSource; private boolean isEntry; private ProcessorSymbolType processorSymbolType; - + private int sizeInBytes; + private Boolean isVolatile; + /** - * Constructs a new AddressLabelInfo object - * @param s symbol to initialize info from. + * Constructor for class AddressLabelInfo + * + * @param addr Address object that describes the memory address + * @param sizeInBytes Integer describing the Size in bytes that the label applies to. + * @param label String label or alias for the Address + * @param isPrimary boolean describes if this object is the primary label for the Address 'addr' + * @param isEntry boolean describes if this object is an entry label for the Address 'addr' + * @param type ProcessorSymbolType the type of symbol + * @param isVolatile Boolean describes if the memory at this address is volatile */ - public AddressLabelInfo(Symbol s) { - this.addr = s.getAddress(); - this.label = s.getName(); - this.isPrimary = s.isPrimary(); - scope = s.getParentNamespace(); - symbolSource = s.getSource(); - isEntry = s.isExternalEntryPoint(); - } - - public AddressLabelInfo(Address addr, String label, boolean isPrimary, Namespace scope, - SourceType symbolSource, boolean isEntry) { - this(addr, label, isPrimary, scope, symbolSource, isEntry, null); - } - - public AddressLabelInfo(Address addr, String label, boolean isPrimary, Namespace scope, - SourceType symbolSource, boolean isEntry, ProcessorSymbolType type) { + public AddressLabelInfo(Address addr, Integer sizeInBytes, String label, boolean isPrimary, + boolean isEntry, ProcessorSymbolType type, Boolean isVolatile) throws AddressOverflowException { this.addr = addr; + if ( sizeInBytes == null || sizeInBytes <= 0 ) { + // Default size in addressable units + this.sizeInBytes = addr.getAddressSpace().getAddressableUnitSize(); + } else { + this.sizeInBytes = sizeInBytes; + } + this.endAddr = this.addr.addNoWrap(this.sizeInBytes-1); this.label = label; this.isPrimary = isPrimary; - this.scope = scope; - this.symbolSource = symbolSource; this.isEntry = isEntry; this.processorSymbolType = type; - } - - public AddressLabelInfo(Address addr, String label, boolean isPrimary, SourceType symbolSource) { - this(addr, label, isPrimary, null, symbolSource, false); - } - - /** - * Constructs a new AddressLabelInfo object with only address information - * @param addr the address to store in this object - */ - public AddressLabelInfo(Address addr) { - this(addr, null, false, null, SourceType.DEFAULT, false); + this.isVolatile = isVolatile; } /** @@ -78,7 +69,14 @@ public class AddressLabelInfo implements Comparable { public final Address getAddress() { return addr; } - + + /** + * Returns the object's end address. + */ + public final Address getEndAddress() { + return endAddr; + } + /** * Returns the object's label or alias. */ @@ -86,18 +84,29 @@ public class AddressLabelInfo implements Comparable { return label; } + /** + * Returns the object's size in bytes. Always non-zero positive value and defaults to + * addressable unit size of associated address space. + */ + public final int getByteSize() { + return sizeInBytes; + } + /** * Returns whether the object is the primary label at the address. */ public final boolean isPrimary() { return isPrimary; } - + /** - * Returns the scope for the symbol. + * Returns whether the object is volatile. + * Boolean.False when the address is explicitly not volatile. + * Boolean.True when the address is volatile. + * NULL when the volatility is not defined at this address. */ - public Namespace getScope() { - return scope; + public final Boolean isVolatile() { + return isVolatile; } /** @@ -140,10 +149,6 @@ public class AddressLabelInfo implements Comparable { return thisLabel.compareTo(addrLabel); } - public SourceType getSource() { - return symbolSource; - } - public boolean isEntry() { return isEntry; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Language.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Language.java index eda905691e..7bbf4b3734 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Language.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/lang/Language.java @@ -24,7 +24,6 @@ import ghidra.program.model.address.*; import ghidra.program.model.listing.DefaultProgramContext; import ghidra.program.model.listing.Program; import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.util.AddressLabelInfo; import ghidra.util.ManualEntry; import ghidra.util.task.TaskMonitor; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/OldLanguage.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/OldLanguage.java index b0191476f4..7ec22fac22 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/OldLanguage.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/OldLanguage.java @@ -29,7 +29,6 @@ import ghidra.program.model.address.*; import ghidra.program.model.lang.*; import ghidra.program.model.listing.DefaultProgramContext; import ghidra.program.model.mem.MemBuffer; -import ghidra.program.model.util.AddressLabelInfo; import ghidra.util.ManualEntry; import ghidra.util.XmlProgramUtilities; import ghidra.util.task.TaskMonitor; diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/processors/sleigh/SleighLanguageVolatilityTest.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/processors/sleigh/SleighLanguageVolatilityTest.java new file mode 100644 index 0000000000..ab275912ff --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/app/plugin/processors/sleigh/SleighLanguageVolatilityTest.java @@ -0,0 +1,288 @@ +/* ### + * 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.processors.sleigh; + +import java.io.*; + +import org.junit.Assert; +import org.junit.Test; + +import generic.jar.ResourceFile; +import generic.test.AbstractGenericTest; +import ghidra.framework.Application; +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.LanguageID; + +public class SleighLanguageVolatilityTest extends AbstractGenericTest{ + protected SleighLanguage lang; + protected String PORTFAddressString = "mem:0x31"; + protected String PORTGAddressString = "mem:0x34"; + protected boolean isPORTFVolatile; + protected boolean isPORTGVolatile; + + /** + * Constructs a string based on parameters, and uses that as the content of a custom pspec file. + * Parameters effect the volatility of the symbol "PORTF". + * The pspec file is read by the SleighLanguage object which tracks volatile addresses. + * @param symbolVolatile Nullable boolean value that specifies the symbol PORTF volatility setting. + * @param symbolSize Nullable integer value specifying the symbol PORTF size in bytes. + * @param memoryVolatile Nullable boolean value that specifies the volatility setting of the + * memory location that includes PORTF. + * @param reverseOrder boolean, reverseOrder refers to the order that 'volatile' and + * 'default_symbols' elements appear in the pspec file. + * @throws Exception if unexpected error occurs + * @returns the data + */ + public void setUp(Boolean symbolVolatile, Integer symbolSize, Boolean memoryVolatile, boolean reverseOrder) throws Exception { + //symbolVolatile and symbolSize are in reference to the symbol PORTF. However, setting a + //size that is too large will overwrite other symbols such as PING, DDRG or PORTG. + String defaultSymbolsElement = + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n"; + + //memoryVolatile null will not set the memory range 0x20 to 0x57 as volatile. + //memoryVolatile true will set the memory range 0x20 to 0x57 to volatile. + //memoryVolatile false will exclude the address of PORTF (0x31) from the volatility setting. + //Example: + // "" + // "" + String volatileElement = + " \r\n"; + volatileElement += memoryVolatile == null ? "" : + memoryVolatile ? + "\r\n" + : + "\r\n" + + "\r\n"; + + volatileElement += " \r\n" + + " \r\n"; + + //This variable represents the content of a pspec file. + //The original pspec file this is based on is the avr8 atmega256.pspec. + String pspecContentString = + "\r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + " \r\n" + + " \r\n"; + pspecContentString += reverseOrder ? volatileElement : defaultSymbolsElement; + pspecContentString += " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n"; + pspecContentString += reverseOrder ? defaultSymbolsElement : volatileElement; + pspecContentString += "\r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + " \r\n" + + "\r\n" + + "\r\n" + + "\r\n" + + ""; + + String languageIDString = "avr8:LE:16:atmega256Test"; + LanguageID langId = new LanguageID(languageIDString); + + ResourceFile pspecFile = createCustomPspecFile("atmega256", pspecContentString); + ResourceFile ldefFile = createTempLdefsFile("avr8", pspecFile); + SleighLanguageProvider provider = new SleighLanguageProvider(ldefFile); + lang = (SleighLanguage) provider.getLanguage(langId); + + Address PORTFAddress = lang.getAddressFactory().getAddress(PORTFAddressString); + Address PORTGAddress = lang.getAddressFactory().getAddress(PORTGAddressString); + + isPORTFVolatile = lang.isVolatile(PORTFAddress); + isPORTGVolatile = lang.isVolatile(PORTGAddress); + } + + @Test + public void testPORTFWithSymbolVolatility() throws Exception { + setUp(null, null, null, false); + + Assert.assertFalse(isPORTFVolatile); + + setUp(false, null, null, false); + + Assert.assertFalse(isPORTFVolatile); + + setUp(true, null, null, false); + + Assert.assertTrue(isPORTFVolatile); + } + + @Test + public void testPORTFWithSize() throws Exception { + setUp(null, 1, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTGVolatile); + + setUp(false, 1, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTGVolatile); + + setUp(true, 1, null, false); + + Assert.assertTrue(isPORTFVolatile); + Assert.assertFalse(isPORTGVolatile); + + setUp(null, 4, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTGVolatile); + + setUp(false, 4, null, false); + + Assert.assertFalse(isPORTFVolatile); + Assert.assertFalse(isPORTGVolatile); + + setUp(true, 4, null, false); // setting portf to size 4 overwrites portg as well + + Assert.assertTrue(isPORTFVolatile); + Assert.assertTrue(isPORTGVolatile); + } + + @Test + public void testPORTFNoSizeOrSymbolVolatility() throws Exception { + setUp(null, null, null, false); + + Assert.assertFalse(isPORTFVolatile); + + setUp(null, null, false, false); + + Assert.assertFalse(isPORTFVolatile); + + setUp(null, null, true, false); + + Assert.assertTrue(isPORTFVolatile); + } + + @Test + public void testPORTFNoSize() throws Exception { + setUp(true, null, true, false); + + Assert.assertTrue(isPORTFVolatile); + + setUp(false, null, true, false); + + Assert.assertFalse(isPORTFVolatile); + + setUp(true, null, false, false); + + Assert.assertTrue(isPORTFVolatile); + + setUp(false, null, false, false); + + Assert.assertFalse(isPORTFVolatile); + } + + @Test + public void testReverseSettingPORTFVolatile() throws Exception { + setUp(false, null, null, true); + Assert.assertFalse(isPORTFVolatile); + setUp(true, null, null, true); + Assert.assertTrue(isPORTFVolatile); + } + + private ResourceFile createTempLdefsFile(String name, ResourceFile pspecFile) { + String pspecFilename = pspecFile.getName(); + return createCustomLdefFile("avr8", pspecFilename); + } + + public ResourceFile createCustomPspecFile(String name, String content) { + File newPspecFile = null; + try { + newPspecFile = File.createTempFile(name, ".pspec"); + BufferedWriter bw = new BufferedWriter(new FileWriter(newPspecFile)); + bw.write(content); + bw.close(); + + } + catch(IOException e){ + System.err.println("Error creating test pspec file."); + } + newPspecFile.deleteOnExit(); + return new ResourceFile(newPspecFile); + } + + public ResourceFile createCustomLdefFile(String name, String pspecFilename) { + Iterable files = Application.findFilesByExtensionInApplication(".ldefs"); + ResourceFile originalLdefFile = null; + for (ResourceFile file : files) { + if (file.getName().equals(name + ".ldefs")) + { + originalLdefFile = file; + break; + } + } + + try { + File editedPspecFile = File.createTempFile(name, ".ldefs"); + BufferedReader br = new BufferedReader(new FileReader(originalLdefFile.getFile(false))); + BufferedWriter bw = new BufferedWriter(new FileWriter(editedPspecFile)); + String s; + while ((s = br.readLine()) != null) { + //if the string is defining a filename, edit that line + String originalPspecFilename = "atmega256.pspec"; + + if ( s.contains(originalPspecFilename) ) + { + s = s.replace(originalPspecFilename, pspecFilename); + } + + if (s.contains("avr8:LE:16:atmega256")) + { + s = s.replace("avr8:LE:16:atmega256", "avr8:LE:16:atmega256Test"); + } + bw.write(s); + bw.newLine(); + } + bw.close(); + br.close(); + editedPspecFile.deleteOnExit(); + return new ResourceFile(editedPspecFile); + } + catch(IOException e) { + System.err.println("Error creating test pspec file."); + } + + return null; + } + +}