Merge remote-tracking branch 'origin/GP-6261_ghidragon_mem_search_refactor--SQUASHED'

This commit is contained in:
Ryan Kurtz
2026-01-08 14:03:58 -05:00
66 changed files with 919 additions and 934 deletions
@@ -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;
@@ -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("");
@@ -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("");
@@ -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("");
@@ -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("");
@@ -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("");
@@ -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();
}
@@ -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("");
@@ -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);
}
@@ -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;
}
@@ -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;
}
@@ -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;
@@ -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
@@ -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);
}
@@ -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();
}
@@ -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());
}
@@ -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());
@@ -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);
@@ -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();
@@ -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);
}
}
@@ -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();
}
@@ -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();
}
@@ -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();
}
@@ -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);
}
@@ -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;
}
@@ -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);
@@ -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();
}
@@ -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;
}
}
}
@@ -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());
}
@@ -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);
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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;
}
}
@@ -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;
@@ -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);
@@ -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
@@ -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;
}
}
@@ -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 {
@@ -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());
}
@@ -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;
}
}
@@ -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());
}
@@ -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());
}
@@ -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);
}
}
@@ -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());
}
@@ -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