mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 13:16:48 +08:00
Merge remote-tracking branch 'origin/GP-6261_ghidragon_mem_search_refactor--SQUASHED'
This commit is contained in:
+37
-28
@@ -18,6 +18,7 @@ package ghidra.features.base.memsearch.combiner;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
@@ -35,10 +36,10 @@ public enum Combiner {
|
||||
B_MINUS_A("B-A", Combiner::reverseSubtract);
|
||||
|
||||
private String name;
|
||||
private BiFunction<List<MemoryMatch>, List<MemoryMatch>, Collection<MemoryMatch>> function;
|
||||
private BiFunction<List<MemoryMatch<SearchData>>, List<MemoryMatch<SearchData>>, Collection<MemoryMatch<SearchData>>> function;
|
||||
|
||||
private Combiner(String name,
|
||||
BiFunction<List<MemoryMatch>, List<MemoryMatch>, Collection<MemoryMatch>> function) {
|
||||
BiFunction<List<MemoryMatch<SearchData>>, List<MemoryMatch<SearchData>>, Collection<MemoryMatch<SearchData>>> function) {
|
||||
this.name = name;
|
||||
this.function = function;
|
||||
}
|
||||
@@ -51,23 +52,25 @@ public enum Combiner {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Collection<MemoryMatch> combine(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
|
||||
public Collection<MemoryMatch<SearchData>> combine(List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
return function.apply(matches1, matches2);
|
||||
}
|
||||
|
||||
private static Collection<MemoryMatch> replace(List<MemoryMatch> matches1,
|
||||
List<MemoryMatch> matches2) {
|
||||
private static Collection<MemoryMatch<SearchData>> replace(
|
||||
List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
|
||||
return matches2;
|
||||
}
|
||||
|
||||
private static Collection<MemoryMatch> union(List<MemoryMatch> matches1,
|
||||
List<MemoryMatch> matches2) {
|
||||
private static Collection<MemoryMatch<SearchData>> union(List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
|
||||
Map<Address, MemoryMatch> matches1Map = createMap(matches1);
|
||||
for (MemoryMatch match2 : matches2) {
|
||||
Map<Address, MemoryMatch<SearchData>> matches1Map = createMap(matches1);
|
||||
for (MemoryMatch<SearchData> match2 : matches2) {
|
||||
Address address = match2.getAddress();
|
||||
MemoryMatch match1 = matches1Map.get(address);
|
||||
MemoryMatch<SearchData> match1 = matches1Map.get(address);
|
||||
if (match1 == null || match2.getLength() > match1.getLength()) {
|
||||
matches1Map.put(address, match2);
|
||||
}
|
||||
@@ -75,50 +78,56 @@ public enum Combiner {
|
||||
return matches1Map.values();
|
||||
}
|
||||
|
||||
private static Collection<MemoryMatch> intersect(List<MemoryMatch> matches1,
|
||||
List<MemoryMatch> matches2) {
|
||||
private static Collection<MemoryMatch<SearchData>> intersect(
|
||||
List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
|
||||
List<MemoryMatch> intersection = new ArrayList<>();
|
||||
Map<Address, MemoryMatch> matches1Map = createMap(matches1);
|
||||
List<MemoryMatch<SearchData>> intersection = new ArrayList<>();
|
||||
Map<Address, MemoryMatch<SearchData>> matches1Map = createMap(matches1);
|
||||
|
||||
for (MemoryMatch match2 : matches2) {
|
||||
for (MemoryMatch<SearchData> match2 : matches2) {
|
||||
Address address = match2.getAddress();
|
||||
MemoryMatch match1 = matches1Map.get(address);
|
||||
MemoryMatch<SearchData> match1 = matches1Map.get(address);
|
||||
if (match1 != null) {
|
||||
MemoryMatch best = match2.getLength() > match1.getLength() ? match2 : match1;
|
||||
MemoryMatch<SearchData> best =
|
||||
match2.getLength() > match1.getLength() ? match2 : match1;
|
||||
intersection.add(best);
|
||||
}
|
||||
}
|
||||
return intersection;
|
||||
}
|
||||
|
||||
private static List<MemoryMatch> xor(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
|
||||
List<MemoryMatch> results = new ArrayList<>();
|
||||
private static List<MemoryMatch<SearchData>> xor(List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
List<MemoryMatch<SearchData>> results = new ArrayList<>();
|
||||
results.addAll(subtract(matches1, matches2));
|
||||
results.addAll(subtract(matches2, matches1));
|
||||
return results;
|
||||
}
|
||||
|
||||
private static Collection<MemoryMatch> subtract(List<MemoryMatch> matches1,
|
||||
List<MemoryMatch> matches2) {
|
||||
private static Collection<MemoryMatch<SearchData>> subtract(
|
||||
List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
|
||||
Map<Address, MemoryMatch> matches1Map = createMap(matches1);
|
||||
Map<Address, MemoryMatch<SearchData>> matches1Map = createMap(matches1);
|
||||
|
||||
for (MemoryMatch match2 : matches2) {
|
||||
for (MemoryMatch<SearchData> match2 : matches2) {
|
||||
Address address = match2.getAddress();
|
||||
matches1Map.remove(address);
|
||||
}
|
||||
return matches1Map.values();
|
||||
}
|
||||
|
||||
private static Collection<MemoryMatch> reverseSubtract(List<MemoryMatch> matches1,
|
||||
List<MemoryMatch> matches2) {
|
||||
private static Collection<MemoryMatch<SearchData>> reverseSubtract(
|
||||
List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
return subtract(matches2, matches1);
|
||||
}
|
||||
|
||||
private static Map<Address, MemoryMatch> createMap(List<MemoryMatch> matches) {
|
||||
Map<Address, MemoryMatch> map = new HashMap<>();
|
||||
for (MemoryMatch result : matches) {
|
||||
private static Map<Address, MemoryMatch<SearchData>> createMap(
|
||||
List<MemoryMatch<SearchData>> matches) {
|
||||
Map<Address, MemoryMatch<SearchData>> map = new HashMap<>();
|
||||
for (MemoryMatch<SearchData> result : matches) {
|
||||
map.put(result.getAddress(), result);
|
||||
}
|
||||
return map;
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ class BinarySearchFormat extends SearchFormat {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatcher parse(String input, SearchSettings settings) {
|
||||
public UserInputByteMatcher parse(String input, SearchSettings settings) {
|
||||
input = input.trim();
|
||||
if (input.isBlank()) {
|
||||
return new InvalidByteMatcher("");
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ class DecimalSearchFormat extends SearchFormat {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatcher parse(String input, SearchSettings settings) {
|
||||
public UserInputByteMatcher parse(String input, SearchSettings settings) {
|
||||
input = input.trim();
|
||||
if (input.isBlank()) {
|
||||
return new InvalidByteMatcher("");
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ class FloatSearchFormat extends SearchFormat {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatcher parse(String input, SearchSettings settings) {
|
||||
public UserInputByteMatcher parse(String input, SearchSettings settings) {
|
||||
input = input.trim();
|
||||
if (input.isBlank()) {
|
||||
return new InvalidByteMatcher("");
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ class HexSearchFormat extends SearchFormat {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatcher parse(String input, SearchSettings settings) {
|
||||
public UserInputByteMatcher parse(String input, SearchSettings settings) {
|
||||
input = input.trim();
|
||||
if (input.isBlank()) {
|
||||
return new InvalidByteMatcher("");
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ class RegExSearchFormat extends SearchFormat {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatcher parse(String input, SearchSettings settings) {
|
||||
public UserInputByteMatcher parse(String input, SearchSettings settings) {
|
||||
input = input.trim();
|
||||
if (input.isBlank()) {
|
||||
return new InvalidByteMatcher("");
|
||||
|
||||
+3
-2
@@ -17,6 +17,7 @@ package ghidra.features.base.memsearch.format;
|
||||
|
||||
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
|
||||
|
||||
/**
|
||||
* SearchFormats are responsible for parsing user input data into a {@link ByteMatcher} that
|
||||
@@ -57,7 +58,7 @@ public abstract class SearchFormat {
|
||||
* @param settings the current search/parse settings
|
||||
* @return a ByteMatcher that can be used for searching bytes (or an error version of a matcher)
|
||||
*/
|
||||
public abstract ByteMatcher parse(String input, SearchSettings settings);
|
||||
public abstract UserInputByteMatcher parse(String input, SearchSettings settings);
|
||||
|
||||
/**
|
||||
* Returns a tool tip describing this search format
|
||||
@@ -152,7 +153,7 @@ public abstract class SearchFormat {
|
||||
}
|
||||
|
||||
protected boolean isValidText(String text, SearchSettings settings) {
|
||||
ByteMatcher byteMatcher = parse(text, settings);
|
||||
UserInputByteMatcher byteMatcher = parse(text, settings);
|
||||
return byteMatcher.isValidSearch();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -36,7 +36,7 @@ class StringSearchFormat extends SearchFormat {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatcher parse(String input, SearchSettings settings) {
|
||||
public UserInputByteMatcher parse(String input, SearchSettings settings) {
|
||||
input = input.trim();
|
||||
if (input.isBlank()) {
|
||||
return new InvalidByteMatcher("");
|
||||
|
||||
+11
-9
@@ -19,6 +19,7 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.features.base.memsearch.combiner.Combiner;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
@@ -29,26 +30,27 @@ import ghidra.util.task.TaskMonitor;
|
||||
* Table loader that performs a search and then combines the new results with existing results.
|
||||
*/
|
||||
public class CombinedMatchTableLoader implements MemoryMatchTableLoader {
|
||||
private MemorySearcher memSearcher;
|
||||
private List<MemoryMatch> previousResults;
|
||||
private MemorySearcher<SearchData> memSearcher;
|
||||
private List<MemoryMatch<SearchData>> previousResults;
|
||||
private Combiner combiner;
|
||||
private boolean completedSearch;
|
||||
private MemoryMatch firstMatch;
|
||||
private MemoryMatch<SearchData> firstMatch;
|
||||
|
||||
public CombinedMatchTableLoader(MemorySearcher memSearcher,
|
||||
List<MemoryMatch> previousResults, Combiner combiner) {
|
||||
public CombinedMatchTableLoader(MemorySearcher<SearchData> memSearcher,
|
||||
List<MemoryMatch<SearchData>> previousResults, Combiner combiner) {
|
||||
this.memSearcher = memSearcher;
|
||||
this.previousResults = previousResults;
|
||||
this.combiner = combiner;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
|
||||
ListAccumulator<MemoryMatch> listAccumulator = new ListAccumulator<>();
|
||||
public void loadResults(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor) {
|
||||
ListAccumulator<MemoryMatch<SearchData>> listAccumulator = new ListAccumulator<>();
|
||||
completedSearch = memSearcher.findAll(listAccumulator, monitor);
|
||||
List<MemoryMatch> followOnResults = listAccumulator.asList();
|
||||
List<MemoryMatch<SearchData>> followOnResults = listAccumulator.asList();
|
||||
firstMatch = followOnResults.isEmpty() ? null : followOnResults.get(0);
|
||||
Collection<MemoryMatch> results = combiner.combine(previousResults, followOnResults);
|
||||
Collection<MemoryMatch<SearchData>> results =
|
||||
combiner.combine(previousResults, followOnResults);
|
||||
accumulator.addAll(results);
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@@ -25,7 +26,7 @@ import ghidra.util.task.TaskMonitor;
|
||||
public class EmptyMemoryMatchTableLoader implements MemoryMatchTableLoader {
|
||||
|
||||
@Override
|
||||
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
|
||||
public void loadResults(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,7 +41,7 @@ public class EmptyMemoryMatchTableLoader implements MemoryMatchTableLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemoryMatch getFirstMatch() {
|
||||
public MemoryMatch<SearchData> getFirstMatch() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
+11
-9
@@ -17,6 +17,7 @@ package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||
import ghidra.program.model.address.Address;
|
||||
@@ -29,15 +30,16 @@ import ghidra.util.task.TaskMonitor;
|
||||
*/
|
||||
public class FindOnceTableLoader implements MemoryMatchTableLoader {
|
||||
|
||||
private MemorySearcher searcher;
|
||||
private MemorySearcher<SearchData> searcher;
|
||||
private Address address;
|
||||
private List<MemoryMatch> previousResults;
|
||||
private List<MemoryMatch<SearchData>> previousResults;
|
||||
private MemorySearchResultsPanel panel;
|
||||
private MemoryMatch match;
|
||||
private MemoryMatch<SearchData> match;
|
||||
private boolean forward;
|
||||
|
||||
public FindOnceTableLoader(MemorySearcher searcher, Address address,
|
||||
List<MemoryMatch> previousResults, MemorySearchResultsPanel panel, boolean forward) {
|
||||
public FindOnceTableLoader(MemorySearcher<SearchData> searcher, Address address,
|
||||
List<MemoryMatch<SearchData>> previousResults, MemorySearchResultsPanel panel,
|
||||
boolean forward) {
|
||||
this.searcher = searcher;
|
||||
this.address = address;
|
||||
this.previousResults = previousResults;
|
||||
@@ -46,13 +48,13 @@ public class FindOnceTableLoader implements MemoryMatchTableLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
|
||||
public void loadResults(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor) {
|
||||
accumulator.addAll(previousResults);
|
||||
|
||||
match = searcher.findOnce(address, forward, monitor);
|
||||
|
||||
if (match != null) {
|
||||
MemoryMatch existing = findExisingMatch(match.getAddress());
|
||||
MemoryMatch<SearchData> existing = findExisingMatch(match.getAddress());
|
||||
if (existing != null) {
|
||||
existing.updateBytes(match.getBytes());
|
||||
}
|
||||
@@ -62,8 +64,8 @@ public class FindOnceTableLoader implements MemoryMatchTableLoader {
|
||||
}
|
||||
}
|
||||
|
||||
private MemoryMatch findExisingMatch(Address newMatchAddress) {
|
||||
for (MemoryMatch memoryMatch : previousResults) {
|
||||
private MemoryMatch<SearchData> findExisingMatch(Address newMatchAddress) {
|
||||
for (MemoryMatch<SearchData> memoryMatch : previousResults) {
|
||||
if (newMatchAddress.equals(memoryMatch.getAddress())) {
|
||||
return memoryMatch;
|
||||
}
|
||||
|
||||
+19
-16
@@ -27,6 +27,7 @@ import ghidra.app.util.ListingHighlightProvider;
|
||||
import ghidra.app.util.SearchConstants;
|
||||
import ghidra.app.util.viewer.field.*;
|
||||
import ghidra.app.util.viewer.proxy.ProxyObj;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
@@ -38,10 +39,10 @@ import ghidra.program.model.listing.Program;
|
||||
public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
private Navigatable navigatable;
|
||||
private Program program;
|
||||
private List<MemoryMatch> sortedResults;
|
||||
private List<MemoryMatch<SearchData>> sortedResults;
|
||||
private MemoryMatchTableModel model;
|
||||
private MemorySearchOptions options;
|
||||
private MemoryMatch selectedMatch;
|
||||
private MemoryMatch<SearchData> selectedMatch;
|
||||
|
||||
public MemoryMatchHighlighter(Navigatable navigatable, MemoryMatchTableModel model,
|
||||
MemorySearchOptions options) {
|
||||
@@ -92,7 +93,7 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
|
||||
Address minAddr = cu.getMinAddress();
|
||||
Address maxAddr = cu.getMaxAddress();
|
||||
List<MemoryMatch> results = getMatchesInRange(minAddr, maxAddr);
|
||||
List<MemoryMatch<SearchData>> results = getMatchesInRange(minAddr, maxAddr);
|
||||
if (results.isEmpty()) {
|
||||
return NO_HIGHLIGHTS;
|
||||
}
|
||||
@@ -100,13 +101,14 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
return getHighlights(text, minAddr, results);
|
||||
}
|
||||
|
||||
private Highlight[] getHighlights(String text, Address minAddr, List<MemoryMatch> results) {
|
||||
private Highlight[] getHighlights(String text, Address minAddr,
|
||||
List<MemoryMatch<SearchData>> results) {
|
||||
|
||||
Highlight[] highlights = new Highlight[results.size()];
|
||||
int selectedMatchIndex = -1;
|
||||
|
||||
for (int i = 0; i < highlights.length; i++) {
|
||||
MemoryMatch match = results.get(i);
|
||||
MemoryMatch<SearchData> match = results.get(i);
|
||||
Color highlightColor = SearchConstants.SEARCH_HIGHLIGHT_COLOR;
|
||||
if (match == selectedMatch) {
|
||||
selectedMatchIndex = i;
|
||||
@@ -124,7 +126,8 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
return highlights;
|
||||
}
|
||||
|
||||
private Highlight createHighlight(MemoryMatch match, Address start, String text, Color color) {
|
||||
private Highlight createHighlight(MemoryMatch<SearchData> match, Address start, String text,
|
||||
Color color) {
|
||||
int highlightLength = match.getLength();
|
||||
Address address = match.getAddress();
|
||||
int startByteOffset = (int) address.subtract(start);
|
||||
@@ -152,7 +155,7 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
return Math.min(text.length() - 1, pos);
|
||||
}
|
||||
|
||||
List<MemoryMatch> getMatches() {
|
||||
List<MemoryMatch<SearchData>> getMatches() {
|
||||
|
||||
if (sortedResults != null) {
|
||||
return sortedResults;
|
||||
@@ -162,7 +165,7 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<MemoryMatch> modelData = model.getModelData();
|
||||
List<MemoryMatch<SearchData>> modelData = model.getModelData();
|
||||
if (model.isSortedOnAddress()) {
|
||||
return modelData;
|
||||
}
|
||||
@@ -173,8 +176,8 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
return sortedResults;
|
||||
}
|
||||
|
||||
private List<MemoryMatch> getMatchesInRange(Address start, Address end) {
|
||||
List<MemoryMatch> matches = getMatches();
|
||||
private List<MemoryMatch<SearchData>> getMatchesInRange(Address start, Address end) {
|
||||
List<MemoryMatch<SearchData>> matches = getMatches();
|
||||
int startIndex = findFirstIndex(matches, start, end);
|
||||
if (startIndex < 0) {
|
||||
return Collections.emptyList();
|
||||
@@ -185,15 +188,15 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
endIndex++; // end index is non-inclusive and we want to include direct hit
|
||||
}
|
||||
|
||||
List<MemoryMatch> resultList = matches.subList(startIndex, endIndex);
|
||||
List<MemoryMatch<SearchData>> resultList = matches.subList(startIndex, endIndex);
|
||||
return resultList;
|
||||
}
|
||||
|
||||
private int findFirstIndex(List<MemoryMatch> matches, Address start, Address end) {
|
||||
private int findFirstIndex(List<MemoryMatch<SearchData>> matches, Address start, Address end) {
|
||||
|
||||
int startIndex = findIndexAtOrGreater(matches, start);
|
||||
if (startIndex > 0) { // see if address before extends into this range.
|
||||
MemoryMatch resultBefore = matches.get(startIndex - 1);
|
||||
MemoryMatch<SearchData> resultBefore = matches.get(startIndex - 1);
|
||||
Address beforeAddr = resultBefore.getAddress();
|
||||
int length = resultBefore.getLength();
|
||||
if (start.hasSameAddressSpace(beforeAddr) && start.subtract(beforeAddr) < length) {
|
||||
@@ -205,7 +208,7 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
return -1;
|
||||
}
|
||||
|
||||
MemoryMatch result = matches.get(startIndex);
|
||||
MemoryMatch<SearchData> result = matches.get(startIndex);
|
||||
Address addr = result.getAddress();
|
||||
if (end.compareTo(addr) >= 0) {
|
||||
return startIndex;
|
||||
@@ -213,9 +216,9 @@ public class MemoryMatchHighlighter implements ListingHighlightProvider {
|
||||
return -1;
|
||||
}
|
||||
|
||||
private int findIndexAtOrGreater(List<MemoryMatch> matches, Address address) {
|
||||
private int findIndexAtOrGreater(List<MemoryMatch<SearchData>> matches, Address address) {
|
||||
|
||||
MemoryMatch key = new MemoryMatch(address);
|
||||
MemoryMatch<SearchData> key = new MemoryMatch<>(address);
|
||||
int index = Collections.binarySearch(matches, key);
|
||||
if (index < 0) {
|
||||
index = -index - 1;
|
||||
|
||||
+2
-1
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@@ -32,7 +33,7 @@ public interface MemoryMatchTableLoader {
|
||||
* @param accumulator the accumulator to store results that will appear in the results table
|
||||
* @param monitor the task monitor
|
||||
*/
|
||||
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor);
|
||||
public void loadResults(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor);
|
||||
|
||||
/**
|
||||
* Returns true if the search/loading did not fully complete. (Search limit reached, cancelled
|
||||
|
||||
+16
-16
@@ -21,7 +21,7 @@ import docking.widgets.table.*;
|
||||
import generic.theme.GThemeDefaults.Colors.Tables;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.features.base.memsearch.format.SearchFormat;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.*;
|
||||
@@ -39,7 +39,7 @@ import ghidra.util.task.TaskMonitor;
|
||||
/**
|
||||
* Table model for memory search results.
|
||||
*/
|
||||
public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch<SearchData>> {
|
||||
private Color CHANGED_COLOR = Tables.ERROR_UNSELECTED;
|
||||
private Color CHANGED_SELECTED_COLOR = Tables.ERROR_SELECTED;
|
||||
|
||||
@@ -50,8 +50,8 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<MemoryMatch> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<MemoryMatch> descriptor = new TableColumnDescriptor<>();
|
||||
protected TableColumnDescriptor<MemoryMatch<SearchData>> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<MemoryMatch<SearchData>> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(
|
||||
DiscoverableTableUtils.adaptColumForModel(this, new AddressTableColumn()), 1, true);
|
||||
@@ -66,7 +66,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor)
|
||||
protected void doLoad(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
if (loader == null) {
|
||||
return;
|
||||
@@ -92,7 +92,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
}
|
||||
|
||||
ColumnSortState primaryState = sortState.getAllSortStates().get(0);
|
||||
DynamicTableColumn<MemoryMatch, ?, ?> column =
|
||||
DynamicTableColumn<MemoryMatch<SearchData>, ?, ?> column =
|
||||
getColumn(primaryState.getColumnModelIndex());
|
||||
String name = column.getColumnName();
|
||||
if (AddressTableColumn.NAME.equals(name)) {
|
||||
@@ -108,7 +108,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
return null; // we've been disposed
|
||||
}
|
||||
|
||||
DynamicTableColumn<MemoryMatch, ?, ?> column = getColumn(modelColumn);
|
||||
DynamicTableColumn<MemoryMatch<SearchData>, ?, ?> column = getColumn(modelColumn);
|
||||
Class<?> columnClass = column.getClass();
|
||||
if (column instanceof MappedTableColumn mappedColumn) {
|
||||
columnClass = mappedColumn.getMappedColumnClass();
|
||||
@@ -147,7 +147,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
}
|
||||
|
||||
public class MatchBytesColumn
|
||||
extends DynamicTableColumnExtensionPoint<MemoryMatch, String, Program> {
|
||||
extends DynamicTableColumnExtensionPoint<MemoryMatch<SearchData>, String, Program> {
|
||||
|
||||
private ByteArrayRenderer renderer = new ByteArrayRenderer();
|
||||
|
||||
@@ -157,7 +157,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(MemoryMatch match, Settings settings, Program pgm,
|
||||
public String getValue(MemoryMatch<SearchData> match, Settings settings, Program pgm,
|
||||
ServiceProvider service) throws IllegalArgumentException {
|
||||
|
||||
return getByteString(match.getBytes());
|
||||
@@ -188,7 +188,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
}
|
||||
|
||||
public class MatchValueColumn
|
||||
extends DynamicTableColumnExtensionPoint<MemoryMatch, String, Program> {
|
||||
extends DynamicTableColumnExtensionPoint<MemoryMatch<SearchData>, String, Program> {
|
||||
|
||||
private ValueRenderer renderer = new ValueRenderer();
|
||||
|
||||
@@ -198,11 +198,11 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(MemoryMatch match, Settings settings, Program pgm,
|
||||
public String getValue(MemoryMatch<SearchData> match, Settings settings, Program pgm,
|
||||
ServiceProvider service) throws IllegalArgumentException {
|
||||
|
||||
ByteMatcher byteMatcher = match.getByteMatcher();
|
||||
SearchSettings searchSettings = byteMatcher.getSettings();
|
||||
SearchData searchData = match.getPattern();
|
||||
SearchSettings searchSettings = searchData.getSettings();
|
||||
SearchFormat format = searchSettings.getSearchFormat();
|
||||
return format.getValueString(match.getBytes(), searchSettings);
|
||||
}
|
||||
@@ -231,7 +231,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
super.getTableCellRendererComponent(data);
|
||||
MemoryMatch match = (MemoryMatch) data.getRowObject();
|
||||
MemoryMatch<SearchData> match = (MemoryMatch<SearchData>) data.getRowObject();
|
||||
String text = data.getValue().toString();
|
||||
if (match.isChanged()) {
|
||||
text = getHtmlColoredString(match, data.isSelected());
|
||||
@@ -240,7 +240,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
return this;
|
||||
}
|
||||
|
||||
private String getHtmlColoredString(MemoryMatch match, boolean isSelected) {
|
||||
private String getHtmlColoredString(MemoryMatch<SearchData> match, boolean isSelected) {
|
||||
Color color = isSelected ? Tables.ERROR_SELECTED : Tables.ERROR_UNSELECTED;
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
@@ -277,7 +277,7 @@ public class MemoryMatchTableModel extends AddressBasedTableModel<MemoryMatch> {
|
||||
super.getTableCellRendererComponent(data);
|
||||
setText((String) data.getValue());
|
||||
|
||||
MemoryMatch match = (MemoryMatch) data.getRowObject();
|
||||
MemoryMatch<SearchData> match = (MemoryMatch<SearchData>) data.getRowObject();
|
||||
if (match.isChanged()) {
|
||||
setForeground(data.isSelected() ? CHANGED_SELECTED_COLOR : CHANGED_COLOR);
|
||||
}
|
||||
|
||||
+3
-2
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
@@ -26,10 +27,10 @@ import ghidra.util.table.ProgramLocationTableRowMapper;
|
||||
* table columns.
|
||||
*/
|
||||
public class MemoryMatchToAddressTableRowMapper
|
||||
extends ProgramLocationTableRowMapper<MemoryMatch, Address> {
|
||||
extends ProgramLocationTableRowMapper<MemoryMatch<SearchData>, Address> {
|
||||
|
||||
@Override
|
||||
public Address map(MemoryMatch rowObject, Program data, ServiceProvider serviceProvider) {
|
||||
public Address map(MemoryMatch<SearchData> rowObject, Program data, ServiceProvider serviceProvider) {
|
||||
return rowObject.getAddress();
|
||||
}
|
||||
|
||||
|
||||
+3
-2
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@@ -26,10 +27,10 @@ import ghidra.util.table.ProgramLocationTableRowMapper;
|
||||
* program location based table columns.
|
||||
*/
|
||||
public class MemoryMatchToProgramLocationTableRowMapper
|
||||
extends ProgramLocationTableRowMapper<MemoryMatch, ProgramLocation> {
|
||||
extends ProgramLocationTableRowMapper<MemoryMatch<SearchData>, ProgramLocation> {
|
||||
|
||||
@Override
|
||||
public ProgramLocation map(MemoryMatch rowObject, Program program,
|
||||
public ProgramLocation map(MemoryMatch<SearchData> rowObject, Program program,
|
||||
ServiceProvider serviceProvider) {
|
||||
return new ProgramLocation(program, rowObject.getAddress());
|
||||
}
|
||||
|
||||
+6
-3
@@ -15,9 +15,12 @@
|
||||
*/
|
||||
package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.FunctionManager;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.table.ProgramLocationTableRowMapper;
|
||||
|
||||
/**
|
||||
@@ -25,10 +28,10 @@ import ghidra.util.table.ProgramLocationTableRowMapper;
|
||||
* table columns.
|
||||
*/
|
||||
public class MemoryMatchtToFunctionTableRowMapper
|
||||
extends ProgramLocationTableRowMapper<MemoryMatch, Function> {
|
||||
extends ProgramLocationTableRowMapper<MemoryMatch<SearchData>, Function> {
|
||||
|
||||
@Override
|
||||
public Function map(MemoryMatch rowObject, Program program,
|
||||
public Function map(MemoryMatch<SearchData> rowObject, Program program,
|
||||
ServiceProvider serviceProvider) {
|
||||
FunctionManager functionManager = program.getFunctionManager();
|
||||
return functionManager.getFunctionContaining(rowObject.getAddress());
|
||||
|
||||
+12
-12
@@ -39,8 +39,8 @@ import docking.widgets.list.GComboBoxCellRenderer;
|
||||
import generic.theme.GThemeDefaults.Colors.Messages;
|
||||
import ghidra.features.base.memsearch.combiner.Combiner;
|
||||
import ghidra.features.base.memsearch.format.SearchFormat;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.InvalidByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.layout.PairLayout;
|
||||
@@ -54,10 +54,10 @@ import ghidra.util.timer.GTimerMonitor;
|
||||
*/
|
||||
class MemorySearchControlPanel extends JPanel {
|
||||
private MultiStateButton<Combiner> searchButton;
|
||||
private GhidraComboBox<ByteMatcher> searchInputField;
|
||||
private GhidraComboBox<UserInputByteMatcher> searchInputField;
|
||||
private GDLabel hexSearchSequenceField;
|
||||
private boolean hasResults;
|
||||
private ByteMatcher currentMatcher = new InvalidByteMatcher("");
|
||||
private UserInputByteMatcher currentMatcher = new InvalidByteMatcher("");
|
||||
private SearchHistory searchHistory;
|
||||
private SearchGuiModel model;
|
||||
private JCheckBox selectionCheckbox;
|
||||
@@ -145,7 +145,7 @@ class MemorySearchControlPanel extends JPanel {
|
||||
String text = searchInputField.getText();
|
||||
String convertedText = searchFormat.convertText(text, oldSettings, model.getSettings());
|
||||
searchInputField.setText(convertedText);
|
||||
ByteMatcher byteMatcher = searchFormat.parse(convertedText, model.getSettings());
|
||||
UserInputByteMatcher byteMatcher = searchFormat.parse(convertedText, model.getSettings());
|
||||
setByteMatcher(byteMatcher);
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ class MemorySearchControlPanel extends JPanel {
|
||||
// our data model is ByteMatcher, not strings
|
||||
return;
|
||||
}
|
||||
ByteMatcher matcher = (ByteMatcher) obj;
|
||||
UserInputByteMatcher matcher = (UserInputByteMatcher) obj;
|
||||
model.setSettings(matcher.getSettings());
|
||||
super.setSelectedItem(obj);
|
||||
}
|
||||
@@ -286,7 +286,7 @@ class MemorySearchControlPanel extends JPanel {
|
||||
return newFormat.convertText(text, oldSettings, newSettings);
|
||||
}
|
||||
|
||||
private void setByteMatcher(ByteMatcher byteMatcher) {
|
||||
private void setByteMatcher(UserInputByteMatcher byteMatcher) {
|
||||
clearInputError();
|
||||
currentMatcher = byteMatcher;
|
||||
String text = currentMatcher.getDescription();
|
||||
@@ -404,7 +404,7 @@ class MemorySearchControlPanel extends JPanel {
|
||||
}
|
||||
|
||||
private void updateCombo() {
|
||||
ByteMatcher[] historyArray = searchHistory.getHistoryAsArray();
|
||||
UserInputByteMatcher[] historyArray = searchHistory.getHistoryAsArray();
|
||||
|
||||
searchInputField.setModel(new DefaultComboBoxModel<>(historyArray));
|
||||
}
|
||||
@@ -428,7 +428,7 @@ class MemorySearchControlPanel extends JPanel {
|
||||
String afterOffset = currentText.substring(offs, currentText.length());
|
||||
String proposedText = beforeOffset + str + afterOffset;
|
||||
|
||||
ByteMatcher byteMatcher = model.parse(proposedText);
|
||||
UserInputByteMatcher byteMatcher = model.parse(proposedText);
|
||||
if (!byteMatcher.isValidInput()) {
|
||||
reportInputError(byteMatcher.getDescription());
|
||||
return;
|
||||
@@ -457,7 +457,7 @@ class MemorySearchControlPanel extends JPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
ByteMatcher byteMatcher = model.parse(proposedResult);
|
||||
UserInputByteMatcher byteMatcher = model.parse(proposedResult);
|
||||
if (!byteMatcher.isValidInput()) {
|
||||
reportInputError(byteMatcher.getDescription());
|
||||
return;
|
||||
@@ -503,14 +503,14 @@ class MemorySearchControlPanel extends JPanel {
|
||||
searchInputField.setText(initialInput);
|
||||
}
|
||||
|
||||
private class SearchHistoryRenderer extends GComboBoxCellRenderer<ByteMatcher> {
|
||||
private class SearchHistoryRenderer extends GComboBoxCellRenderer<UserInputByteMatcher> {
|
||||
{
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends ByteMatcher> list,
|
||||
ByteMatcher matcher, int index,
|
||||
public Component getListCellRendererComponent(JList<? extends UserInputByteMatcher> list,
|
||||
UserInputByteMatcher matcher, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
|
||||
super.getListCellRendererComponent(list, matcher, index, isSelected, cellHasFocus);
|
||||
|
||||
+8
-6
@@ -30,7 +30,8 @@ import ghidra.app.services.*;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.app.util.query.TableService;
|
||||
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||
import ghidra.framework.options.SaveState;
|
||||
@@ -68,7 +69,7 @@ public class MemorySearchPlugin extends Plugin implements MemorySearchService {
|
||||
private static final String SHOW_OPTIONS_PANEL = "Show Options Panel";
|
||||
private static final String SHOW_SCAN_PANEL = "Show Scan Panel";
|
||||
|
||||
private ByteMatcher lastByteMatcher;
|
||||
private UserInputByteMatcher lastByteMatcher;
|
||||
private MemorySearchOptions options;
|
||||
private SearchHistory searchHistory = new SearchHistory(MAX_HISTORY);
|
||||
private Address lastSearchAddress;
|
||||
@@ -133,7 +134,7 @@ public class MemorySearchPlugin extends Plugin implements MemorySearchService {
|
||||
TaskLauncher.launch(task);
|
||||
}
|
||||
|
||||
void updateByteMatcher(ByteMatcher matcher) {
|
||||
void updateByteMatcher(UserInputByteMatcher matcher) {
|
||||
lastByteMatcher = matcher;
|
||||
searchHistory.addSearch(matcher);
|
||||
}
|
||||
@@ -207,8 +208,9 @@ public class MemorySearchPlugin extends Plugin implements MemorySearchService {
|
||||
return;
|
||||
}
|
||||
|
||||
MemorySearcher searcher = new MemorySearcher(source, lastByteMatcher, addresses, 1);
|
||||
MemoryMatch match = searcher.findOnce(start, forward, monitor);
|
||||
MemorySearcher<SearchData> searcher =
|
||||
new MemorySearcher<>(source, lastByteMatcher, addresses, 1);
|
||||
MemoryMatch<SearchData> match = searcher.findOnce(start, forward, monitor);
|
||||
Swing.runLater(() -> navigateToMatch(match));
|
||||
}
|
||||
|
||||
@@ -227,7 +229,7 @@ public class MemorySearchPlugin extends Plugin implements MemorySearchService {
|
||||
return forward ? start.next() : start.previous();
|
||||
}
|
||||
|
||||
private void navigateToMatch(MemoryMatch match) {
|
||||
private void navigateToMatch(MemoryMatch<SearchData> match) {
|
||||
if (match != null) {
|
||||
lastSearchAddress = match.getAddress();
|
||||
Program program = navigatable.getProgram();
|
||||
|
||||
+41
-20
@@ -15,13 +15,24 @@
|
||||
*/
|
||||
package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Toolkit;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JSeparator;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.DockingContextListener;
|
||||
@@ -35,16 +46,22 @@ import docking.widgets.OptionDialogBuilder;
|
||||
import docking.widgets.table.actions.DeleteTableRowAction;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.context.NavigatableActionContext;
|
||||
import ghidra.app.nav.*;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.nav.NavigatableRegistry;
|
||||
import ghidra.app.nav.NavigatableRemovalListener;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||
import ghidra.app.script.AskDialog;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
|
||||
import ghidra.features.base.memsearch.bytesource.SearchRegion;
|
||||
import ghidra.features.base.memsearch.combiner.Combiner;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
|
||||
import ghidra.features.base.memsearch.scan.Scanner;
|
||||
import ghidra.features.base.memsearch.searcher.*;
|
||||
import ghidra.features.base.memsearch.searcher.AlignmentFilter;
|
||||
import ghidra.features.base.memsearch.searcher.CodeUnitFilter;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||
import ghidra.framework.model.DomainObject;
|
||||
import ghidra.framework.model.DomainObjectClosedListener;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
@@ -52,7 +69,9 @@ import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.program.util.BytesFieldLocation;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.layout.VerticalLayout;
|
||||
@@ -96,7 +115,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
private DockingAction nextAction;
|
||||
private DockingAction refreshAction;
|
||||
|
||||
private ByteMatcher byteMatcher;
|
||||
private UserInputByteMatcher byteMatcher;
|
||||
private Address lastMatchingAddress;
|
||||
|
||||
private boolean isBusy;
|
||||
@@ -188,7 +207,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
return mainComponent;
|
||||
}
|
||||
|
||||
void setByteMatcher(ByteMatcher byteMatcher) {
|
||||
void setByteMatcher(UserInputByteMatcher byteMatcher) {
|
||||
this.byteMatcher = byteMatcher;
|
||||
tool.contextChanged(this);
|
||||
}
|
||||
@@ -216,7 +235,8 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
|
||||
Address start = getSearchStartAddress(forward);
|
||||
AddressSet addresses = getSearchAddresses();
|
||||
MemorySearcher searcher = new MemorySearcher(byteSource, byteMatcher, addresses, 1);
|
||||
MemorySearcher<SearchData> searcher =
|
||||
new MemorySearcher<>(byteSource, byteMatcher, addresses, 1);
|
||||
searcher.setMatchFilter(createFilter());
|
||||
|
||||
setBusy(true);
|
||||
@@ -238,7 +258,8 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
updateTitle();
|
||||
int limit = options.getSearchLimit();
|
||||
AddressSet addresses = getSearchAddresses();
|
||||
MemorySearcher searcher = new MemorySearcher(byteSource, byteMatcher, addresses, limit);
|
||||
MemorySearcher<SearchData> searcher =
|
||||
new MemorySearcher<>(byteSource, byteMatcher, addresses, limit);
|
||||
searcher.setMatchFilter(createFilter());
|
||||
|
||||
setBusy(true);
|
||||
@@ -312,7 +333,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
tool.contextChanged(this);
|
||||
}
|
||||
|
||||
private Predicate<MemoryMatch> createFilter() {
|
||||
private Predicate<MemoryMatch<SearchData>> createFilter() {
|
||||
AlignmentFilter alignmentFilter = new AlignmentFilter(model.getAlignment());
|
||||
CodeUnitFilter codeUnitFilter =
|
||||
new CodeUnitFilter(program, model.includeInstructions(),
|
||||
@@ -375,7 +396,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
}
|
||||
}
|
||||
|
||||
void searchOnceCompleted(MemoryMatch match, boolean cancelled) {
|
||||
void searchOnceCompleted(MemoryMatch<SearchData> match, boolean cancelled) {
|
||||
setBusy(false);
|
||||
updateSubTitle();
|
||||
if (match != null) {
|
||||
@@ -387,7 +408,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
}
|
||||
}
|
||||
|
||||
void refreshAndScanCompleted(MemoryMatch match) {
|
||||
void refreshAndScanCompleted(MemoryMatch<SearchData> match) {
|
||||
setBusy(false);
|
||||
updateSubTitle();
|
||||
if (match != null) {
|
||||
@@ -645,7 +666,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
}
|
||||
|
||||
void tableSelectionChanged() {
|
||||
MemoryMatch selectedMatch = resultsPanel.getSelectedMatch();
|
||||
MemoryMatch<SearchData> selectedMatch = resultsPanel.getSelectedMatch();
|
||||
matchHighlighter.setSelectedMatch(selectedMatch);
|
||||
if (selectedMatch != null) {
|
||||
lastMatchingAddress = selectedMatch.getAddress();
|
||||
@@ -673,7 +694,7 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
return isBusy;
|
||||
}
|
||||
|
||||
public List<MemoryMatch> getSearchResults() {
|
||||
public List<MemoryMatch<SearchData>> getSearchResults() {
|
||||
return resultsPanel.getTableModel().getModelData();
|
||||
}
|
||||
|
||||
@@ -751,13 +772,13 @@ public class MemorySearchProvider extends ComponentProviderAdapter
|
||||
nextProvider.setSearchInput(this.getSearchInput());
|
||||
nextProvider.showScanPanel(true);
|
||||
|
||||
List<MemoryMatch> searchResults = getSearchResults();
|
||||
List<MemoryMatch> rebasedResults = new ArrayList<>();
|
||||
for (MemoryMatch match : searchResults) {
|
||||
List<MemoryMatch<SearchData>> searchResults = getSearchResults();
|
||||
List<MemoryMatch<SearchData>> rebasedResults = new ArrayList<>();
|
||||
for (MemoryMatch<SearchData> match : searchResults) {
|
||||
ProgramLocation canonicalLocation = byteSource.getCanonicalLocation(match.getAddress());
|
||||
Address rebase = nextByteSource.rebaseFromCanonical(canonicalLocation);
|
||||
if (rebase != null) {
|
||||
MemoryMatch nextMatch = new MemoryMatch(rebase, match.getBytes(), match.getByteMatcher());
|
||||
MemoryMatch<SearchData> nextMatch = new MemoryMatch<>(rebase, match.getBytes(), match.getPattern());
|
||||
rebasedResults.add(nextMatch);
|
||||
}
|
||||
}
|
||||
|
||||
+32
-25
@@ -26,6 +26,7 @@ import javax.swing.event.TableModelEvent;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
|
||||
import ghidra.features.base.memsearch.combiner.Combiner;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.scan.Scanner;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||
@@ -42,8 +43,8 @@ import ghidra.util.task.*;
|
||||
* table for showing the results.
|
||||
*/
|
||||
public class MemorySearchResultsPanel extends JPanel {
|
||||
private GhidraThreadedTablePanel<MemoryMatch> threadedTablePanel;
|
||||
private GhidraTableFilterPanel<MemoryMatch> tableFilterPanel;
|
||||
private GhidraThreadedTablePanel<MemoryMatch<SearchData>> threadedTablePanel;
|
||||
private GhidraTableFilterPanel<MemoryMatch<SearchData>> tableFilterPanel;
|
||||
private GhidraTable table;
|
||||
private MemoryMatchTableModel tableModel;
|
||||
private MemorySearchProvider provider;
|
||||
@@ -101,7 +102,7 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
return tableFilterPanel;
|
||||
}
|
||||
|
||||
public void search(MemorySearcher searcher, Combiner combiner) {
|
||||
public void search(MemorySearcher<SearchData> searcher, Combiner combiner) {
|
||||
MemoryMatchTableLoader loader = createLoader(searcher, combiner);
|
||||
tableModel.addInitialLoadListener(
|
||||
cancelled -> provider.searchAllCompleted(loader.hasResults(), cancelled,
|
||||
@@ -109,7 +110,7 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
tableModel.setLoader(loader);
|
||||
}
|
||||
|
||||
public void searchOnce(MemorySearcher searcher, Address address, boolean forward) {
|
||||
public void searchOnce(MemorySearcher<SearchData> searcher, Address address, boolean forward) {
|
||||
SearchOnceTask task = new SearchOnceTask(forward, searcher, address);
|
||||
TaskLauncher.launch(task);
|
||||
}
|
||||
@@ -119,12 +120,14 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
TaskLauncher.launch(task);
|
||||
}
|
||||
|
||||
public void refreshAndMaybeScanForChanges(AddressableByteSource byteSource, Scanner scanner, List<MemoryMatch> previousResults) {
|
||||
public void refreshAndMaybeScanForChanges(AddressableByteSource byteSource, Scanner scanner,
|
||||
List<MemoryMatch<SearchData>> previousResults) {
|
||||
RefreshAndScanTask task = new RefreshAndScanTask(byteSource, scanner, previousResults);
|
||||
TaskLauncher.launch(task);
|
||||
}
|
||||
|
||||
private MemoryMatchTableLoader createLoader(MemorySearcher searcher, Combiner combiner) {
|
||||
private MemoryMatchTableLoader createLoader(MemorySearcher<SearchData> searcher,
|
||||
Combiner combiner) {
|
||||
if (!hasResults()) {
|
||||
hasDeleted = false;
|
||||
return new NewSearchTableLoader(searcher);
|
||||
@@ -141,7 +144,7 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
// loader as if doing an initial search because you get incremental loading and also
|
||||
// don't need to copy the existing results to feed to a combiner.
|
||||
hasCombined = true;
|
||||
List<MemoryMatch> previousResults = tableModel.getModelData();
|
||||
List<MemoryMatch<SearchData>> previousResults = tableModel.getModelData();
|
||||
return new CombinedMatchTableLoader(searcher, previousResults, combiner);
|
||||
}
|
||||
|
||||
@@ -164,7 +167,7 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
return tableModel.getRowCount();
|
||||
}
|
||||
|
||||
void select(MemoryMatch match) {
|
||||
void select(MemoryMatch<SearchData> match) {
|
||||
int rowIndex = tableModel.getRowIndex(match);
|
||||
if (rowIndex >= 0) {
|
||||
threadedTablePanel.getTable().selectRow(rowIndex);
|
||||
@@ -175,7 +178,7 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
return table;
|
||||
}
|
||||
|
||||
public MemoryMatch getSelectedMatch() {
|
||||
public MemoryMatch<SearchData> getSelectedMatch() {
|
||||
int row = table.getSelectedRow();
|
||||
return row < 0 ? null : tableModel.getRowObject(row);
|
||||
}
|
||||
@@ -192,17 +195,17 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
private class SearchOnceTask extends Task {
|
||||
|
||||
private boolean forward;
|
||||
private MemorySearcher searcher;
|
||||
private MemorySearcher<SearchData> searcher;
|
||||
private Address start;
|
||||
|
||||
public SearchOnceTask(boolean forward, MemorySearcher searcher, Address start) {
|
||||
public SearchOnceTask(boolean forward, MemorySearcher<SearchData> searcher, Address start) {
|
||||
super(forward ? "Search Next" : "Search Previous", true, true, true);
|
||||
this.forward = forward;
|
||||
this.searcher = searcher;
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
private void tableLoadComplete(MemoryMatch match, boolean wasCancelled) {
|
||||
private void tableLoadComplete(MemoryMatch<SearchData> match, boolean wasCancelled) {
|
||||
int rowIndex = tableModel.getRowIndex(match);
|
||||
if (rowIndex >= 0) {
|
||||
table.selectRow(rowIndex);
|
||||
@@ -214,7 +217,7 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
try {
|
||||
MemoryMatch match = searcher.findOnce(start, forward, monitor);
|
||||
MemoryMatch<SearchData> match = searcher.findOnce(start, forward, monitor);
|
||||
if (match != null) {
|
||||
tableModel.addInitialLoadListener(b -> tableLoadComplete(match, b));
|
||||
tableModel.addObject(match);
|
||||
@@ -234,20 +237,21 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
|
||||
private AddressableByteSource byteSource;
|
||||
private Scanner scanner;
|
||||
private List<MemoryMatch> matchList;
|
||||
private List<MemoryMatch<SearchData>> matchList;
|
||||
|
||||
public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner) {
|
||||
this(byteSource, scanner, tableModel.getModelData());
|
||||
}
|
||||
|
||||
public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner, List<MemoryMatch> matches) {
|
||||
public RefreshAndScanTask(AddressableByteSource byteSource, Scanner scanner,
|
||||
List<MemoryMatch<SearchData>> matches) {
|
||||
super("Refreshing", true, true, true);
|
||||
this.byteSource = byteSource;
|
||||
this.scanner = scanner;
|
||||
this.matchList = matches;
|
||||
}
|
||||
|
||||
private void tableLoadComplete(MemoryMatch match) {
|
||||
private void tableLoadComplete(MemoryMatch<SearchData> match) {
|
||||
if (match == null) {
|
||||
provider.refreshAndScanCompleted(null);
|
||||
}
|
||||
@@ -270,11 +274,12 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean refreshByteValues(TaskMonitor monitor, List<MemoryMatch> matches) {
|
||||
private boolean refreshByteValues(TaskMonitor monitor,
|
||||
List<MemoryMatch<SearchData>> matches) {
|
||||
try {
|
||||
byteSource.invalidate(); // clear any caches before refreshing byte values
|
||||
monitor.initialize(matches.size(), "Refreshing...");
|
||||
for (MemoryMatch match : matches) {
|
||||
for (MemoryMatch<SearchData> match : matches) {
|
||||
byte[] bytes = new byte[match.getLength()];
|
||||
byteSource.getBytes(match.getAddress(), bytes, bytes.length);
|
||||
match.updateBytes(bytes);
|
||||
@@ -293,10 +298,11 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void performScanFiltering(TaskMonitor monitor, List<MemoryMatch> matches) {
|
||||
private void performScanFiltering(TaskMonitor monitor,
|
||||
List<MemoryMatch<SearchData>> matches) {
|
||||
monitor.initialize(matches.size(), "Scanning for changes...");
|
||||
List<MemoryMatch> scanResults = new ArrayList<>();
|
||||
for (MemoryMatch match : matches) {
|
||||
List<MemoryMatch<SearchData>> scanResults = new ArrayList<>();
|
||||
for (MemoryMatch<SearchData> match : matches) {
|
||||
if (scanner.accept(match)) {
|
||||
scanResults.add(match);
|
||||
}
|
||||
@@ -305,14 +311,15 @@ public class MemorySearchResultsPanel extends JPanel {
|
||||
}
|
||||
}
|
||||
|
||||
MemoryMatch firstIfReduced = getFirstMatchIfReduced(matches, scanResults);
|
||||
MemoryMatch<SearchData> firstIfReduced = getFirstMatchIfReduced(matches, scanResults);
|
||||
tableModel.addInitialLoadListener(b -> tableLoadComplete(firstIfReduced));
|
||||
tableModel.setLoader(new RefreshResultsTableLoader(scanResults));
|
||||
}
|
||||
|
||||
private MemoryMatch getFirstMatchIfReduced(List<MemoryMatch> matches,
|
||||
List<MemoryMatch> scanResults) {
|
||||
MemoryMatch firstIfReduced = null;
|
||||
private MemoryMatch<SearchData> getFirstMatchIfReduced(
|
||||
List<MemoryMatch<SearchData>> matches,
|
||||
List<MemoryMatch<SearchData>> scanResults) {
|
||||
MemoryMatch<SearchData> firstIfReduced = null;
|
||||
if (!scanResults.isEmpty() && scanResults.size() != matches.size()) {
|
||||
firstIfReduced = scanResults.isEmpty() ? null : scanResults.getFirst();
|
||||
}
|
||||
|
||||
+6
-5
@@ -17,6 +17,7 @@ package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
@@ -27,18 +28,18 @@ import ghidra.util.task.TaskMonitor;
|
||||
*/
|
||||
public class NewSearchTableLoader implements MemoryMatchTableLoader {
|
||||
|
||||
private MemorySearcher memSearcher;
|
||||
private MemorySearcher<SearchData> memSearcher;
|
||||
private boolean completedSearch;
|
||||
private MemoryMatch firstMatch;
|
||||
private MemoryMatch<SearchData> firstMatch;
|
||||
|
||||
NewSearchTableLoader(MemorySearcher memSearcher) {
|
||||
NewSearchTableLoader(MemorySearcher<SearchData> memSearcher) {
|
||||
this.memSearcher = memSearcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
|
||||
public void loadResults(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor) {
|
||||
completedSearch = memSearcher.findAll(accumulator, monitor);
|
||||
Iterator<MemoryMatch> iterator = accumulator.iterator();
|
||||
Iterator<MemoryMatch<SearchData>> iterator = accumulator.iterator();
|
||||
if (iterator.hasNext()) {
|
||||
firstMatch = iterator.next();
|
||||
}
|
||||
|
||||
+4
-3
@@ -17,6 +17,7 @@ package ghidra.features.base.memsearch.gui;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@@ -27,15 +28,15 @@ import ghidra.util.task.TaskMonitor;
|
||||
*/
|
||||
public class RefreshResultsTableLoader implements MemoryMatchTableLoader {
|
||||
|
||||
private List<MemoryMatch> matches;
|
||||
private List<MemoryMatch<SearchData>> matches;
|
||||
private boolean hasResults;
|
||||
|
||||
public RefreshResultsTableLoader(List<MemoryMatch> matches) {
|
||||
public RefreshResultsTableLoader(List<MemoryMatch<SearchData>> matches) {
|
||||
this.matches = matches;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadResults(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
|
||||
public void loadResults(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor) {
|
||||
accumulator.addAll(matches);
|
||||
hasResults = !accumulator.isEmpty();
|
||||
}
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ import java.util.function.Consumer;
|
||||
import ghidra.features.base.memsearch.bytesource.SearchRegion;
|
||||
import ghidra.features.base.memsearch.combiner.Combiner;
|
||||
import ghidra.features.base.memsearch.format.SearchFormat;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
|
||||
|
||||
/**
|
||||
* Maintains the state of all the settings and controls for the memory search window.
|
||||
@@ -133,7 +133,7 @@ public class SearchGuiModel {
|
||||
notifySettingsChanged(oldSettings);
|
||||
}
|
||||
|
||||
public ByteMatcher parse(String proposedText) {
|
||||
public UserInputByteMatcher parse(String proposedText) {
|
||||
return settings.getSearchFormat().parse(proposedText, settings);
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -18,7 +18,7 @@ package ghidra.features.base.memsearch.gui;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.features.base.memsearch.format.SearchFormat;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
|
||||
|
||||
/**
|
||||
* Class for managing memory search history. It maintains a list of previously used ByteMatchers to
|
||||
@@ -26,7 +26,7 @@ import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
* to create it.
|
||||
*/
|
||||
public class SearchHistory {
|
||||
private List<ByteMatcher> history = new LinkedList<>();
|
||||
private List<UserInputByteMatcher> history = new LinkedList<>();
|
||||
private int maxHistory;
|
||||
|
||||
public SearchHistory(int maxHistory) {
|
||||
@@ -38,18 +38,18 @@ public class SearchHistory {
|
||||
this.maxHistory = other.maxHistory;
|
||||
}
|
||||
|
||||
public void addSearch(ByteMatcher matcher) {
|
||||
public void addSearch(UserInputByteMatcher matcher) {
|
||||
removeSimilarMatchers(matcher);
|
||||
history.addFirst(matcher);
|
||||
truncateHistoryAsNeeded();
|
||||
}
|
||||
|
||||
private void removeSimilarMatchers(ByteMatcher matcher) {
|
||||
Iterator<ByteMatcher> it = history.iterator();
|
||||
private void removeSimilarMatchers(UserInputByteMatcher matcher) {
|
||||
Iterator<UserInputByteMatcher> it = history.iterator();
|
||||
String newInput = matcher.getInput();
|
||||
SearchFormat newFormat = matcher.getSettings().getSearchFormat();
|
||||
while (it.hasNext()) {
|
||||
ByteMatcher historyMatch = it.next();
|
||||
UserInputByteMatcher historyMatch = it.next();
|
||||
SearchFormat historyFormat = historyMatch.getSettings().getSearchFormat();
|
||||
String historyInput = historyMatch.getInput();
|
||||
if (historyFormat.equals(newFormat) && historyInput.equals(newInput)) {
|
||||
@@ -70,8 +70,8 @@ public class SearchHistory {
|
||||
}
|
||||
}
|
||||
|
||||
public ByteMatcher[] getHistoryAsArray() {
|
||||
ByteMatcher[] historyArray = new ByteMatcher[history.size()];
|
||||
public UserInputByteMatcher[] getHistoryAsArray() {
|
||||
UserInputByteMatcher[] historyArray = new UserInputByteMatcher[history.size()];
|
||||
history.toArray(historyArray);
|
||||
return historyArray;
|
||||
}
|
||||
|
||||
+3
-2
@@ -22,6 +22,7 @@ import javax.swing.Icon;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.services.*;
|
||||
import ghidra.app.util.SearchConstants;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@@ -68,7 +69,7 @@ public class SearchMarkers {
|
||||
service.setMarkerForGroup(MarkerService.HIGHLIGHT_GROUP, markerSet, program);
|
||||
}
|
||||
|
||||
void loadMarkers(String title, List<MemoryMatch> matches) {
|
||||
void loadMarkers(String title, List<MemoryMatch<SearchData>> matches) {
|
||||
if (service == null) {
|
||||
return;
|
||||
}
|
||||
@@ -92,7 +93,7 @@ public class SearchMarkers {
|
||||
}
|
||||
|
||||
markerSet.clearAll();
|
||||
for (MemoryMatch match : matches) {
|
||||
for (MemoryMatch<SearchData> match : matches) {
|
||||
markerSet.add(match.getAddress());
|
||||
}
|
||||
service.setMarkerForGroup(MarkerService.HIGHLIGHT_GROUP, markerSet, program);
|
||||
|
||||
+5
-110
@@ -15,124 +15,19 @@
|
||||
*/
|
||||
package ghidra.features.base.memsearch.matcher;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
|
||||
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||
import ghidra.util.bytesearch.Match;
|
||||
|
||||
/**
|
||||
* ByteMatcher is the base class for an object that be used to scan bytes looking for sequences
|
||||
* that match some criteria. As a convenience, it also stores the input string and settings that
|
||||
* were used to generated this ByteMatcher.
|
||||
* @param <T> The type of object used by the client to identify the matched pattern
|
||||
*/
|
||||
public abstract class ByteMatcher {
|
||||
public interface ByteMatcher<T> {
|
||||
|
||||
private final String name;
|
||||
private final String input;
|
||||
private final SearchSettings settings;
|
||||
public Iterable<Match<T>> match(ExtendedByteSequence bytes);
|
||||
|
||||
protected ByteMatcher(String name, String input, SearchSettings settings) {
|
||||
this.name = name;
|
||||
this.input = input;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the name of this byte matcher.}
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original input text that generated this ByteMatacher.
|
||||
* @return the original input text that generated this BytesMatcher
|
||||
*/
|
||||
public final String getInput() {
|
||||
return input == null ? "" : input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the settings used to generate this ByteMatcher.
|
||||
* @return the settings used to generate this ByteMatcher
|
||||
*/
|
||||
public SearchSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Iterable} for returning matches within the given byte sequence.
|
||||
* @param bytes the byte sequence to search
|
||||
* @return an iterable for return matches in the given sequence
|
||||
*/
|
||||
public abstract Iterable<ByteMatch> match(ExtendedByteSequence bytes);
|
||||
|
||||
/**
|
||||
* Returns a description of what this byte matcher matches. (Typically a sequence of bytes)
|
||||
* @return a description of what this byte matcher matches
|
||||
*/
|
||||
public abstract String getDescription();
|
||||
|
||||
/**
|
||||
* Returns additional information about this byte matcher. (Typically the mask bytes)
|
||||
* @return additional information about this byte matcher
|
||||
*/
|
||||
public abstract String getToolTip();
|
||||
|
||||
/**
|
||||
* Returns true if this byte matcher is valid and can be used to perform a search. If false,
|
||||
* the description will return an error message explaining why this byte matcher is
|
||||
* invalid.
|
||||
* @return true if this byte matcher is valid and can be used to perform a search.
|
||||
*/
|
||||
public boolean isValidSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this byte matcher has valid (but possibly incomplete) input text. For
|
||||
* example, when entering decimal values, the input could be just "-" as the user starts
|
||||
* to enter a negative number. In this case the input is valid, but the {@link #isValidSearch()}
|
||||
* would return false.
|
||||
* @return true if this byte matcher has valid text
|
||||
*/
|
||||
public boolean isValidInput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return input;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return input.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ByteMatcher other = (ByteMatcher) obj;
|
||||
return Objects.equals(input, other.input) &&
|
||||
settings.getSearchFormat() == other.settings.getSearchFormat();
|
||||
}
|
||||
|
||||
/**
|
||||
* Record class to contain a match specification.
|
||||
* @param start the offset in the buffer where the match starts
|
||||
* @param length the length of the match
|
||||
* @param matcher the matcher the found the match
|
||||
*/
|
||||
public record ByteMatch(int start, int length, ByteMatcher matcher) {}
|
||||
public String getDescription();
|
||||
|
||||
}
|
||||
|
||||
-98
@@ -1,98 +0,0 @@
|
||||
/* ###
|
||||
* 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.features.base.memsearch.matcher;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
|
||||
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||
import ghidra.features.base.memsearch.searcher.MemorySearcher;
|
||||
|
||||
/**
|
||||
* A ByteMatcher that searches an input sequence for matches from multiple patterns. This is
|
||||
* useful for using with the {@link MemorySearcher} so that multiple patterns can be searched with
|
||||
* only one pass through memory, thus paying the memory I/O costs only once. The resulting matches
|
||||
* will contain the sub ByteMatcher that matched so that it is easy to know which of the multiple
|
||||
* patterns matched.
|
||||
*/
|
||||
public class CombinedByteMatcher extends ByteMatcher {
|
||||
|
||||
private List<ByteMatcher> matchers;
|
||||
|
||||
public CombinedByteMatcher(List<ByteMatcher> matchers, SearchSettings settings) {
|
||||
super("Multi-Pattern Matcher", null, settings);
|
||||
this.matchers = matchers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ByteMatch> match(ExtendedByteSequence bytes) {
|
||||
return new MultiMatcherIterator(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTip() {
|
||||
return null;
|
||||
}
|
||||
|
||||
private class MultiMatcherIterator implements Iterable<ByteMatch>, Iterator<ByteMatch> {
|
||||
|
||||
private Iterator<ByteMatcher> matcherIterator;
|
||||
private Iterator<ByteMatch> currentMatchIterator;
|
||||
private ExtendedByteSequence bytes;
|
||||
|
||||
MultiMatcherIterator(ExtendedByteSequence bytes) {
|
||||
this.bytes = bytes;
|
||||
matcherIterator = matchers.iterator();
|
||||
currentMatchIterator = getNextMatchIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
while (currentMatchIterator != null && !currentMatchIterator.hasNext()) {
|
||||
currentMatchIterator = getNextMatchIterator();
|
||||
}
|
||||
return currentMatchIterator != null;
|
||||
}
|
||||
|
||||
private Iterator<ByteMatch> getNextMatchIterator() {
|
||||
if (matcherIterator.hasNext()) {
|
||||
ByteMatcher matcher = matcherIterator.next();
|
||||
return matcher.match(bytes).iterator();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatch next() {
|
||||
if (hasNext()) {
|
||||
return currentMatchIterator.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ByteMatch> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -19,6 +19,7 @@ import org.apache.commons.collections4.iterators.EmptyIterator;
|
||||
|
||||
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
|
||||
import ghidra.features.base.memsearch.format.SearchFormat;
|
||||
import ghidra.util.bytesearch.Match;
|
||||
import util.CollectionUtils;
|
||||
|
||||
/**
|
||||
@@ -29,7 +30,7 @@ import util.CollectionUtils;
|
||||
* but not complete so that a fully valid byte matcher could not be created. In this case, the
|
||||
* search is still invalid, but the input is valid. The description will reflect this situation.
|
||||
*/
|
||||
public class InvalidByteMatcher extends ByteMatcher {
|
||||
public class InvalidByteMatcher extends UserInputByteMatcher {
|
||||
|
||||
private final String errorMessage;
|
||||
private final boolean isValidInput;
|
||||
@@ -50,13 +51,13 @@ public class InvalidByteMatcher extends ByteMatcher {
|
||||
* a negative number.
|
||||
*/
|
||||
public InvalidByteMatcher(String errorMessage, boolean isValidInput) {
|
||||
super("Invalid", null, null);
|
||||
super("Invalid", "", null);
|
||||
this.errorMessage = errorMessage;
|
||||
this.isValidInput = isValidInput;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ByteMatch> match(ExtendedByteSequence bytes) {
|
||||
public Iterable<Match<SearchData>> match(ExtendedByteSequence bytes) {
|
||||
return CollectionUtils.asIterable(EmptyIterator.emptyIterator());
|
||||
}
|
||||
|
||||
|
||||
+11
-10
@@ -22,12 +22,13 @@ import org.bouncycastle.util.Arrays;
|
||||
import ghidra.features.base.memsearch.bytesequence.ByteSequence;
|
||||
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
|
||||
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||
import ghidra.util.bytesearch.Match;
|
||||
|
||||
/**
|
||||
* {@link ByteMatcher} where the user search input has been parsed into a sequence of bytes and
|
||||
* masks to be used for searching a byte sequence.
|
||||
*/
|
||||
public class MaskedByteSequenceByteMatcher extends ByteMatcher {
|
||||
public class MaskedByteSequenceByteMatcher extends UserInputByteMatcher {
|
||||
|
||||
private final byte[] searchBytes;
|
||||
private final byte[] masks;
|
||||
@@ -68,7 +69,7 @@ public class MaskedByteSequenceByteMatcher extends ByteMatcher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ByteMatch> match(ExtendedByteSequence byteSequence) {
|
||||
public Iterable<Match<SearchData>> match(ExtendedByteSequence byteSequence) {
|
||||
return new MatchIterator(byteSequence);
|
||||
}
|
||||
|
||||
@@ -109,11 +110,12 @@ public class MaskedByteSequenceByteMatcher extends ByteMatcher {
|
||||
//==================================================================================================
|
||||
// Inner classes
|
||||
//==================================================================================================
|
||||
private class MatchIterator implements Iterator<ByteMatch>, Iterable<ByteMatch> {
|
||||
private class MatchIterator
|
||||
implements Iterator<Match<SearchData>>, Iterable<Match<SearchData>> {
|
||||
|
||||
private ByteSequence byteSequence;
|
||||
private int startIndex = 0;
|
||||
private ByteMatch nextMatch;
|
||||
private Match<SearchData> nextMatch;
|
||||
|
||||
public MatchIterator(ByteSequence byteSequence) {
|
||||
this.byteSequence = byteSequence;
|
||||
@@ -121,7 +123,7 @@ public class MaskedByteSequenceByteMatcher extends ByteMatcher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ByteMatch> iterator() {
|
||||
public Iterator<Match<SearchData>> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -131,22 +133,21 @@ public class MaskedByteSequenceByteMatcher extends ByteMatcher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatch next() {
|
||||
public Match<SearchData> next() {
|
||||
if (nextMatch == null) {
|
||||
return null;
|
||||
}
|
||||
ByteMatch returnValue = nextMatch;
|
||||
Match<SearchData> returnValue = nextMatch;
|
||||
nextMatch = findNextMatch();
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
private ByteMatch findNextMatch() {
|
||||
private Match<SearchData> findNextMatch() {
|
||||
int nextPossibleStart = findNextPossibleStart(startIndex);
|
||||
while (nextPossibleStart >= 0) {
|
||||
startIndex = nextPossibleStart + 1;
|
||||
if (isValidMatch(nextPossibleStart)) {
|
||||
return new ByteMatch(nextPossibleStart, searchBytes.length,
|
||||
MaskedByteSequenceByteMatcher.this);
|
||||
return new Match<>(searchData, nextPossibleStart, searchBytes.length);
|
||||
}
|
||||
nextPossibleStart = findNextPossibleStart(startIndex);
|
||||
}
|
||||
|
||||
+12
-10
@@ -23,11 +23,12 @@ import javax.help.UnsupportedOperationException;
|
||||
|
||||
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
|
||||
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||
import ghidra.util.bytesearch.Match;
|
||||
|
||||
/**
|
||||
* {@link ByteMatcher} where the user search input has been parsed as a regular expression.
|
||||
*/
|
||||
public class RegExByteMatcher extends ByteMatcher {
|
||||
public class RegExByteMatcher extends UserInputByteMatcher {
|
||||
|
||||
private final Pattern pattern;
|
||||
|
||||
@@ -43,7 +44,7 @@ public class RegExByteMatcher extends ByteMatcher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ByteMatch> match(ExtendedByteSequence byteSequence) {
|
||||
public Iterable<Match<SearchData>> match(ExtendedByteSequence byteSequence) {
|
||||
return new PatternMatchIterator(byteSequence);
|
||||
}
|
||||
|
||||
@@ -93,12 +94,13 @@ public class RegExByteMatcher extends ByteMatcher {
|
||||
|
||||
/**
|
||||
* Adapter class for converting java {@link Pattern} matching into an iterator of
|
||||
* {@link ByteMatch}s.
|
||||
* {@link Match}s.
|
||||
*/
|
||||
private class PatternMatchIterator implements Iterable<ByteMatch>, Iterator<ByteMatch> {
|
||||
private class PatternMatchIterator
|
||||
implements Iterable<Match<SearchData>>, Iterator<Match<SearchData>> {
|
||||
|
||||
private Matcher matcher;
|
||||
private ByteMatch nextMatch;
|
||||
private Match<SearchData> nextMatch;
|
||||
private ExtendedByteSequence byteSequence;
|
||||
|
||||
public PatternMatchIterator(ExtendedByteSequence byteSequence) {
|
||||
@@ -113,22 +115,22 @@ public class RegExByteMatcher extends ByteMatcher {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ByteMatch next() {
|
||||
public Match<SearchData> next() {
|
||||
if (nextMatch == null) {
|
||||
return null;
|
||||
}
|
||||
ByteMatch returnValue = nextMatch;
|
||||
Match<SearchData> returnValue = nextMatch;
|
||||
nextMatch = findNextMatch();
|
||||
return returnValue;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ByteMatch> iterator() {
|
||||
public Iterator<Match<SearchData>> iterator() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private ByteMatch findNextMatch() {
|
||||
private Match<SearchData> findNextMatch() {
|
||||
if (!matcher.find()) {
|
||||
return null;
|
||||
}
|
||||
@@ -137,7 +139,7 @@ public class RegExByteMatcher extends ByteMatcher {
|
||||
if (start >= byteSequence.getLength()) {
|
||||
return null;
|
||||
}
|
||||
return new ByteMatch(start, end - start, RegExByteMatcher.this);
|
||||
return new Match<>(searchData, start, end - start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/* ###
|
||||
* 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.features.base.memsearch.matcher;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||
|
||||
public class SearchData {
|
||||
private final String name;
|
||||
private final String input;
|
||||
private final SearchSettings settings;
|
||||
|
||||
public SearchData(String name, String input, SearchSettings settings) {
|
||||
this.name = name;
|
||||
this.input = input == null ? "" : input;
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public SearchSettings getSettings() {
|
||||
return settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(input, settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
SearchData other = (SearchData) obj;
|
||||
return Objects.equals(input, other.input) && Objects.equals(settings, other.settings);
|
||||
}
|
||||
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/* ###
|
||||
* 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.features.base.memsearch.matcher;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.features.base.memsearch.bytesequence.ExtendedByteSequence;
|
||||
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||
import ghidra.util.bytesearch.Match;
|
||||
|
||||
public abstract class UserInputByteMatcher implements ByteMatcher<SearchData> {
|
||||
|
||||
protected final SearchData searchData;
|
||||
|
||||
protected UserInputByteMatcher(String name, String input, SearchSettings settings) {
|
||||
searchData = new SearchData(name, input, settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the name of this byte matcher.}
|
||||
*/
|
||||
public String getName() {
|
||||
return searchData.getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link Iterable} for returning matches within the given byte sequence.
|
||||
* @param bytes the byte sequence to search
|
||||
* @return an iterable for return matches in the given sequence
|
||||
*/
|
||||
@Override
|
||||
public abstract Iterable<Match<SearchData>> match(ExtendedByteSequence bytes);
|
||||
|
||||
/**
|
||||
* Returns a description of what this byte matcher matches. (Typically a sequence of bytes)
|
||||
* @return a description of what this byte matcher matches
|
||||
*/
|
||||
@Override
|
||||
public abstract String getDescription();
|
||||
|
||||
/**
|
||||
* Returns additional information about this byte matcher. (Typically the mask bytes)
|
||||
* @return additional information about this byte matcher
|
||||
*/
|
||||
public abstract String getToolTip();
|
||||
|
||||
/**
|
||||
* Returns true if this byte matcher is valid and can be used to perform a search. If false,
|
||||
* the description will return an error message explaining why this byte matcher is
|
||||
* invalid.
|
||||
* @return true if this byte matcher is valid and can be used to perform a search.
|
||||
*/
|
||||
public boolean isValidSearch() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this byte matcher has valid (but possibly incomplete) input text. For
|
||||
* example, when entering decimal values, the input could be just "-" as the user starts
|
||||
* to enter a negative number. In this case the input is valid, but the {@link #isValidSearch()}
|
||||
* would return false.
|
||||
* @return true if this byte matcher has valid text
|
||||
*/
|
||||
public boolean isValidInput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return searchData.getInput();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return searchData.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
UserInputByteMatcher other = (UserInputByteMatcher) obj;
|
||||
return Objects.equals(searchData, other.searchData);
|
||||
}
|
||||
|
||||
public SearchSettings getSettings() {
|
||||
return searchData.getSettings();
|
||||
}
|
||||
|
||||
public String getInput() {
|
||||
return searchData.getInput();
|
||||
}
|
||||
|
||||
public SearchData getSearchData() {
|
||||
return searchData;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import java.util.function.Predicate;
|
||||
|
||||
import ghidra.features.base.memsearch.format.SearchFormat;
|
||||
import ghidra.features.base.memsearch.gui.SearchSettings;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
|
||||
/**
|
||||
@@ -37,10 +37,10 @@ public enum Scanner {
|
||||
DECREASED("Decreased", mm -> compareBytes(mm) < 0, "Keep results whose values decreased");
|
||||
|
||||
private final String name;
|
||||
private final Predicate<MemoryMatch> acceptCondition;
|
||||
private final Predicate<MemoryMatch<SearchData>> acceptCondition;
|
||||
private final String description;
|
||||
|
||||
private Scanner(String name, Predicate<MemoryMatch> condition, String description) {
|
||||
private Scanner(String name, Predicate<MemoryMatch<SearchData>> condition, String description) {
|
||||
this.name = name;
|
||||
this.acceptCondition = condition;
|
||||
this.description = description;
|
||||
@@ -54,16 +54,16 @@ public enum Scanner {
|
||||
return description;
|
||||
}
|
||||
|
||||
public boolean accept(MemoryMatch match) {
|
||||
public boolean accept(MemoryMatch<SearchData> match) {
|
||||
return acceptCondition.test(match);
|
||||
}
|
||||
|
||||
private static int compareBytes(MemoryMatch match) {
|
||||
private static int compareBytes(MemoryMatch<SearchData> match) {
|
||||
byte[] bytes = match.getBytes();
|
||||
byte[] originalBytes = match.getPreviousBytes();
|
||||
|
||||
ByteMatcher matcher = match.getByteMatcher();
|
||||
SearchSettings settings = matcher.getSettings();
|
||||
SearchData matchInfo = match.getPattern();
|
||||
SearchSettings settings = matchInfo.getSettings();
|
||||
SearchFormat searchFormat = settings.getSearchFormat();
|
||||
return searchFormat.compareValues(bytes, originalBytes, settings);
|
||||
}
|
||||
|
||||
+4
-2
@@ -17,11 +17,13 @@ package ghidra.features.base.memsearch.searcher;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
|
||||
/**
|
||||
* Search filter that can test a search result and determine if that result is at an address
|
||||
* whose offset matches the given alignment (i.e. its offset is a multiple of the alignment value)
|
||||
*/
|
||||
public class AlignmentFilter implements Predicate<MemoryMatch> {
|
||||
public class AlignmentFilter implements Predicate<MemoryMatch<SearchData>> {
|
||||
|
||||
private int alignment;
|
||||
|
||||
@@ -30,7 +32,7 @@ public class AlignmentFilter implements Predicate<MemoryMatch> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean test(MemoryMatch match) {
|
||||
public boolean test(MemoryMatch<SearchData> match) {
|
||||
return match.getAddress().getOffset() % alignment == 0;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -17,13 +17,18 @@ package ghidra.features.base.memsearch.searcher;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.program.model.listing.CodeUnit;
|
||||
import ghidra.program.model.listing.Data;
|
||||
import ghidra.program.model.listing.Instruction;
|
||||
import ghidra.program.model.listing.Listing;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* Search filter that can test a search result and determine if that result starts at or inside
|
||||
* a code unit that matches one of the selected types.
|
||||
*/
|
||||
public class CodeUnitFilter implements Predicate<MemoryMatch> {
|
||||
public class CodeUnitFilter implements Predicate<MemoryMatch<SearchData>> {
|
||||
|
||||
private boolean includeInstructions;
|
||||
private boolean includeUndefinedData;
|
||||
|
||||
+10
-10
@@ -18,34 +18,34 @@ package ghidra.features.base.memsearch.searcher;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
/**
|
||||
* A class that represents a memory search hit at an address. Matches can also be updated with
|
||||
* new byte values (from a scan or refresh action). The original bytes that matched the original
|
||||
* search are maintained in addition to the "refreshed" bytes.
|
||||
* @param <T> The client object type that identifies the matching pattern
|
||||
*/
|
||||
public class MemoryMatch implements Comparable<MemoryMatch> {
|
||||
public class MemoryMatch<T> implements Comparable<MemoryMatch<T>> {
|
||||
|
||||
private final Address address;
|
||||
private byte[] bytes;
|
||||
private byte[] previousBytes;
|
||||
private final ByteMatcher matcher;
|
||||
private final T pattern;
|
||||
|
||||
public MemoryMatch(Address address, byte[] bytes, ByteMatcher matcher) {
|
||||
public MemoryMatch(Address address, byte[] bytes, T pattern) {
|
||||
if (bytes == null || bytes.length < 1) {
|
||||
throw new IllegalArgumentException("Must provide at least 1 byte");
|
||||
}
|
||||
this.address = Objects.requireNonNull(address);
|
||||
this.bytes = bytes;
|
||||
this.previousBytes = bytes;
|
||||
this.matcher = matcher;
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
public MemoryMatch(Address address) {
|
||||
this.address = address;
|
||||
this.matcher = null;
|
||||
this.pattern = null;
|
||||
}
|
||||
|
||||
public void updateBytes(byte[] newBytes) {
|
||||
@@ -71,12 +71,12 @@ public class MemoryMatch implements Comparable<MemoryMatch> {
|
||||
return previousBytes;
|
||||
}
|
||||
|
||||
public ByteMatcher getByteMatcher() {
|
||||
return matcher;
|
||||
public T getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MemoryMatch o) {
|
||||
public int compareTo(MemoryMatch<T> o) {
|
||||
return address.compareTo(o.address);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ public class MemoryMatch implements Comparable<MemoryMatch> {
|
||||
return false;
|
||||
}
|
||||
|
||||
MemoryMatch other = (MemoryMatch) obj;
|
||||
MemoryMatch<?> other = (MemoryMatch<?>) obj;
|
||||
// just compare addresses. The bytes are mutable and we want matches to be equal even
|
||||
// if the bytes are different
|
||||
return Objects.equals(address, other.address);
|
||||
|
||||
+39
-36
@@ -20,8 +20,8 @@ import java.util.function.Predicate;
|
||||
import ghidra.features.base.memsearch.bytesequence.*;
|
||||
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.util.bytesearch.Match;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
@@ -34,17 +34,18 @@ import ghidra.util.task.TaskMonitor;
|
||||
* then either call the {@link #findAll(Accumulator, TaskMonitor)} method or use it to incrementally
|
||||
* search using {@link #findNext(Address, TaskMonitor)},
|
||||
* {@link #findPrevious(Address, TaskMonitor)}, or {@link #findOnce(Address, boolean, TaskMonitor)}.
|
||||
* @param <T> The client object type used to identify matching patterns
|
||||
*/
|
||||
|
||||
public class MemorySearcher {
|
||||
public class MemorySearcher<T> {
|
||||
private static final int DEFAULT_CHUNK_SIZE = 16 * 1024;
|
||||
private static final int OVERLAP_SIZE = 100;
|
||||
private final AddressableByteSequence bytes1;
|
||||
private final AddressableByteSequence bytes2;
|
||||
private final ByteMatcher matcher;
|
||||
private final ByteMatcher<T> matcher;
|
||||
private final int chunkSize;
|
||||
|
||||
private Predicate<MemoryMatch> filter = r -> true;
|
||||
private Predicate<MemoryMatch<T>> filter = r -> true;
|
||||
private final int searchLimit;
|
||||
private final AddressSetView searchSet;
|
||||
|
||||
@@ -55,7 +56,7 @@ public class MemorySearcher {
|
||||
* @param addresses the address in the byte source to search
|
||||
* @param searchLimit the max number of hits before stopping
|
||||
*/
|
||||
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher,
|
||||
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher<T> matcher,
|
||||
AddressSetView addresses, int searchLimit) {
|
||||
this(byteSource, matcher, addresses, searchLimit, DEFAULT_CHUNK_SIZE);
|
||||
}
|
||||
@@ -68,7 +69,7 @@ public class MemorySearcher {
|
||||
* @param searchLimit the max number of hits before stopping
|
||||
* @param chunkSize the maximum number of bytes to feed to the matcher at any one time.
|
||||
*/
|
||||
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher matcher,
|
||||
public MemorySearcher(AddressableByteSource byteSource, ByteMatcher<T> matcher,
|
||||
AddressSetView addresses, int searchLimit, int chunkSize) {
|
||||
this.matcher = matcher;
|
||||
this.searchSet = addresses;
|
||||
@@ -84,7 +85,7 @@ public class MemorySearcher {
|
||||
* criteria that is not captured in the byte matcher such as alignment and code unit type.
|
||||
* @param filter the predicate to use to filter search results
|
||||
*/
|
||||
public void setMatchFilter(Predicate<MemoryMatch> filter) {
|
||||
public void setMatchFilter(Predicate<MemoryMatch<T>> filter) {
|
||||
this.filter = filter;
|
||||
}
|
||||
|
||||
@@ -97,7 +98,7 @@ public class MemorySearcher {
|
||||
* @param monitor the task monitor
|
||||
* @return true if the search completed searching through the entire address set.
|
||||
*/
|
||||
public boolean findAll(Accumulator<MemoryMatch> accumulator, TaskMonitor monitor) {
|
||||
public boolean findAll(Accumulator<MemoryMatch<T>> accumulator, TaskMonitor monitor) {
|
||||
monitor.initialize(searchSet.getNumAddresses(), "Searching...");
|
||||
|
||||
for (AddressRange range : searchSet.getAddressRanges()) {
|
||||
@@ -116,7 +117,7 @@ public class MemorySearcher {
|
||||
* @param monitor the task monitor
|
||||
* @return the first match found or null if no match found.
|
||||
*/
|
||||
public MemoryMatch findOnce(Address start, boolean forward, TaskMonitor monitor) {
|
||||
public MemoryMatch<T> findOnce(Address start, boolean forward, TaskMonitor monitor) {
|
||||
if (forward) {
|
||||
return findNext(start, monitor);
|
||||
}
|
||||
@@ -130,14 +131,14 @@ public class MemorySearcher {
|
||||
* @param monitor the task monitor
|
||||
* @return the first match found or null if no match found.
|
||||
*/
|
||||
public MemoryMatch findNext(Address start, TaskMonitor monitor) {
|
||||
public MemoryMatch<T> findNext(Address start, TaskMonitor monitor) {
|
||||
|
||||
long numAddresses = searchSet.getNumAddresses() - searchSet.getAddressCountBefore(start);
|
||||
monitor.initialize(numAddresses, "Searching....");
|
||||
|
||||
for (AddressRange range : searchSet.getAddressRanges(start, true)) {
|
||||
range = range.intersectRange(start, range.getMaxAddress());
|
||||
MemoryMatch match = findFirst(range, monitor);
|
||||
MemoryMatch<T> match = findFirst(range, monitor);
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
@@ -155,12 +156,12 @@ public class MemorySearcher {
|
||||
* @param monitor the task monitor
|
||||
* @return the first match found or null if no match found.
|
||||
*/
|
||||
public MemoryMatch findPrevious(Address start, TaskMonitor monitor) {
|
||||
public MemoryMatch<T> findPrevious(Address start, TaskMonitor monitor) {
|
||||
|
||||
monitor.initialize(searchSet.getAddressCountBefore(start) + 1, "Searching....");
|
||||
|
||||
for (AddressRange range : searchSet.getAddressRanges(start, false)) {
|
||||
MemoryMatch match = findLast(range, start, monitor);
|
||||
MemoryMatch<T> match = findLast(range, start, monitor);
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
@@ -171,7 +172,7 @@ public class MemorySearcher {
|
||||
return null;
|
||||
}
|
||||
|
||||
private MemoryMatch findFirst(AddressRange range, TaskMonitor monitor) {
|
||||
private MemoryMatch<T> findFirst(AddressRange range, TaskMonitor monitor) {
|
||||
AddressableByteSequence searchBytes = bytes1;
|
||||
AddressableByteSequence extra = bytes2;
|
||||
|
||||
@@ -183,7 +184,7 @@ public class MemorySearcher {
|
||||
AddressRange next = it.next();
|
||||
extra.setRange(next);
|
||||
|
||||
MemoryMatch match = findFirst(searchBytes, extra, monitor);
|
||||
MemoryMatch<T> match = findFirst(searchBytes, extra, monitor);
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
@@ -202,7 +203,7 @@ public class MemorySearcher {
|
||||
return findFirst(searchBytes, extra, monitor);
|
||||
}
|
||||
|
||||
private MemoryMatch findLast(AddressRange range, Address start, TaskMonitor monitor) {
|
||||
private MemoryMatch<T> findLast(AddressRange range, Address start, TaskMonitor monitor) {
|
||||
AddressableByteSequence searchBytes = bytes1;
|
||||
AddressableByteSequence extra = bytes2;
|
||||
extra.clear();
|
||||
@@ -221,7 +222,7 @@ public class MemorySearcher {
|
||||
while (it.hasNext()) {
|
||||
AddressRange next = it.next();
|
||||
searchBytes.setRange(next);
|
||||
MemoryMatch match = findLast(searchBytes, extra, monitor);
|
||||
MemoryMatch<T> match = findLast(searchBytes, extra, monitor);
|
||||
if (match != null) {
|
||||
return match;
|
||||
}
|
||||
@@ -237,16 +238,17 @@ public class MemorySearcher {
|
||||
return null;
|
||||
}
|
||||
|
||||
private MemoryMatch findFirst(AddressableByteSequence searchBytes, ByteSequence extra,
|
||||
private MemoryMatch<T> findFirst(AddressableByteSequence searchBytes, ByteSequence extra,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
ExtendedByteSequence searchSequence =
|
||||
new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE);
|
||||
|
||||
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
|
||||
Address address = searchBytes.getAddress(byteMatch.start());
|
||||
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
|
||||
MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher());
|
||||
for (Match<T> byteMatch : matcher.match(searchSequence)) {
|
||||
Address address = searchBytes.getAddress((int) byteMatch.getStart());
|
||||
byte[] bytes =
|
||||
searchSequence.getBytes((int) byteMatch.getStart(), byteMatch.getLength());
|
||||
MemoryMatch<T> match = new MemoryMatch<>(address, bytes, byteMatch.getPattern());
|
||||
if (filter.test(match)) {
|
||||
return match;
|
||||
}
|
||||
@@ -258,18 +260,19 @@ public class MemorySearcher {
|
||||
return null;
|
||||
}
|
||||
|
||||
private MemoryMatch findLast(AddressableByteSequence searchBytes, ByteSequence extra,
|
||||
private MemoryMatch<T> findLast(AddressableByteSequence searchBytes, ByteSequence extra,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
MemoryMatch last = null;
|
||||
MemoryMatch<T> last = null;
|
||||
|
||||
ExtendedByteSequence searchSequence =
|
||||
new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE);
|
||||
|
||||
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
|
||||
Address address = searchBytes.getAddress(byteMatch.start());
|
||||
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
|
||||
MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher());
|
||||
for (Match<T> byteMatch : matcher.match(searchSequence)) {
|
||||
Address address = searchBytes.getAddress((int) byteMatch.getStart());
|
||||
byte[] bytes =
|
||||
searchSequence.getBytes((int) byteMatch.getStart(), byteMatch.getLength());
|
||||
MemoryMatch<T> match = new MemoryMatch<>(address, bytes, byteMatch.getPattern());
|
||||
if (filter.test(match)) {
|
||||
last = match;
|
||||
}
|
||||
@@ -281,7 +284,7 @@ public class MemorySearcher {
|
||||
return last;
|
||||
}
|
||||
|
||||
private boolean findAll(Accumulator<MemoryMatch> accumulator, AddressRange range,
|
||||
private boolean findAll(Accumulator<MemoryMatch<T>> accumulator, AddressRange range,
|
||||
TaskMonitor monitor) {
|
||||
AddressableByteSequence searchBytes = bytes1;
|
||||
AddressableByteSequence extra = bytes2;
|
||||
@@ -303,7 +306,7 @@ public class MemorySearcher {
|
||||
return findAll(accumulator, searchBytes, extra, monitor);
|
||||
}
|
||||
|
||||
private boolean findAll(Accumulator<MemoryMatch> accumulator,
|
||||
private boolean findAll(Accumulator<MemoryMatch<T>> accumulator,
|
||||
AddressableByteSequence searchBytes, ByteSequence extra, TaskMonitor monitor) {
|
||||
|
||||
if (monitor.isCancelled()) {
|
||||
@@ -313,15 +316,15 @@ public class MemorySearcher {
|
||||
ExtendedByteSequence searchSequence =
|
||||
new ExtendedByteSequence(searchBytes, extra, OVERLAP_SIZE);
|
||||
|
||||
for (ByteMatch byteMatch : matcher.match(searchSequence)) {
|
||||
Address address = searchBytes.getAddress(byteMatch.start());
|
||||
byte[] bytes = searchSequence.getBytes(byteMatch.start(), byteMatch.length());
|
||||
MemoryMatch match = new MemoryMatch(address, bytes, byteMatch.matcher());
|
||||
if (filter.test(match)) {
|
||||
for (Match<T> match : matcher.match(searchSequence)) {
|
||||
Address address = searchBytes.getAddress((int) match.getStart());
|
||||
byte[] bytes = searchSequence.getBytes((int) match.getStart(), match.getLength());
|
||||
MemoryMatch<T> memMatch = new MemoryMatch<>(address, bytes, match.getPattern());
|
||||
if (filter.test(memMatch)) {
|
||||
if (accumulator.size() >= searchLimit) {
|
||||
return false;
|
||||
}
|
||||
accumulator.add(match);
|
||||
accumulator.add(memMatch);
|
||||
}
|
||||
if (monitor.isCancelled()) {
|
||||
return false;
|
||||
|
||||
@@ -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.
|
||||
@@ -15,109 +15,76 @@
|
||||
*/
|
||||
package ghidra.util.bytesearch;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents a match of a DittedBitSequence at a given offset in a byte sequence.
|
||||
* Represents a match of a BytePattern at a given offset in a byte sequence.
|
||||
*
|
||||
* There is a hidden assumption that the sequence is actually a Pattern
|
||||
* that might have a ditted-bit-sequence, a set of match actions,
|
||||
* and post match rules/checks
|
||||
* @param <T> The specific implementation of the BytePattern that was used to create this match
|
||||
*
|
||||
*/
|
||||
public class Match {
|
||||
private DittedBitSequence sequence; // Pattern that matched
|
||||
private long offset; // Offset within bytestream where the match occurred
|
||||
public class Match<T> {
|
||||
private T pattern; // Pattern that matched
|
||||
private long start; // position in the input byte sequence where this pattern matched
|
||||
private int length;
|
||||
|
||||
/**
|
||||
* Construct a Match of a DittedBitSequence at an offset within a byte stream.
|
||||
* Object normally used when a match occurs during a MemoryBytePatternSearch.
|
||||
* @param sequence that matched
|
||||
* @param offset from the start of byte stream where the matched occured
|
||||
* Construct a Match of a BytePattern that matched at a position in the input byte sequence.
|
||||
* @param pattern the byte pattern that matched
|
||||
* @param start the location in the input byte sequence where the pattern match begins
|
||||
* @param length the length of the matching sequence
|
||||
*/
|
||||
public Match(DittedBitSequence sequence, long offset) {
|
||||
this.sequence = sequence;
|
||||
this.offset = offset;
|
||||
public Match(T pattern, long start, int length) {
|
||||
this.pattern = pattern;
|
||||
this.start = start;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the sequence corresponds to a PatternPair, return the number of postbits
|
||||
* @return the number of post bits
|
||||
* @return length in bytes of the matched pattern
|
||||
*/
|
||||
public int getNumPostBits() {
|
||||
if (!(sequence instanceof Pattern)) {
|
||||
return 0;
|
||||
}
|
||||
int marked = ((Pattern) sequence).getMarkOffset();
|
||||
if (marked == 0) {
|
||||
return sequence.getNumFixedBits();
|
||||
}
|
||||
return sequence.getNumFixedBits() - sequence.getNumInitialFixedBits(marked);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return actions associated with this match
|
||||
*/
|
||||
public MatchAction[] getMatchActions() {
|
||||
return ((Pattern) sequence).getMatchActions();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return size in bytes of sequence
|
||||
*/
|
||||
public int getSequenceSize() {
|
||||
return sequence.getSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return index of sequence in a possibly longer set of sequences
|
||||
*/
|
||||
public int getSequenceIndex() {
|
||||
return sequence.getIndex();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the offset of the match within a longer byte sequence
|
||||
*/
|
||||
public long getMarkOffset() {
|
||||
return offset + ((Pattern) sequence).getMarkOffset();
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return offset of match in sequence of bytes
|
||||
*/
|
||||
public long getMatchStart() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the possible post rules are satisfied
|
||||
*
|
||||
* @param streamoffset offset within from match location to check postrules.
|
||||
*
|
||||
* @return true if post rules are satisfied
|
||||
*/
|
||||
public boolean checkPostRules(long streamoffset) {
|
||||
long curoffset = streamoffset + offset;
|
||||
Pattern pattern = (Pattern) sequence;
|
||||
PostRule[] postRules = pattern.getPostRules();
|
||||
for (PostRule postRule : postRules) {
|
||||
if (!postRule.apply(pattern, curoffset)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ditted bit sequence as a string
|
||||
*/
|
||||
public String getHexString() {
|
||||
return sequence.getHexString();
|
||||
public long getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sequence that was matched
|
||||
*/
|
||||
public DittedBitSequence getSequence() {
|
||||
return sequence;
|
||||
public T getPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return pattern.toString() + " @ " + start;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(pattern, start, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Match<?> other = (Match<?>) obj;
|
||||
return Objects.equals(pattern, other.pattern) && start == other.start &&
|
||||
length == other.length;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 @@ public interface MatchAction {
|
||||
* @param addr where the match occured
|
||||
* @param match information about the match that occurred
|
||||
*/
|
||||
public void apply(Program program, Address addr, Match match);
|
||||
public void apply(Program program, Address addr, Match<Pattern> match);
|
||||
|
||||
/**
|
||||
* Action can be constructed from XML
|
||||
|
||||
+12
-10
@@ -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.
|
||||
@@ -36,7 +36,7 @@ import ghidra.util.task.TaskMonitor;
|
||||
public class MemoryBytePatternSearcher {
|
||||
private static final long RESTRICTED_PATTERN_BYTE_RANGE = 32;
|
||||
|
||||
SequenceSearchState root = null;
|
||||
SequenceSearchState<Pattern> root = null;
|
||||
|
||||
ArrayList<Pattern> patternList;
|
||||
|
||||
@@ -62,7 +62,7 @@ public class MemoryBytePatternSearcher {
|
||||
* @param searchName name of search
|
||||
* @param root search state pre-initialized
|
||||
*/
|
||||
public MemoryBytePatternSearcher(String searchName, SequenceSearchState root) {
|
||||
public MemoryBytePatternSearcher(String searchName, SequenceSearchState<Pattern> root) {
|
||||
this.searchName = searchName;
|
||||
this.root = root;
|
||||
}
|
||||
@@ -164,7 +164,7 @@ public class MemoryBytePatternSearcher {
|
||||
* @throws IOException exception during read of memory
|
||||
* @throws CancelledException canceled search
|
||||
*/
|
||||
private void searchBlock(SequenceSearchState rootState, Program program, MemoryBlock block,
|
||||
private void searchBlock(SequenceSearchState<Pattern> rootState, Program program, MemoryBlock block,
|
||||
AddressSetView restrictSet, TaskMonitor monitor)
|
||||
throws IOException, CancelledException {
|
||||
|
||||
@@ -193,7 +193,7 @@ public class MemoryBytePatternSearcher {
|
||||
AddressRange addressRange = addressRanges.next();
|
||||
long numAddressesInRange = addressRange.getLength();
|
||||
|
||||
ArrayList<Match> mymatches = new ArrayList<>();
|
||||
ArrayList<Match<Pattern>> mymatches = new ArrayList<>();
|
||||
|
||||
long streamoffset = blockStartAddr.getOffset();
|
||||
|
||||
@@ -228,13 +228,15 @@ public class MemoryBytePatternSearcher {
|
||||
monitor.checkCancelled();
|
||||
monitor.setProgress(
|
||||
matchProgress + (long) (numAddressesInRange * ((float) i / mymatches.size())));
|
||||
Match match = mymatches.get(i);
|
||||
Address addr = blockStartAddr.add(match.getMarkOffset() + blockOffset);
|
||||
if (!match.checkPostRules(streamoffset + blockOffset)) {
|
||||
Match<Pattern> match = mymatches.get(i);
|
||||
Pattern pattern = match.getPattern();
|
||||
Address addr = blockStartAddr.add(pattern.getMarkOffset()+match.getStart() + blockOffset);
|
||||
long totalOffset = streamoffset + blockOffset + match.getStart();
|
||||
if (!pattern.checkPostRules(totalOffset)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MatchAction[] matchactions = match.getMatchActions();
|
||||
MatchAction[] matchactions = pattern.getMatchActions();
|
||||
preMatchApply(matchactions, addr);
|
||||
for (MatchAction matchaction : matchactions) {
|
||||
matchaction.apply(program, addr, match);
|
||||
|
||||
@@ -227,4 +227,20 @@ public class Pattern extends DittedBitSequence {
|
||||
parser.end();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the possible post rules are satisfied
|
||||
*
|
||||
* @param offset offset in stream to check postrules.
|
||||
*
|
||||
* @return true if post rules are satisfied
|
||||
*/
|
||||
public boolean checkPostRules(long offset) {
|
||||
for (PostRule postRule : postrule) {
|
||||
if (!postRule.apply(this, offset)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
+45
-43
@@ -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.
|
||||
@@ -17,6 +17,7 @@ package ghidra.util.bytesearch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
@@ -25,22 +26,22 @@ import ghidra.util.task.TaskMonitor;
|
||||
* SeqenceSearchState holds the state of a search for a DittedBitSequence within a byte
|
||||
* sequence.
|
||||
*/
|
||||
public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
public class SequenceSearchState<T extends DittedBitSequence> implements Comparable<SequenceSearchState<T>> {
|
||||
|
||||
private static final int PATTERN_ENDED = Integer.MAX_VALUE;
|
||||
private SequenceSearchState parent;
|
||||
private ArrayList<DittedBitSequence> possible; // Patterns that could still match in this state
|
||||
private ArrayList<DittedBitSequence> success; // Patterns that have matched successfully if we reached this state
|
||||
private SequenceSearchState[] trans; // State transitions based on next byte
|
||||
private SequenceSearchState<T> parent;
|
||||
private ArrayList<T> possible; // Patterns that could still match in this state
|
||||
private ArrayList<T> success; // Patterns that have matched successfully if we reached this state
|
||||
private SequenceSearchState<T>[] trans; // State transitions based on next byte
|
||||
|
||||
/**
|
||||
* Construct a sub sequence state with a parent sequence
|
||||
*
|
||||
* @param parent parent SequenceSearchState
|
||||
*/
|
||||
public SequenceSearchState(SequenceSearchState parent) {
|
||||
public SequenceSearchState(SequenceSearchState<T> parent) {
|
||||
this.parent = parent;
|
||||
possible = new ArrayList<DittedBitSequence>();
|
||||
possible = new ArrayList<T>();
|
||||
success = null;
|
||||
trans = null;
|
||||
}
|
||||
@@ -65,11 +66,11 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
* @param pat pattern to add
|
||||
* @param pos position within the current set of patterns to add this pattern
|
||||
*/
|
||||
public void addSequence(DittedBitSequence pat, int pos) {
|
||||
public void addSequence(T pat, int pos) {
|
||||
possible.add(pat);
|
||||
if (pos == pat.getSize()) {
|
||||
if (success == null) {
|
||||
success = new ArrayList<DittedBitSequence>();
|
||||
success = new ArrayList<T>();
|
||||
}
|
||||
success.add(pat);
|
||||
}
|
||||
@@ -92,7 +93,7 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SequenceSearchState o) {
|
||||
public int compareTo(SequenceSearchState<T> o) {
|
||||
int i = 0;
|
||||
for (;;) {
|
||||
if (possible.size() <= i) {
|
||||
@@ -113,12 +114,12 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
}
|
||||
}
|
||||
|
||||
private void buildSingleTransition(ArrayList<SequenceSearchState> all, int pos, int val) {
|
||||
SequenceSearchState newstate = null;
|
||||
for (DittedBitSequence curpat : possible) {
|
||||
private void buildSingleTransition(ArrayList<SequenceSearchState<T>> all, int pos, int val) {
|
||||
SequenceSearchState<T> newstate = null;
|
||||
for (T curpat : possible) {
|
||||
if (curpat.isMatch(pos, val)) {
|
||||
if (newstate == null) {
|
||||
newstate = new SequenceSearchState(this);
|
||||
newstate = new SequenceSearchState<>(this);
|
||||
}
|
||||
newstate.addSequence(curpat, pos + 1);
|
||||
}
|
||||
@@ -130,9 +131,9 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
}
|
||||
}
|
||||
|
||||
private void exportSuccess(ArrayList<Match> match, int offset) {
|
||||
for (DittedBitSequence succes : success) { // If we found matches
|
||||
Match newmatch = new Match(succes, offset);
|
||||
private void exportSuccess(ArrayList<Match<T>> match, int offset) {
|
||||
for (T succes : success) { // If we found matches
|
||||
Match<T> newmatch = new Match<T>(succes, offset, succes.getSize());
|
||||
match.add(newmatch);
|
||||
}
|
||||
}
|
||||
@@ -141,8 +142,8 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
* Merge in -op- and this as a single state
|
||||
* @param op
|
||||
*/
|
||||
private void merge(SequenceSearchState op) {
|
||||
SequenceSearchState parent = op.parent;
|
||||
private void merge(SequenceSearchState<T> op) {
|
||||
SequenceSearchState<T> parent = op.parent;
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
if (parent.trans[i] == op) {
|
||||
parent.trans[i] = this; // Should be replaced with this
|
||||
@@ -153,7 +154,7 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
success = op.success;
|
||||
}
|
||||
else {
|
||||
ArrayList<DittedBitSequence> tmp = new ArrayList<DittedBitSequence>();
|
||||
ArrayList<T> tmp = new ArrayList<>();
|
||||
int i = 0;
|
||||
int j = 0;
|
||||
int curpat = -1;
|
||||
@@ -200,9 +201,9 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
* @param numbytes retrict number of bytes to allow to match
|
||||
* @param match list of matches, the result
|
||||
*/
|
||||
public void sequenceMatch(byte[] bytearray, int numbytes, ArrayList<Match> match) {
|
||||
public void sequenceMatch(byte[] bytearray, int numbytes, ArrayList<Match<T>> match) {
|
||||
int subindex = 0;
|
||||
SequenceSearchState curstate = this;
|
||||
SequenceSearchState<T> curstate = this;
|
||||
|
||||
do {
|
||||
if (curstate.success != null) {
|
||||
@@ -223,8 +224,8 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
* @param buffer is the array of bytes to search
|
||||
* @param match is populated with a Match object for each pattern and position that matches
|
||||
*/
|
||||
public void apply(byte[] buffer, ArrayList<Match> match) {
|
||||
SequenceSearchState curstate;
|
||||
public void apply(byte[] buffer, ArrayList<Match<T>> match) {
|
||||
SequenceSearchState<T> curstate;
|
||||
int subindex;
|
||||
for (int offset = 0; offset < buffer.length; ++offset) {
|
||||
curstate = this; // New starting offset -> Root state
|
||||
@@ -250,7 +251,7 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
* @param monitor - if non-null, check for user cancel, and maintain progress info
|
||||
* @throws IOException
|
||||
*/
|
||||
public void apply(InputStream in, ArrayList<Match> match, TaskMonitor monitor)
|
||||
public void apply(InputStream in, ArrayList<Match<T>> match, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
apply(in, -1L, match, monitor);
|
||||
}
|
||||
@@ -263,7 +264,7 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
* @param monitor - if non-null, check for user cancel, and maintain progress info
|
||||
* @throws IOException
|
||||
*/
|
||||
public void apply(InputStream in, long maxBytes, ArrayList<Match> match, TaskMonitor monitor)
|
||||
public void apply(InputStream in, long maxBytes, ArrayList<Match<T>> match, TaskMonitor monitor)
|
||||
throws IOException {
|
||||
long progress = monitor.getProgress();
|
||||
|
||||
@@ -277,7 +278,7 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
byte[] firstBuf = new byte[maxSize];
|
||||
byte[] secondBuf = new byte[maxSize];
|
||||
byte[] curBuf;
|
||||
SequenceSearchState curState;
|
||||
SequenceSearchState<T> curState;
|
||||
int fullBuffers; // Number of buffers that are completely full
|
||||
int ra = in.read(firstBuf);
|
||||
if (ra == firstBuf.length) {
|
||||
@@ -405,13 +406,14 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
* @param pos position within the search sequence state for this level
|
||||
* @return list of possible new search states to be added to the state machine
|
||||
*/
|
||||
static ArrayList<SequenceSearchState> buildTransitionLevel(ArrayList<SequenceSearchState> prev,
|
||||
@SuppressWarnings("unchecked")
|
||||
static <T extends DittedBitSequence> ArrayList<SequenceSearchState<T>> buildTransitionLevel(ArrayList<SequenceSearchState<T>> prev,
|
||||
int pos) {
|
||||
ArrayList<SequenceSearchState> res = new ArrayList<SequenceSearchState>();
|
||||
Iterator<SequenceSearchState> iterator = prev.iterator();
|
||||
ArrayList<SequenceSearchState<T>> res = new ArrayList<SequenceSearchState<T>>();
|
||||
Iterator<SequenceSearchState<T>> iterator = prev.iterator();
|
||||
while (iterator.hasNext()) { // For each current state
|
||||
SequenceSearchState next = iterator.next();
|
||||
next.trans = new SequenceSearchState[256];
|
||||
SequenceSearchState<T> next = iterator.next();
|
||||
next.trans = (SequenceSearchState<T>[]) Array.newInstance(next.getClass(), 256);
|
||||
for (int i = 0; i < 256; ++i) { // Try every byte transition
|
||||
next.buildSingleTransition(res, pos, i);
|
||||
}
|
||||
@@ -421,12 +423,12 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
}
|
||||
// Prepare to dedup the states
|
||||
Collections.sort(res);
|
||||
ArrayList<SequenceSearchState> finalres = new ArrayList<SequenceSearchState>();
|
||||
Iterator<SequenceSearchState> iter = res.iterator();
|
||||
SequenceSearchState curpat = iter.next();
|
||||
ArrayList<SequenceSearchState<T>> finalres = new ArrayList<SequenceSearchState<T>>();
|
||||
Iterator<SequenceSearchState<T>> iter = res.iterator();
|
||||
SequenceSearchState<T> curpat = iter.next();
|
||||
finalres.add(curpat);
|
||||
while (iter.hasNext()) {
|
||||
SequenceSearchState nextpat = iter.next();
|
||||
SequenceSearchState<T> nextpat = iter.next();
|
||||
int comp = curpat.compareTo(nextpat);
|
||||
if (comp == 0) { // Identical states
|
||||
curpat.merge(nextpat);
|
||||
@@ -444,16 +446,16 @@ public class SequenceSearchState implements Comparable<SequenceSearchState> {
|
||||
* @param patterns bit sequence patterns
|
||||
* @return search state the will match the given sequences
|
||||
*/
|
||||
static public SequenceSearchState buildStateMachine(
|
||||
ArrayList<? extends DittedBitSequence> patterns) {
|
||||
SequenceSearchState root = new SequenceSearchState(null);
|
||||
static public <T extends DittedBitSequence> SequenceSearchState<T> buildStateMachine(
|
||||
ArrayList<T> patterns) {
|
||||
SequenceSearchState<T> root = new SequenceSearchState<>(null);
|
||||
for (int i = 0; i < patterns.size(); ++i) {
|
||||
DittedBitSequence pat = patterns.get(i);
|
||||
T pat = patterns.get(i);
|
||||
pat.setIndex(i);
|
||||
root.addSequence(pat, 0);
|
||||
}
|
||||
root.sortSequences();
|
||||
ArrayList<SequenceSearchState> statelevel = new ArrayList<SequenceSearchState>();
|
||||
ArrayList<SequenceSearchState<T>> statelevel = new ArrayList<SequenceSearchState<T>>();
|
||||
statelevel.add(root);
|
||||
int level = 0;
|
||||
do {
|
||||
|
||||
+2
-1
@@ -35,6 +35,7 @@ import ghidra.app.util.viewer.format.FormatManager;
|
||||
import ghidra.features.base.memsearch.bytesource.SearchRegion;
|
||||
import ghidra.features.base.memsearch.format.SearchFormat;
|
||||
import ghidra.features.base.memsearch.gui.*;
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.*;
|
||||
@@ -122,7 +123,7 @@ public abstract class AbstractMemSearchTest extends AbstractProgramBasedTest {
|
||||
assertEquals("The inner-class has been renamed", "MemoryMatchHighlighter",
|
||||
highlightProvider.getClass().getSimpleName());
|
||||
|
||||
List<MemoryMatch> data = searchProvider.getSearchResults();
|
||||
List<MemoryMatch<SearchData>> data = searchProvider.getSearchResults();
|
||||
return data.stream().map(result -> result.getAddress()).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
/* ###
|
||||
* 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.features.base.memsearch.bytesequence;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.*;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch;
|
||||
|
||||
public class CombinedByteMatcherTest {
|
||||
private ByteMatcher xxxByteMatcher;
|
||||
private ByteMatcher yyyByteMatcher;
|
||||
private ByteMatcher zzzByteMatcher;
|
||||
private CombinedByteMatcher multiMatcher;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
xxxByteMatcher = new RegExByteMatcher("xxx", null);
|
||||
yyyByteMatcher = new RegExByteMatcher("yyy", null);
|
||||
zzzByteMatcher = new RegExByteMatcher("zzz", null);
|
||||
multiMatcher =
|
||||
new CombinedByteMatcher(List.of(xxxByteMatcher, yyyByteMatcher, zzzByteMatcher), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textFindsOneEachPatterns() {
|
||||
List<ByteMatch> results = match("fooxxxbar, fooyyybar, foozzzbar");
|
||||
assertEquals(3, results.size());
|
||||
assertEquals(new ByteMatch(3, 3, xxxByteMatcher), results.get(0));
|
||||
assertEquals(new ByteMatch(15, 3, yyyByteMatcher), results.get(1));
|
||||
assertEquals(new ByteMatch(26, 3, zzzByteMatcher), results.get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textFindsMutliplePatterns() {
|
||||
List<ByteMatch> results = match("xxxyyyzzzxxxyyyzzz");
|
||||
assertEquals(6, results.size());
|
||||
assertEquals(new ByteMatch(0, 3, xxxByteMatcher), results.get(0));
|
||||
assertEquals(new ByteMatch(9, 3, xxxByteMatcher), results.get(1));
|
||||
assertEquals(new ByteMatch(3, 3, yyyByteMatcher), results.get(2));
|
||||
assertEquals(new ByteMatch(12, 3, yyyByteMatcher), results.get(3));
|
||||
assertEquals(new ByteMatch(6, 3, zzzByteMatcher), results.get(4));
|
||||
assertEquals(new ByteMatch(15, 3, zzzByteMatcher), results.get(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMatches() {
|
||||
List<ByteMatch> results = match("There are no matches here!");
|
||||
assertEquals(0, results.size());
|
||||
}
|
||||
|
||||
private List<ByteMatch> match(String s) {
|
||||
ExtendedByteSequence sequence = createByteSequence(s);
|
||||
Iterable<ByteMatch> match = multiMatcher.match(sequence);
|
||||
return StreamSupport.stream(match.spliterator(), false).toList();
|
||||
}
|
||||
|
||||
private ExtendedByteSequence createByteSequence(String s) {
|
||||
ByteSequence main = new ByteArrayByteSequence(makeBytes(s));
|
||||
ByteSequence extra = new ByteArrayByteSequence(makeBytes(""));
|
||||
return new ExtendedByteSequence(main, extra, 0);
|
||||
}
|
||||
|
||||
private byte[] makeBytes(String string) {
|
||||
byte[] bytes = new byte[string.length()];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = (byte) string.charAt(i);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
}
|
||||
+23
-19
@@ -22,9 +22,8 @@ import java.util.Iterator;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch;
|
||||
import ghidra.features.base.memsearch.matcher.MaskedByteSequenceByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.*;
|
||||
import ghidra.util.bytesearch.Match;
|
||||
|
||||
public class MaskedBytesSequenceByteMatcherTest {
|
||||
|
||||
@@ -44,15 +43,17 @@ public class MaskedBytesSequenceByteMatcherTest {
|
||||
public void testSimplePatterWithOneMatchCrossingBoundary() {
|
||||
|
||||
byte[] searchBytes = makeBytes(3, 2, 4);
|
||||
ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null);
|
||||
MaskedByteSequenceByteMatcher byteMatcher =
|
||||
new MaskedByteSequenceByteMatcher("", searchBytes, null);
|
||||
SearchData searchData = byteMatcher.getSearchData();
|
||||
|
||||
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
|
||||
Iterator<Match<SearchData>> it = byteMatcher.match(byteSequence).iterator();
|
||||
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(new ByteMatch(2, 3, byteMatcher), it.next());
|
||||
assertEquals(new Match<>(searchData, 2, 3), it.next());
|
||||
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(new ByteMatch(9, 3, byteMatcher), it.next());
|
||||
assertEquals(new Match<>(searchData, 9, 3), it.next());
|
||||
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
@@ -62,12 +63,14 @@ public class MaskedBytesSequenceByteMatcherTest {
|
||||
public void testSimplePatterWithOneMatchCrossingBoundaryNoHasNextCalls() {
|
||||
|
||||
byte[] searchBytes = makeBytes(3, 2, 4);
|
||||
ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null);
|
||||
MaskedByteSequenceByteMatcher byteMatcher =
|
||||
new MaskedByteSequenceByteMatcher("", searchBytes, null);
|
||||
SearchData searchData = byteMatcher.getSearchData();
|
||||
|
||||
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
|
||||
Iterator<Match<SearchData>> it = byteMatcher.match(byteSequence).iterator();
|
||||
|
||||
assertEquals(new ByteMatch(2, 3, byteMatcher), it.next());
|
||||
assertEquals(new ByteMatch(9, 3, byteMatcher), it.next());
|
||||
assertEquals(new Match<>(searchData, 2, 3), it.next());
|
||||
assertEquals(new Match<>(searchData, 9, 3), it.next());
|
||||
assertNull(it.next());
|
||||
}
|
||||
|
||||
@@ -76,14 +79,15 @@ public class MaskedBytesSequenceByteMatcherTest {
|
||||
|
||||
byte[] searchBytes = makeBytes(2, 0, 2);
|
||||
byte[] masks = makeBytes(0xff, 0x00, 0xff);
|
||||
ByteMatcher byteMatcher =
|
||||
MaskedByteSequenceByteMatcher byteMatcher =
|
||||
new MaskedByteSequenceByteMatcher("", searchBytes, masks, null);
|
||||
SearchData searchdata = byteMatcher.getSearchData();
|
||||
|
||||
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
|
||||
Iterator<Match<SearchData>> it = byteMatcher.match(byteSequence).iterator();
|
||||
|
||||
assertEquals(new ByteMatch(1, 3, byteMatcher), it.next());
|
||||
assertEquals(new ByteMatch(6, 3, byteMatcher), it.next());
|
||||
assertEquals(new ByteMatch(8, 3, byteMatcher), it.next());
|
||||
assertEquals(new Match<>(searchdata, 1, 3), it.next());
|
||||
assertEquals(new Match<>(searchdata, 6, 3), it.next());
|
||||
assertEquals(new Match<>(searchdata, 8, 3), it.next());
|
||||
assertNull(it.next());
|
||||
}
|
||||
|
||||
@@ -91,17 +95,17 @@ public class MaskedBytesSequenceByteMatcherTest {
|
||||
public void testPatternStartButNotEnoughExtraBytes() {
|
||||
byte[] searchBytes = makeBytes(6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
byte[] masks = makeBytes(0xff, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
ByteMatcher byteMatcher =
|
||||
MaskedByteSequenceByteMatcher byteMatcher =
|
||||
new MaskedByteSequenceByteMatcher("", searchBytes, masks, null);
|
||||
|
||||
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
|
||||
Iterator<Match<SearchData>> it = byteMatcher.match(byteSequence).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDescription() {
|
||||
byte[] searchBytes = makeBytes(1, 2, 3, 0xaa);
|
||||
ByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null);
|
||||
UserInputByteMatcher byteMatcher = new MaskedByteSequenceByteMatcher("", searchBytes, null);
|
||||
|
||||
assertEquals("01 02 03 aa", byteMatcher.getDescription());
|
||||
}
|
||||
|
||||
+14
-13
@@ -22,9 +22,8 @@ import java.util.Iterator;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher.ByteMatch;
|
||||
import ghidra.features.base.memsearch.matcher.RegExByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.*;
|
||||
import ghidra.util.bytesearch.Match;
|
||||
|
||||
public class RegExByteMatcherTest {
|
||||
private ExtendedByteSequence byteSequence;
|
||||
@@ -40,15 +39,16 @@ public class RegExByteMatcherTest {
|
||||
@Test
|
||||
public void testSimplePatternWithOneMatchCrossingBoundary() {
|
||||
|
||||
ByteMatcher byteMatcher = new RegExByteMatcher("two", null);
|
||||
RegExByteMatcher byteMatcher = new RegExByteMatcher("two", null);
|
||||
SearchData searchData = byteMatcher.getSearchData();
|
||||
|
||||
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
|
||||
Iterator<Match<SearchData>> it = byteMatcher.match(byteSequence).iterator();
|
||||
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(new ByteMatch(4, 3, byteMatcher), it.next());
|
||||
assertEquals(new Match<>(searchData, 4, 3), it.next());
|
||||
|
||||
assertTrue(it.hasNext());
|
||||
assertEquals(new ByteMatch(14, 3, byteMatcher), it.next());
|
||||
assertEquals(new Match<>(searchData, 14, 3), it.next());
|
||||
|
||||
assertFalse(it.hasNext());
|
||||
|
||||
@@ -57,21 +57,22 @@ public class RegExByteMatcherTest {
|
||||
@Test
|
||||
public void testSimplePatternWithOneMatchCrossingBoundaryNoHasNextCalls() {
|
||||
|
||||
ByteMatcher byteMatcher = new RegExByteMatcher("two", null);
|
||||
RegExByteMatcher byteMatcher = new RegExByteMatcher("two", null);
|
||||
SearchData searchData = byteMatcher.getSearchData();
|
||||
|
||||
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
|
||||
Iterator<Match<SearchData>> it = byteMatcher.match(byteSequence).iterator();
|
||||
|
||||
assertEquals(new ByteMatch(4, 3, byteMatcher), it.next());
|
||||
assertEquals(new ByteMatch(14, 3, byteMatcher), it.next());
|
||||
assertEquals(new Match<>(searchData, 4, 3), it.next());
|
||||
assertEquals(new Match<>(searchData, 14, 3), it.next());
|
||||
assertNull(it.next());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMatch() {
|
||||
|
||||
ByteMatcher byteMatcher = new RegExByteMatcher("apple", null);
|
||||
ByteMatcher<SearchData> byteMatcher = new RegExByteMatcher("apple", null);
|
||||
|
||||
Iterator<ByteMatch> it = byteMatcher.match(byteSequence).iterator();
|
||||
Iterator<Match<SearchData>> it = byteMatcher.match(byteSequence).iterator();
|
||||
assertFalse(it.hasNext());
|
||||
}
|
||||
|
||||
|
||||
+46
-34
@@ -22,6 +22,7 @@ import java.util.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.SearchData;
|
||||
import ghidra.features.base.memsearch.searcher.MemoryMatch;
|
||||
import ghidra.program.model.address.*;
|
||||
|
||||
@@ -29,18 +30,18 @@ public class MemoryMatchCombinerTest {
|
||||
|
||||
private GenericAddressSpace space;
|
||||
|
||||
private MemoryMatch m1;
|
||||
private MemoryMatch m2;
|
||||
private MemoryMatch m3;
|
||||
private MemoryMatch m4;
|
||||
private MemoryMatch m5;
|
||||
private MemoryMatch m6;
|
||||
private MemoryMatch m7;
|
||||
private MemoryMatch m8;
|
||||
private MemoryMatch<SearchData> m1;
|
||||
private MemoryMatch<SearchData> m2;
|
||||
private MemoryMatch<SearchData> m3;
|
||||
private MemoryMatch<SearchData> m4;
|
||||
private MemoryMatch<SearchData> m5;
|
||||
private MemoryMatch<SearchData> m6;
|
||||
private MemoryMatch<SearchData> m7;
|
||||
private MemoryMatch<SearchData> m8;
|
||||
|
||||
List<MemoryMatch> list1;
|
||||
List<MemoryMatch> list2;
|
||||
List<MemoryMatch> result;
|
||||
List<MemoryMatch<SearchData>> list1;
|
||||
List<MemoryMatch<SearchData>> list2;
|
||||
List<MemoryMatch<SearchData>> result;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -88,8 +89,8 @@ public class MemoryMatchCombinerTest {
|
||||
|
||||
@Test
|
||||
public void testUnionWithDupsKeepsLonger() {
|
||||
MemoryMatch m3_short = createMatch(3, 2);
|
||||
MemoryMatch m3_long = createMatch(3, 8);
|
||||
MemoryMatch<SearchData> m3_short = createMatch(3, 2);
|
||||
MemoryMatch<SearchData> m3_long = createMatch(3, 8);
|
||||
|
||||
list1 = list(m1, m2, m3);
|
||||
list2 = list(m3_short, m4, m5);
|
||||
@@ -127,8 +128,8 @@ public class MemoryMatchCombinerTest {
|
||||
|
||||
@Test
|
||||
public void testIntersectionKeepsLonger() {
|
||||
MemoryMatch m4_long = createMatch(4, 8);
|
||||
MemoryMatch m3_long = createMatch(3, 8);
|
||||
MemoryMatch<SearchData> m4_long = createMatch(4, 8);
|
||||
MemoryMatch<SearchData> m3_long = createMatch(3, 8);
|
||||
list1 = list(m1, m2, m3, m4_long);
|
||||
list2 = list(m1, m2, m3_long, m4);
|
||||
result = intersect(list1, list2);
|
||||
@@ -174,8 +175,8 @@ public class MemoryMatchCombinerTest {
|
||||
|
||||
@Test
|
||||
public void testXorLengthDontMatter() {
|
||||
MemoryMatch m4_long = createMatch(4, 8);
|
||||
MemoryMatch m3_short = createMatch(3, 2);
|
||||
MemoryMatch<SearchData> m4_long = createMatch(4, 8);
|
||||
MemoryMatch<SearchData> m3_short = createMatch(3, 2);
|
||||
|
||||
list1 = list(m1, m2, m3, m4);
|
||||
list2 = list(m3_short, m4_long, m5);
|
||||
@@ -227,8 +228,8 @@ public class MemoryMatchCombinerTest {
|
||||
|
||||
@Test
|
||||
public void testAMinusBLengthDontMatter() {
|
||||
MemoryMatch m4_long = createMatch(4, 8);
|
||||
MemoryMatch m3_short = createMatch(3, 2);
|
||||
MemoryMatch<SearchData> m4_long = createMatch(4, 8);
|
||||
MemoryMatch<SearchData> m3_short = createMatch(3, 2);
|
||||
|
||||
list1 = list(m1, m2, m3, m4);
|
||||
list2 = list(m3_short, m4_long, m5);
|
||||
@@ -280,8 +281,8 @@ public class MemoryMatchCombinerTest {
|
||||
|
||||
@Test
|
||||
public void testBMinusALengthDontMatter() {
|
||||
MemoryMatch m4_long = createMatch(4, 8);
|
||||
MemoryMatch m3_short = createMatch(3, 2);
|
||||
MemoryMatch<SearchData> m4_long = createMatch(4, 8);
|
||||
MemoryMatch<SearchData> m3_short = createMatch(3, 2);
|
||||
|
||||
list1 = list(m1, m2, m3, m4);
|
||||
list2 = list(m3_short, m4_long, m5);
|
||||
@@ -294,42 +295,53 @@ public class MemoryMatchCombinerTest {
|
||||
assertEquals(list(m5), result);
|
||||
}
|
||||
|
||||
private List<MemoryMatch> xor(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
|
||||
private List<MemoryMatch<SearchData>> xor(List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
Combiner combiner = Combiner.XOR;
|
||||
List<MemoryMatch> results = new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
List<MemoryMatch<SearchData>> results =
|
||||
new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
Collections.sort(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<MemoryMatch> union(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
|
||||
private List<MemoryMatch<SearchData>> union(List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
Combiner combiner = Combiner.UNION;
|
||||
List<MemoryMatch> results = new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
List<MemoryMatch<SearchData>> results =
|
||||
new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
Collections.sort(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<MemoryMatch> intersect(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
|
||||
private List<MemoryMatch<SearchData>> intersect(List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
Combiner combiner = Combiner.INTERSECT;
|
||||
List<MemoryMatch> results = new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
List<MemoryMatch<SearchData>> results =
|
||||
new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
Collections.sort(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<MemoryMatch> aMinusB(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
|
||||
private List<MemoryMatch<SearchData>> aMinusB(List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
Combiner combiner = Combiner.A_MINUS_B;
|
||||
List<MemoryMatch> results = new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
List<MemoryMatch<SearchData>> results =
|
||||
new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
Collections.sort(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<MemoryMatch> BMinusA(List<MemoryMatch> matches1, List<MemoryMatch> matches2) {
|
||||
private List<MemoryMatch<SearchData>> BMinusA(List<MemoryMatch<SearchData>> matches1,
|
||||
List<MemoryMatch<SearchData>> matches2) {
|
||||
Combiner combiner = Combiner.B_MINUS_A;
|
||||
List<MemoryMatch> results = new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
List<MemoryMatch<SearchData>> results =
|
||||
new ArrayList<>(combiner.combine(matches1, matches2));
|
||||
Collections.sort(results);
|
||||
return results;
|
||||
}
|
||||
|
||||
private List<MemoryMatch> list(MemoryMatch... matches) {
|
||||
@SafeVarargs
|
||||
private List<MemoryMatch<SearchData>> list(MemoryMatch<SearchData>... matches) {
|
||||
return Arrays.asList(matches);
|
||||
}
|
||||
|
||||
@@ -337,9 +349,9 @@ public class MemoryMatchCombinerTest {
|
||||
return space.getAddress(offset);
|
||||
}
|
||||
|
||||
private MemoryMatch createMatch(int offset, int length) {
|
||||
private MemoryMatch<SearchData> createMatch(int offset, int length) {
|
||||
Address address = addr(offset);
|
||||
byte[] bytes = new byte[length];
|
||||
return new MemoryMatch(address, bytes, null);
|
||||
return new MemoryMatch<>(address, bytes, null);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
|
||||
|
||||
public class BinarySearchFormatTest extends AbstractSearchFormatTest {
|
||||
public BinarySearchFormatTest() {
|
||||
@@ -47,14 +47,14 @@ public class BinarySearchFormatTest extends AbstractSearchFormatTest {
|
||||
|
||||
@Test
|
||||
public void testGroupTooBig() {
|
||||
ByteMatcher bad = format.parse("111111111", settings);
|
||||
UserInputByteMatcher bad = format.parse("111111111", settings);
|
||||
assertFalse(bad.isValidInput());
|
||||
assertEquals("Max group size exceeded. Enter <space> to add more.", bad.getDescription());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidChars() {
|
||||
ByteMatcher bad = format.parse("012", settings);
|
||||
UserInputByteMatcher bad = format.parse("012", settings);
|
||||
assertFalse(bad.isValidInput());
|
||||
assertEquals("Invalid character", bad.getDescription());
|
||||
}
|
||||
|
||||
+7
-7
@@ -19,7 +19,7 @@ import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.features.base.memsearch.matcher.ByteMatcher;
|
||||
import ghidra.features.base.memsearch.matcher.UserInputByteMatcher;
|
||||
|
||||
public class DoubleSearchFormatTest extends AbstractSearchFormatTest {
|
||||
public DoubleSearchFormatTest() {
|
||||
@@ -58,7 +58,7 @@ public class DoubleSearchFormatTest extends AbstractSearchFormatTest {
|
||||
|
||||
@Test
|
||||
public void testDotOnly() {
|
||||
ByteMatcher byteMatcher = format.parse(".", settings);
|
||||
UserInputByteMatcher byteMatcher = format.parse(".", settings);
|
||||
assertTrue(byteMatcher.isValidInput());
|
||||
assertFalse(byteMatcher.isValidSearch());
|
||||
assertEquals("Incomplete floating point number", byteMatcher.getDescription());
|
||||
@@ -66,7 +66,7 @@ public class DoubleSearchFormatTest extends AbstractSearchFormatTest {
|
||||
|
||||
@Test
|
||||
public void testEndE() {
|
||||
ByteMatcher byteMatcher = format.parse("2.1e", settings);
|
||||
UserInputByteMatcher byteMatcher = format.parse("2.1e", settings);
|
||||
assertTrue(byteMatcher.isValidInput());
|
||||
assertFalse(byteMatcher.isValidSearch());
|
||||
assertEquals("Incomplete floating point number", byteMatcher.getDescription());
|
||||
@@ -74,7 +74,7 @@ public class DoubleSearchFormatTest extends AbstractSearchFormatTest {
|
||||
|
||||
@Test
|
||||
public void testEndNegativeE() {
|
||||
ByteMatcher byteMatcher = format.parse("2.1-e", settings);
|
||||
UserInputByteMatcher byteMatcher = format.parse("2.1-e", settings);
|
||||
assertTrue(byteMatcher.isValidInput());
|
||||
assertFalse(byteMatcher.isValidSearch());
|
||||
assertEquals("Incomplete floating point number", byteMatcher.getDescription());
|
||||
@@ -82,7 +82,7 @@ public class DoubleSearchFormatTest extends AbstractSearchFormatTest {
|
||||
|
||||
@Test
|
||||
public void testNegativeSignOnly() {
|
||||
ByteMatcher byteMatcher = format.parse("-", settings);
|
||||
UserInputByteMatcher byteMatcher = format.parse("-", settings);
|
||||
assertTrue(byteMatcher.isValidInput());
|
||||
assertFalse(byteMatcher.isValidSearch());
|
||||
assertEquals("Incomplete negative floating point number", byteMatcher.getDescription());
|
||||
@@ -90,7 +90,7 @@ public class DoubleSearchFormatTest extends AbstractSearchFormatTest {
|
||||
|
||||
@Test
|
||||
public void testNegativeDotSignOnly() {
|
||||
ByteMatcher byteMatcher = format.parse("-.", settings);
|
||||
UserInputByteMatcher byteMatcher = format.parse("-.", settings);
|
||||
assertTrue(byteMatcher.isValidInput());
|
||||
assertFalse(byteMatcher.isValidSearch());
|
||||
assertEquals("Incomplete negative floating point number", byteMatcher.getDescription());
|
||||
@@ -98,7 +98,7 @@ public class DoubleSearchFormatTest extends AbstractSearchFormatTest {
|
||||
|
||||
@Test
|
||||
public void testBadChars() {
|
||||
ByteMatcher byteMatcher = format.parse("12.z", settings);
|
||||
UserInputByteMatcher byteMatcher = format.parse("12.z", settings);
|
||||
assertFalse(byteMatcher.isValidInput());
|
||||
assertFalse(byteMatcher.isValidSearch());
|
||||
assertEquals("Floating point parse error: For input string: \"12.z\"",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user