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));
}
}