From c70de2b590e86985516dfa29ed105d2539f6b740 Mon Sep 17 00:00:00 2001 From: dev747368 <48332326+dev747368@users.noreply.github.com> Date: Thu, 15 May 2025 16:20:27 -0400 Subject: [PATCH 1/2] GP-2941 better defined string iterator Don't iterate arrays that can't contain a string. When iterating a struct, only look at defined fields. Should address issues #8134, #5726, #3498 --- .../TranslateStringsScript.java | 10 +- .../analysis/rust/RustStringAnalyzer.java | 9 +- .../core/string/CombinedStringSearcher.java | 8 +- ...r.java => FoundDefinedStringIterator.java} | 8 +- .../core/strings/ViewStringsTableModel.java | 8 +- ...va => FoundDefinedStringIteratorTest.java} | 12 +- .../program/util/DefinedDataIteratorTest.java | 10 +- .../util/DefinedStringIteratorTest.java | 169 +++++++++++++ .../string/variadic/FormatStringAnalyzer.java | 9 +- .../program/util/DefinedDataIterator.java | 46 +--- .../program/util/DefinedStringIterator.java | 234 ++++++++++++++++++ 11 files changed, 442 insertions(+), 81 deletions(-) rename Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/{DefinedStringIterator.java => FoundDefinedStringIterator.java} (92%) rename Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/string/{DefinedStringIteratorTest.java => FoundDefinedStringIteratorTest.java} (95%) create mode 100644 Ghidra/Features/Base/src/test/java/ghidra/program/util/DefinedStringIteratorTest.java create mode 100644 Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DefinedStringIterator.java diff --git a/Ghidra/Features/Base/ghidra_scripts/TranslateStringsScript.java b/Ghidra/Features/Base/ghidra_scripts/TranslateStringsScript.java index 7d49f7ea86..73bd7d4203 100644 --- a/Ghidra/Features/Base/ghidra_scripts/TranslateStringsScript.java +++ b/Ghidra/Features/Base/ghidra_scripts/TranslateStringsScript.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,8 +20,7 @@ import ghidra.app.script.GhidraScript; import ghidra.program.model.data.StringDataInstance; import ghidra.program.model.data.TranslationSettingsDefinition; import ghidra.program.model.listing.Data; -import ghidra.program.util.DefinedDataIterator; -import util.CollectionUtils; +import ghidra.program.util.DefinedStringIterator; public class TranslateStringsScript extends GhidraScript { @@ -40,8 +39,7 @@ public class TranslateStringsScript extends GhidraScript { int count = 0; monitor.initialize(currentProgram.getListing().getNumDefinedData()); monitor.setMessage("Translating strings"); - for (Data data : CollectionUtils.asIterable( - DefinedDataIterator.definedStrings(currentProgram, currentSelection))) { + for (Data data : DefinedStringIterator.forProgram(currentProgram, currentSelection)) { if (monitor.isCancelled()) { break; } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustStringAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustStringAnalyzer.java index dc31b5f5bc..9ab182a422 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustStringAnalyzer.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/rust/RustStringAnalyzer.java @@ -19,10 +19,11 @@ import ghidra.app.services.*; import ghidra.app.util.importer.MessageLog; import ghidra.framework.options.Options; import ghidra.program.model.address.*; -import ghidra.program.model.data.*; +import ghidra.program.model.data.DataUtilities; +import ghidra.program.model.data.StringDataType; import ghidra.program.model.listing.Data; import ghidra.program.model.listing.Program; -import ghidra.program.util.DefinedDataIterator; +import ghidra.program.util.DefinedStringIterator; import ghidra.util.exception.CancelledException; import ghidra.util.task.TaskMonitor; @@ -51,9 +52,7 @@ public class RustStringAnalyzer extends AbstractAnalyzer { public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws CancelledException { - DefinedDataIterator dataIterator = DefinedDataIterator.definedStrings(program); - - for (Data data : dataIterator) { + for (Data data : DefinedStringIterator.forProgram(program)) { Address start = data.getAddress(); int length = data.getLength(); diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/CombinedStringSearcher.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/CombinedStringSearcher.java index 97ae5cd700..74ac10d282 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/CombinedStringSearcher.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/CombinedStringSearcher.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -31,7 +31,7 @@ import ghidra.util.task.TaskMonitor; public class CombinedStringSearcher { - private DefinedStringIterator definedStringIterator; + private FoundDefinedStringIterator definedStringIterator; private FoundString nextDefinedString; private Program program; private StringTableOptions options; @@ -44,7 +44,7 @@ public class CombinedStringSearcher { this.options = options; this.accumulator = accumulator; definedStringIterator = - new DefinedStringIterator(program, options.getWordModelInitialized()); + new FoundDefinedStringIterator(program, options.getWordModelInitialized()); } private FoundString findNextDefinedString() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/DefinedStringIterator.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/FoundDefinedStringIterator.java similarity index 92% rename from Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/DefinedStringIterator.java rename to Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/FoundDefinedStringIterator.java index 63bd2e94c5..00bf1b3706 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/DefinedStringIterator.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/string/FoundDefinedStringIterator.java @@ -20,7 +20,7 @@ import static ghidra.program.util.string.FoundString.DefinedState.*; import java.util.Iterator; import ghidra.program.model.listing.*; -import ghidra.program.util.DefinedDataIterator; +import ghidra.program.util.DefinedStringIterator; import ghidra.program.util.string.FoundString; /** @@ -42,16 +42,16 @@ import ghidra.program.util.string.FoundString; *

* The iterator is over when the resultQueue is empty and the defined data iterator's hasNext() method is false. */ -public class DefinedStringIterator implements Iterator { +public class FoundDefinedStringIterator implements Iterator { private boolean isWordModelInitialized; private DataIterator stringDataIterator; private Program program; - DefinedStringIterator(Program program, boolean isWordModelInitialized) { + FoundDefinedStringIterator(Program program, boolean isWordModelInitialized) { this.program = program; this.isWordModelInitialized = isWordModelInitialized; - this.stringDataIterator = DefinedDataIterator.definedStrings(program); + this.stringDataIterator = DefinedStringIterator.forProgram(program); } @Override diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsTableModel.java index 4406f7fd26..83796b2e65 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/strings/ViewStringsTableModel.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -122,7 +122,7 @@ class ViewStringsTableModel extends AddressBasedTableModel { monitor.setCancelEnabled(true); monitor.initialize(listing.getNumDefinedData()); Swing.allowSwingToProcessEvents(); - for (Data stringInstance : DefinedDataIterator.definedStrings(localProgram)) { + for (Data stringInstance : DefinedStringIterator.forProgram(localProgram)) { accumulator.add(createIndexedStringInstanceLocation(localProgram, stringInstance)); monitor.increment(); } @@ -148,7 +148,7 @@ class ViewStringsTableModel extends AddressBasedTableModel { } public void addDataInstance(Program localProgram, Data data) { - for (Data stringInstance : DefinedDataIterator.definedStrings(data)) { + for (Data stringInstance : DefinedStringIterator.forDataInstance(data)) { addObject(createIndexedStringInstanceLocation(localProgram, stringInstance)); } } diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/string/DefinedStringIteratorTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/string/FoundDefinedStringIteratorTest.java similarity index 95% rename from Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/string/DefinedStringIteratorTest.java rename to Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/string/FoundDefinedStringIteratorTest.java index bb09566f6e..f92a1c6a0e 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/string/DefinedStringIteratorTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/string/FoundDefinedStringIteratorTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -30,12 +30,12 @@ import ghidra.program.util.string.FoundString; import ghidra.test.AbstractGhidraHeadlessIntegrationTest; import ghidra.test.ToyProgramBuilder; -public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegrationTest { +public class FoundDefinedStringIteratorTest extends AbstractGhidraHeadlessIntegrationTest { private ProgramDB program; private ArrayDataType arrayDataType; - public DefinedStringIteratorTest() { + public FoundDefinedStringIteratorTest() { super(); } @@ -87,7 +87,7 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration @Test public void testIterator() throws Exception { - DefinedStringIterator iterator = new DefinedStringIterator(program, false); + FoundDefinedStringIterator iterator = new FoundDefinedStringIterator(program, false); assertTrue(iterator.hasNext()); FoundString foundString = iterator.next(); @@ -143,7 +143,7 @@ public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegration initializeStringModel(); - DefinedStringIterator iterator = new DefinedStringIterator(program, true); + FoundDefinedStringIterator iterator = new FoundDefinedStringIterator(program, true); while (iterator.hasNext()) { FoundString string = iterator.next(); Address address = string.getAddress(); diff --git a/Ghidra/Features/Base/src/test/java/ghidra/program/util/DefinedDataIteratorTest.java b/Ghidra/Features/Base/src/test/java/ghidra/program/util/DefinedDataIteratorTest.java index 50b8e8538f..7169507691 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/program/util/DefinedDataIteratorTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/program/util/DefinedDataIteratorTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,6 +20,7 @@ import static org.junit.Assert.*; import java.nio.charset.StandardCharsets; import java.util.List; +import org.apache.commons.collections4.IteratorUtils; import org.junit.Before; import org.junit.Test; @@ -95,7 +96,7 @@ public class DefinedDataIteratorTest extends AbstractGhidraHeadlessIntegrationTe builder.applyFixedLengthDataType("0x100", struct1DT, struct1DT.getLength()); List list = - CollectionUtils.asList((Iterable) DefinedDataIterator.definedStrings(program)); + CollectionUtils.asList((Iterable) DefinedStringIterator.forProgram(program)); assertTrue(list.get(0).getAddress().getOffset() == 0x10); assertTrue(list.get(1).getAddress().getOffset() == 0x100 + 10); @@ -114,8 +115,7 @@ public class DefinedDataIteratorTest extends AbstractGhidraHeadlessIntegrationTe int lastEle = numElements - 1; int elementSize = structArray.getElementLength(); - List list = - CollectionUtils.asList((Iterable) DefinedDataIterator.definedStrings(program)); + List list = IteratorUtils.toList(DefinedStringIterator.forProgram(program)); assertEquals(list.get(0).getAddress().getOffset(), 0x10); assertEquals(list.get(1 + 0).getAddress().getOffset(), 0x100 + 10); diff --git a/Ghidra/Features/Base/src/test/java/ghidra/program/util/DefinedStringIteratorTest.java b/Ghidra/Features/Base/src/test/java/ghidra/program/util/DefinedStringIteratorTest.java new file mode 100644 index 0000000000..0eae524e88 --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/program/util/DefinedStringIteratorTest.java @@ -0,0 +1,169 @@ +/* ### + * 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.util; + +import static org.junit.Assert.*; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.apache.commons.collections4.IteratorUtils; +import org.junit.Before; +import org.junit.Test; + +import ghidra.program.database.ProgramDB; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.Data; +import ghidra.test.AbstractGhidraHeadlessIntegrationTest; +import ghidra.test.ToyProgramBuilder; + +public class DefinedStringIteratorTest extends AbstractGhidraHeadlessIntegrationTest { + + private ToyProgramBuilder builder; + private ProgramDB program; + private DataTypeManager dtm; + private DataType intDT; + private StringDataType stringDT; + private CharDataType charDT; + private DataType charArray; + private StructureDataType struct1DT; + private int s1f2Offset = 10; + private int s1f3Offset = 50; + private ArrayDataType structArray; + private StructureDataType struct2DT; + + @Before + public void setUp() throws Exception { + + builder = new ToyProgramBuilder("DefinedStringIteratorTests", false); + program = builder.getProgram(); + dtm = program.getDataTypeManager(); + + intDT = AbstractIntegerDataType.getSignedDataType(4, dtm); + stringDT = StringDataType.dataType; + charDT = new CharDataType(dtm); + charArray = new ArrayDataType(charDT, 20); + + struct1DT = new StructureDataType("struct1", 100); + struct1DT.replaceAtOffset(0, intDT, -1, "f1", null); + struct1DT.replaceAtOffset(s1f2Offset, charArray, -1, "f2", null); + struct1DT.replaceAtOffset(s1f3Offset, stringDT, 10, "f3", null); + + structArray = new ArrayDataType(struct1DT, 10, -1); + + struct2DT = new StructureDataType("struct2", 200); + struct2DT.replaceAtOffset(0, intDT, -1, "f1", null); + struct2DT.replaceAtOffset(10, struct1DT, -1, "f2", null); + + builder.createMemory("test", "0x0", 0x20000); + program = builder.getProgram(); + } + + private String addrStr(long offset) { + return builder.getAddress(offset).toString(); + } + + @Test + public void test_Strings() throws Exception { + int str1Addr = 0x10; + int struct1Addr = 0x100; + + builder.applyFixedLengthDataType("0x0", intDT, -1); + builder.createString(addrStr(str1Addr), "test1", StandardCharsets.UTF_8, true, stringDT); + builder.applyFixedLengthDataType(addrStr(struct1Addr), struct1DT, -1); + + List list = IteratorUtils.toList(DefinedStringIterator.forProgram(program)); + + assertEquals(str1Addr, list.get(0).getAddress().getOffset()); + assertEquals(struct1Addr + s1f2Offset, list.get(1).getAddress().getOffset()); + assertEquals(struct1Addr + s1f3Offset, list.get(2).getAddress().getOffset()); + + assertEquals(3, list.size()); + } + + @Test + public void test_AllowIteratorNextWithoutHasNextIck() throws Exception { + int str1Addr = 0x10; + int struct1Addr = 0x100; + + builder.applyFixedLengthDataType("0x0", intDT, -1); + builder.createString(addrStr(str1Addr), "test1", StandardCharsets.UTF_8, true, stringDT); + builder.applyFixedLengthDataType(addrStr(struct1Addr), struct1DT, -1); + + DefinedStringIterator it = DefinedStringIterator.forProgram(program); + + assertEquals(str1Addr, it.next().getAddress().getOffset()); + assertEquals(struct1Addr + s1f2Offset, it.next().getAddress().getOffset()); + assertEquals(struct1Addr + s1f3Offset, it.next().getAddress().getOffset()); + + assertFalse(it.hasNext()); + } + + @Test + public void test_ArrayOfStructs() throws Exception { + int str1Addr = 0x10; + int s1ArrayAddr = 0x100; + + builder.applyFixedLengthDataType("0x0", intDT, -1); + builder.createString(addrStr(str1Addr), "test1", StandardCharsets.UTF_8, true, stringDT); + builder.applyFixedLengthDataType(addrStr(s1ArrayAddr), structArray, -1); + + int numElements = structArray.getNumElements(); + int lastEle = numElements - 1; + int elementSize = structArray.getElementLength(); + + List list = IteratorUtils.toList(DefinedStringIterator.forProgram(program)); + + assertEquals(str1Addr, list.get(0).getAddress().getOffset()); + + int s1ArrayElemIndex = 1; + assertEquals(arrayElementAddr(s1ArrayAddr, elementSize, 0) + s1f2Offset, + list.get(s1ArrayElemIndex + 0).getAddress().getOffset()); + assertEquals(arrayElementAddr(s1ArrayAddr, elementSize, 0) + s1f3Offset, + list.get(s1ArrayElemIndex + 1).getAddress().getOffset()); + + s1ArrayElemIndex = s1ArrayElemIndex + (lastEle * 2 /* 2 strs per struct */); + assertEquals(arrayElementAddr(s1ArrayAddr, elementSize, lastEle) + s1f2Offset, + list.get(s1ArrayElemIndex + 0).getAddress().getOffset()); + assertEquals(arrayElementAddr(s1ArrayAddr, elementSize, lastEle) + s1f3Offset, + list.get(s1ArrayElemIndex + 1).getAddress().getOffset()); + + assertEquals(s1ArrayElemIndex + 2, list.size()); + } + + private long arrayElementAddr(long arrayAddr, int elemSize, int elemIndex) { + return arrayAddr + (elemSize * elemIndex); + } + + @Test + public void test_DontLookAtArrayElements() throws Exception { + int str1Addr = 0x10; + int s1ArrayAddr = 0x100; + + builder.applyFixedLengthDataType("0x0", intDT, -1); // +1 candidate count + builder.createString(addrStr(str1Addr), "test1", StandardCharsets.UTF_8, true, stringDT); // +1 + builder.applyFixedLengthDataType(addrStr(s1ArrayAddr), structArray, -1); // +(1 + 10*3) + + DataType byteArray = new ArrayDataType(ByteDataType.dataType, 2000, -1); + builder.applyFixedLengthDataType("0x1000", byteArray, -1); // +1 + + DefinedStringIterator it = DefinedStringIterator.forProgram(program); + List list = IteratorUtils.toList(it); + + assertEquals(34, it.getDataCandidateCount()); // 1 + 1 + 1 + 10*3 + assertEquals(21, list.size()); // 1 ds@0x10 + 2 per structArray element + } +} diff --git a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringAnalyzer.java b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringAnalyzer.java index 85b7ef8025..79b752a361 100644 --- a/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringAnalyzer.java +++ b/Ghidra/Features/DecompilerDependent/src/main/java/ghidra/app/plugin/core/string/variadic/FormatStringAnalyzer.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -30,7 +30,7 @@ import ghidra.program.model.data.*; import ghidra.program.model.listing.*; import ghidra.program.model.pcode.HighFunctionDBUtil; import ghidra.program.model.pcode.PcodeOpAST; -import ghidra.program.util.DefinedDataIterator; +import ghidra.program.util.DefinedStringIterator; import ghidra.util.Msg; import ghidra.util.exception.CancelledException; import ghidra.util.exception.InvalidInputException; @@ -101,9 +101,8 @@ public class FormatStringAnalyzer extends AbstractAnalyzer { private void run(AddressSetView selection, TaskMonitor monitor) throws CancelledException { - DefinedDataIterator dataIterator = DefinedDataIterator.definedStrings(currentProgram); Map stringsByAddress = new HashMap<>(); - for (Data data : dataIterator) { + for (Data data : DefinedStringIterator.forProgram(currentProgram)) { String s = data.getDefaultValueRepresentation(); if (s.contains("%")) { stringsByAddress.put(data.getAddress(), data); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DefinedDataIterator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DefinedDataIterator.java index 11e04f48f9..0a6f2788db 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DefinedDataIterator.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DefinedDataIterator.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -28,6 +28,8 @@ import ghidra.program.model.listing.*; *

* Data elements that are nested inside of composites or arrays are visited, not just the * parent/containing data element. + *

+ * Not thread safe. */ public class DefinedDataIterator implements DataIterator { @@ -71,46 +73,6 @@ public class DefinedDataIterator implements DataIterator { return new DefinedDataIterator(program, null, null, dataInstancePredicate); } - /** - * Creates a new iterator that traverses the entire Program's address space returning - * data instances that are strings. - * - * @param program Ghidra {@link Program} to search - * @return new iterator - */ - public static DefinedDataIterator definedStrings(Program program) { - return new DefinedDataIterator(program, null, - dataType -> StringDataInstance.isStringDataType(dataType), - data -> StringDataInstance.isString(data)); - } - - /** - * Creates a new iterator that traverses a portion of the Program's address space returning - * data instances that are strings. - * - * @param program Ghidra {@link Program} to search - * @param addrs addresses to limit the iteration to - * @return new iterator - */ - public static DefinedDataIterator definedStrings(Program program, AddressSetView addrs) { - return new DefinedDataIterator(program, addrs, - dataType -> StringDataInstance.isStringDataType(dataType), - data -> StringDataInstance.isString(data)); - } - - /** - * Creates a new iterator that traverses the address space of a single data item (ie. a - * composite or array data instance that needs to be recursed into). - * - * @param singleDataInstance Data instance - * @return new iterator - */ - public static DefinedDataIterator definedStrings(Data singleDataInstance) { - return new DefinedDataIterator(singleDataInstance, - dataType -> StringDataInstance.isStringDataType(dataType), - data -> StringDataInstance.isString(data)); - } - private Predicate dataTypePredicate; private Predicate dataInstancePredicate; diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DefinedStringIterator.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DefinedStringIterator.java new file mode 100644 index 0000000000..04f8f1b725 --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/util/DefinedStringIterator.java @@ -0,0 +1,234 @@ +/* ### + * 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.util; + +import java.util.*; + +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.*; + +/** + * Iterator that visits each defined string instance in the initialized memory of a Program + * or in the footprint of a specified data element. + *

+ * Strings that are nested inside of composites or arrays are visited, not just the + * parent/containing data element. + *

+ * Not thread safe. + */ +public class DefinedStringIterator implements DataIterator { + + /** + * Creates a new iterator that traverses the entire Program's address space returning + * data instances that are strings. + * + * @param program Ghidra {@link Program} to search + * @return new iterator + */ + public static DefinedStringIterator forProgram(Program program) { + return new DefinedStringIterator(program, null); + } + + /** + * Creates a new iterator that traverses a portion of the Program's address space returning + * data instances that are strings. + * + * @param program Ghidra {@link Program} to search + * @param addrs addresses to limit the iteration to + * @return new iterator + */ + public static DefinedStringIterator forProgram(Program program, AddressSetView addrs) { + return new DefinedStringIterator(program, addrs); + } + + /** + * Creates a new iterator that traverses the address space of a single data item (ie. a + * composite or array data instance that needs to be recursed into). + * + * @param singleDataInstance Data instance + * @return new iterator + */ + public static DefinedStringIterator forDataInstance(Data singleDataInstance) { + return new DefinedStringIterator(singleDataInstance); + } + + /** + * LIFO stack of iterators. Newly found iterators of sub-components are + * pushed onto the end and become the current iterator. When an iterator is exhausted, + * it is popped of the end and the uncovered iterator is now the current. + */ + private Deque itStack = new ArrayDeque<>(); + private Data currentDataResult; + private int dataCandidateCount; // mostly for tests to ensure we aren't doing unneeded work + + private DefinedStringIterator(Program program, AddressSetView addrs) { + + itStack.addLast(program.getListing() + .getDefinedData( + (addrs == null) ? program.getMemory().getAllInitializedAddressSet() : addrs, + true)); + } + + private DefinedStringIterator(Data singleDataInstance) { + itStack.addLast(DataIterator.of(singleDataInstance)); + } + + @Override + public boolean hasNext() { + updateNextResultIfNeeded(); + return currentDataResult != null; + } + + @Override + public Data next() { + updateNextResultIfNeeded(); + if (currentDataResult == null) { + throw new NoSuchElementException(); + } + Data result = currentDataResult; + currentDataResult = null; + return result; + } + + public int getDataCandidateCount() { + return dataCandidateCount; + } + + private DataIterator currentIt() { + DataIterator it = null; + while ((it = itStack.peekLast()) != null && !it.hasNext()) { + itStack.removeLast(); + } + return it; + } + + private void updateNextResultIfNeeded() { + DataIterator it = null; + while (currentDataResult == null && (it = currentIt()) != null) { + dataCandidateCount++; + + Data data = it.next(); + DataType dt = data.getBaseDataType(); + + if (StringDataInstance.isString(data)) { + currentDataResult = data; + return; + } + else if (dt instanceof Array arrayDT) { + // Will only get here for arrays of stuff that aren't char/wchar/int, which will + // be handled earlier by isString(data) + DataType elementDT = arrayDT.getDataType(); + if (containsStringDataType(elementDT)) { + itStack.addLast(new ArrayElementIterator(data)); + } + // side-effect: don't iterate arrays that have elements that aren't strings + } + else if (dt instanceof Composite comp && containsStringDataType(comp)) { + itStack.addLast(new StructDtcIterator(data, comp)); + } + } + } + + private boolean containsStringDataType(DataType dt) { + if (StringDataInstance.isStringDataType(dt)) { + return true; + } + else if (dt instanceof Array arrayDT) { + DataType elementDT = arrayDT.getDataType(); + return arrayDT.getNumElements() != 0 && containsStringDataType(elementDT); + } + else if (dt instanceof Structure struct) { + // handle Structures and general Composite's separately so + // we can focus on just the defined elements of a structure + for (DataTypeComponent dtc : struct.getDefinedComponents()) { + if (dtc.getLength() != 0 && containsStringDataType(dtc.getDataType())) { + return true; + } + } + return false; + } + else if (dt instanceof Composite comp) { + for (DataTypeComponent dtc : comp.getComponents()) { + if (containsStringDataType(dtc.getDataType())) { + return true; + } + } + return false; + } + else if (dt instanceof TypeDef tdDT) { + return containsStringDataType(tdDT.getBaseDataType()); + } + return false; + } + + private static class StructDtcIterator implements DataIterator { + private Data data; + private int currentIndex; + private DataTypeComponent[] dtcs; + + public StructDtcIterator(Data data, Composite compDT) { + this.data = data; + this.dtcs = compDT.getDefinedComponents(); + advanceToNextGoodDtcIndex(); + } + + @Override + public boolean hasNext() { + return currentIndex < dtcs.length; + } + + private void advanceToNextGoodDtcIndex() { + currentIndex++; + while (currentIndex < dtcs.length && + (dtcs[currentIndex].getLength() == 0 || dtcs[currentIndex].isBitFieldComponent())) { + currentIndex++; + } + } + + @Override + public Data next() { + Data result = data.getComponentContaining(dtcs[currentIndex].getOffset()); + advanceToNextGoodDtcIndex(); + return result; + } + + } + + private static class ArrayElementIterator implements DataIterator { + private Data data; + private int currentIndex; + private int elementCount; + + public ArrayElementIterator(Data data) { + this.data = data; + this.elementCount = data.getNumComponents(); + } + + @Override + public boolean hasNext() { + return currentIndex < elementCount; + } + + @Override + public Data next() { + Data result = data.getComponent(currentIndex); + currentIndex++; + return result; + } + + } +} From d2d0c989f92c7f189acb0bf1bf2fded62da4f044 Mon Sep 17 00:00:00 2001 From: James <49045138+ghidracadabra@users.noreply.github.com> Date: Thu, 15 May 2025 16:50:59 -0400 Subject: [PATCH 2/2] GP-5641 update SetExecutableCategoryScript --- .../SetExecutableCategoryScript.java | 36 +++++++++++++------ 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/Ghidra/Features/BSim/ghidra_scripts/SetExecutableCategoryScript.java b/Ghidra/Features/BSim/ghidra_scripts/SetExecutableCategoryScript.java index 7eea56de45..bb4b74caad 100644 --- a/Ghidra/Features/BSim/ghidra_scripts/SetExecutableCategoryScript.java +++ b/Ghidra/Features/BSim/ghidra_scripts/SetExecutableCategoryScript.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,6 +16,7 @@ import org.apache.commons.lang3.StringUtils; import ghidra.app.script.GhidraScript; +import ghidra.features.base.values.GhidraValuesMap; import ghidra.framework.options.Options; import ghidra.program.model.listing.Program; @@ -23,6 +24,8 @@ import ghidra.program.model.listing.Program; //sets a property on the current program which can be used as //an executable category in BSim public class SetExecutableCategoryScript extends GhidraScript { + private static final String PROPERTY_NAME = "Property Name"; + private static final String PROPERTY_VALUE = "Property Value"; @Override protected void run() throws Exception { @@ -30,16 +33,27 @@ public class SetExecutableCategoryScript extends GhidraScript { popup("This script requires a program"); return; } + GhidraValuesMap valuesMap = new GhidraValuesMap(); + valuesMap.defineString(PROPERTY_NAME); + valuesMap.defineString(PROPERTY_VALUE); + + valuesMap.setValidator((values, status) -> { + String name = valuesMap.getString(PROPERTY_NAME); + if (StringUtils.isAllBlank(name)) { + status.setStatusText("Name cannot be blank"); + return false; + } + String value = valuesMap.getString(PROPERTY_VALUE); + if (StringUtils.isAllBlank(value)) { + status.setStatusText("Value cannot be blank"); + return false; + } + return true; + }); + + askValues("Set Program Property", "Set Program Property", valuesMap); Options opts = currentProgram.getOptions(Program.PROGRAM_INFO); - String name = askString("Enter Property Name", "Name"); - if (StringUtils.isAllBlank(name)) { - return; - } - String value = askString("Enter Value of Property " + name, "Value"); - if (StringUtils.isAllBlank(value)) { - return; - } - opts.setString(name, value); + opts.setString(valuesMap.getString(PROPERTY_NAME), valuesMap.getString(PROPERTY_VALUE)); } }