mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-12 23:23:17 +00:00
GP-4930 Improved GoTo dialog to support full and partial namespace paths with wildcard support
This commit is contained in:
parent
7148590e5c
commit
0e0b0255f2
File diff suppressed because it is too large
Load Diff
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -16,39 +16,19 @@
|
||||
package ghidra.app.plugin.core.gotoquery;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.app.services.QueryData;
|
||||
import ghidra.app.util.query.ProgramLocationPreviewTableModel;
|
||||
import ghidra.framework.model.DomainObjectException;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.UserSearchUtils;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.ClosedException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class GoToQueryResultsTableModel extends ProgramLocationPreviewTableModel {
|
||||
private QueryData queryData;
|
||||
private int maxSearchHits;
|
||||
private List<ProgramLocation> locations;
|
||||
|
||||
private SymbolTable symbolTable;
|
||||
|
||||
public GoToQueryResultsTableModel(Program prog, QueryData queryData,
|
||||
ServiceProvider serviceProvider, int maxSearchHits, TaskMonitor monitor) {
|
||||
super("Goto", serviceProvider, prog, monitor);
|
||||
|
||||
this.symbolTable = prog.getSymbolTable();
|
||||
this.queryData = queryData;
|
||||
this.maxSearchHits = maxSearchHits;
|
||||
}
|
||||
|
||||
public GoToQueryResultsTableModel(Program prog, ServiceProvider serviceProvider,
|
||||
List<ProgramLocation> locations, TaskMonitor monitor) {
|
||||
super("Goto", serviceProvider, prog, monitor);
|
||||
@ -66,102 +46,8 @@ public class GoToQueryResultsTableModel extends ProgramLocationPreviewTableModel
|
||||
|
||||
if (locations != null) {
|
||||
accumulator.addAll(locations);
|
||||
locations = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
doLoadMaybeWithExceptions(accumulator, monitor);
|
||||
}
|
||||
catch (DomainObjectException doe) {
|
||||
// Super Special Code:
|
||||
// There comes a time when this table is asked to load, but the program from whence
|
||||
// the load comes is no longer open. Normal table models we would dispose, but this
|
||||
// one is special in that nobody that has a handle to it will get notification of
|
||||
// the program being closed. So, we must anticipate the problem and deal with it
|
||||
// ourselves.
|
||||
Throwable cause = doe.getCause();
|
||||
if (!(cause instanceof ClosedException)) {
|
||||
throw doe;
|
||||
}
|
||||
cancelAllUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
private void doLoadMaybeWithExceptions(Accumulator<ProgramLocation> accumulator,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
searchDefinedSymbols(accumulator, monitor);
|
||||
searchDynamicSymbols(accumulator, monitor);
|
||||
}
|
||||
|
||||
private void searchDynamicSymbols(Accumulator<ProgramLocation> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
if (!queryData.isIncludeDynamicLables()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String queryString = queryData.getQueryString();
|
||||
if (!queryData.isWildCard()) {
|
||||
// if no wild cards, just parse off the address from the string and go there.
|
||||
parseDynamic(accumulator, queryString);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean caseSensitive = queryData.isCaseSensitive();
|
||||
Pattern pattern = UserSearchUtils.createSearchPattern(queryString, caseSensitive);
|
||||
|
||||
ReferenceManager refMgr = getProgram().getReferenceManager();
|
||||
AddressSet addressSet = getProgram().getAddressFactory().getAddressSet();
|
||||
AddressIterator addrIt = refMgr.getReferenceDestinationIterator(addressSet, true);
|
||||
while (addrIt.hasNext() && accumulator.size() < maxSearchHits) {
|
||||
monitor.checkCancelled();
|
||||
Address addr = addrIt.next();
|
||||
Symbol s = symbolTable.getPrimarySymbol(addr);
|
||||
if (!s.isDynamic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Matcher matcher = pattern.matcher(s.getName());
|
||||
if (matcher.matches()) {
|
||||
ProgramLocation programLocation = s.getProgramLocation();
|
||||
if (programLocation != null) {
|
||||
accumulator.add(programLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void parseDynamic(Accumulator<ProgramLocation> accumulator, String queryString) {
|
||||
Address address =
|
||||
SymbolUtilities.parseDynamicName(getProgram().getAddressFactory(), queryString);
|
||||
|
||||
if (address == null) {
|
||||
return;
|
||||
}
|
||||
Symbol s = symbolTable.getPrimarySymbol(address);
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
if (s.getName().equalsIgnoreCase(queryString)) {
|
||||
accumulator.add(s.getProgramLocation());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean searchDefinedSymbols(Accumulator<ProgramLocation> accumulator,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
SymbolIterator it =
|
||||
symbolTable.getSymbolIterator(queryData.getQueryString(), queryData.isCaseSensitive());
|
||||
|
||||
while (it.hasNext() && accumulator.size() < maxSearchHits) {
|
||||
monitor.checkCancelled();
|
||||
Symbol s = it.next();
|
||||
ProgramLocation programLocation = s.getProgramLocation();
|
||||
if (programLocation != null) {
|
||||
accumulator.add(programLocation);
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
@ -20,12 +20,12 @@ public class QueryData {
|
||||
/**
|
||||
* Wildcard char for any string.
|
||||
*/
|
||||
private String ANY_STRING_WILDCARD = "*";
|
||||
private static String ANY_STRING_WILDCARD = "*";
|
||||
|
||||
/**
|
||||
* Wildcard char for a single char.
|
||||
*/
|
||||
private String ANY_CHAR_WILDCARD = "?";
|
||||
private static String ANY_CHAR_WILDCARD = "?";
|
||||
|
||||
private final String queryString;
|
||||
private final boolean caseSensitive;
|
||||
@ -55,6 +55,10 @@ public class QueryData {
|
||||
}
|
||||
|
||||
public boolean isWildCard() {
|
||||
return queryString.contains(ANY_STRING_WILDCARD) || queryString.contains(ANY_CHAR_WILDCARD);
|
||||
return hasWildCards(queryString);
|
||||
}
|
||||
|
||||
public static boolean hasWildCards(String query) {
|
||||
return query.contains(ANY_STRING_WILDCARD) || query.contains(ANY_CHAR_WILDCARD);
|
||||
}
|
||||
}
|
||||
|
@ -15,18 +15,12 @@
|
||||
*/
|
||||
package ghidra.app.util.navigation;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import docking.widgets.table.threaded.ThreadedTableModelListener;
|
||||
import ghidra.GhidraOptions;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.nav.NavigationUtils;
|
||||
import ghidra.app.plugin.core.gotoquery.GoToHelper;
|
||||
import ghidra.app.plugin.core.gotoquery.GoToQueryResultsTableModel;
|
||||
import ghidra.app.plugin.core.navigation.NavigationOptions;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
@ -39,14 +33,12 @@ import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.AddressEvaluator;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.table.AddressArrayTableModel;
|
||||
import ghidra.util.table.GhidraProgramTableModel;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class GoToQuery {
|
||||
@ -60,68 +52,44 @@ public class GoToQuery {
|
||||
|
||||
private QueryData queryData;
|
||||
private Address fromAddress;
|
||||
private GhidraProgramTableModel<?> model;
|
||||
private GoToServiceListener listener;
|
||||
private TaskMonitor monitor;
|
||||
|
||||
protected ProgramGroup programs;
|
||||
private GoToService goToService;
|
||||
private GoToQueryThreadedTableModelListener tableModelListener;
|
||||
private final int maxHits;
|
||||
private final Plugin plugin;
|
||||
private final Navigatable navigatable;
|
||||
private NavigationOptions navigationOptions;
|
||||
|
||||
private PluginTool tool;
|
||||
|
||||
public GoToQuery(Navigatable navigatable, Plugin plugin, GoToService goToService,
|
||||
QueryData queryData, Address fromAddr, GoToServiceListener listener,
|
||||
NavigationOptions navigationOptions, TaskMonitor monitor) {
|
||||
QueryData queryData, Address fromAddr, NavigationOptions navigationOptions,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
this.navigatable = navigatable;
|
||||
this.queryData = queryData;
|
||||
this.plugin = plugin;
|
||||
this.goToService = goToService;
|
||||
this.navigationOptions = navigationOptions;
|
||||
this.tool = plugin.getTool();
|
||||
|
||||
Options opt = plugin.getTool().getOptions(SearchConstants.SEARCH_OPTION_NAME);
|
||||
Options options = plugin.getTool().getOptions(SearchConstants.SEARCH_OPTION_NAME);
|
||||
this.maxHits =
|
||||
opt.getInt(GhidraOptions.OPTION_SEARCH_LIMIT, SearchConstants.DEFAULT_SEARCH_LIMIT);
|
||||
options.getInt(GhidraOptions.OPTION_SEARCH_LIMIT, SearchConstants.DEFAULT_SEARCH_LIMIT);
|
||||
this.fromAddress = fromAddr;
|
||||
this.monitor = monitor;
|
||||
|
||||
if (listener != null) {
|
||||
this.listener = listener;
|
||||
}
|
||||
else {
|
||||
this.listener = new DummyGoToServiceListener();
|
||||
}
|
||||
|
||||
programs = getAllPrograms();
|
||||
tableModelListener = new GoToQueryThreadedTableModelListener();
|
||||
}
|
||||
|
||||
private ProgramGroup getAllPrograms() {
|
||||
ProgramManager progService = plugin.getTool().getService(ProgramManager.class);
|
||||
return new ProgramGroup(progService.getAllOpenPrograms(), navigatable.getProgram());
|
||||
}
|
||||
|
||||
public boolean processQuery() {
|
||||
if (processAddressExpression()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (processWildCard()) {
|
||||
return true;
|
||||
}
|
||||
// Queries can be of several different types. Handle all the non-symbol types first since
|
||||
// they are faster to try, as they don't require searching through all the program's
|
||||
// symbols.
|
||||
|
||||
if (processFileOffset()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (processSymbolInParsedScope()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (processSymbolInPrograms(getSearchPrograms())) {
|
||||
if (processAddressExpression()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -129,242 +97,9 @@ public class GoToQuery {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (processDynamicOrCaseInsensitive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
notifyListener(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkForOverride() {
|
||||
GoToOverrideService override = goToService.getOverrideService();
|
||||
if (override == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ProgramLocation pLoc = override.goTo(queryData.getQueryString());
|
||||
if (pLoc != null) {
|
||||
goToService.goTo(navigatable, pLoc, pLoc.getProgram());
|
||||
notifyListener(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean processAddress() {
|
||||
if (checkForOverride()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String queryString = queryData.getQueryString();
|
||||
for (Program program : getSearchPrograms()) {
|
||||
Address[] addresses = program.parseAddress(queryString, queryData.isCaseSensitive());
|
||||
Address[] validAddresses = validateAddresses(program, addresses);
|
||||
if (validAddresses.length > 0) {
|
||||
goToAddresses(program, validAddresses);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// check once more if the current location has an address for the address string. This
|
||||
// will catch the case where the current location is in FILE space.
|
||||
Program currentProgram = navigatable.getProgram();
|
||||
Address fileAddress = getFileAddress(currentProgram, queryString);
|
||||
if (fileAddress != null) {
|
||||
goToAddresses(currentProgram, new Address[] { fileAddress });
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Address getFileAddress(Program program, String queryString) {
|
||||
if (fromAddress == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Address address = fromAddress.getAddressSpace().getAddress(queryString);
|
||||
if (address != null && program.getMemory().contains(address)) {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
catch (AddressFormatException e) {
|
||||
// ignore and return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void goToAddresses(Program program, Address[] validAddresses) {
|
||||
if (validAddresses.length == 1) {
|
||||
goTo(program, validAddresses[0], fromAddress);
|
||||
notifyListener(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
model = new AddressArrayTableModel("Goto: ", plugin.getTool(), program, validAddresses,
|
||||
monitor);
|
||||
model.addInitialLoadListener(tableModelListener);
|
||||
});
|
||||
}
|
||||
|
||||
private void goToProgramLocations(Program program, List<ProgramLocation> locations) {
|
||||
|
||||
if (locations.size() == 1) {
|
||||
goTo(program, locations.get(0));
|
||||
notifyListener(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
model = new GoToQueryResultsTableModel(program, plugin.getTool(), locations, monitor);
|
||||
model.addInitialLoadListener(tableModelListener);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean processDynamicOrCaseInsensitive() {
|
||||
if (!queryData.isIncludeDynamicLables() && queryData.isCaseSensitive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
model = new GoToQueryResultsTableModel(navigatable.getProgram(), queryData,
|
||||
plugin.getTool(), maxHits, monitor);
|
||||
model.addInitialLoadListener(tableModelListener);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean processSymbolInPrograms(Iterable<Program> searchPrograms) {
|
||||
for (Program program : searchPrograms) {
|
||||
List<ProgramLocation> locations = getValidSymbolLocationsForProgram(program);
|
||||
if (!locations.isEmpty()) {
|
||||
goToProgramLocations(program, locations);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private List<ProgramLocation> getValidSymbolLocationsForProgram(Program program) {
|
||||
|
||||
List<ProgramLocation> locations = new ArrayList<>();
|
||||
SymbolTable symTable = program.getSymbolTable();
|
||||
SymbolIterator it = symTable.getSymbols(queryData.getQueryString());
|
||||
while (it.hasNext() && locations.size() < maxHits) {
|
||||
Symbol symbol = it.next();
|
||||
ProgramLocation location = getProgramLocationForSymbol(symbol, program);
|
||||
if (location != null) {
|
||||
locations.add(location);
|
||||
}
|
||||
else {
|
||||
locations.addAll(getExtenalLinkageLocations(symbol));
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
private Collection<ProgramLocation> getExtenalLinkageLocations(Symbol symbol) {
|
||||
|
||||
Collection<ProgramLocation> locations = new ArrayList<>();
|
||||
Program program = symbol.getProgram();
|
||||
Address[] externalLinkageAddresses =
|
||||
NavigationUtils.getExternalLinkageAddresses(program, symbol.getAddress());
|
||||
for (Address address : externalLinkageAddresses) {
|
||||
ProgramLocation location = GoToHelper.getProgramLocationForAddress(address, program);
|
||||
if (location != null) {
|
||||
locations.add(location);
|
||||
}
|
||||
}
|
||||
return locations;
|
||||
}
|
||||
|
||||
private ProgramLocation getProgramLocationForSymbol(Symbol symbol, Program program) {
|
||||
Address symbolAddress = symbol.getAddress();
|
||||
if (symbolAddress.isExternalAddress()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ((symbolAddress.isMemoryAddress() && !program.getMemory().contains(symbolAddress))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return symbol.getProgramLocation();
|
||||
}
|
||||
|
||||
private boolean processSymbolInParsedScope() {
|
||||
String queryInput = queryData.getQueryString();
|
||||
int colonPos = queryInput.lastIndexOf("::");
|
||||
if (colonPos < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String scopeName = queryInput.substring(0, colonPos);
|
||||
String symbolName = queryInput.substring(colonPos + 2);
|
||||
if (goToSymbolInScope(scopeName, symbolName)) {
|
||||
notifyListener(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Iterable<Program> getSearchPrograms() {
|
||||
return navigationOptions.isGoToRestrictedToCurrentProgram()
|
||||
? Collections.singleton(navigatable.getProgram())
|
||||
: programs;
|
||||
}
|
||||
|
||||
private boolean processAddressExpression() {
|
||||
String queryInput = queryData.getQueryString();
|
||||
if (!isAddressExpression(queryInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean relative = queryInput.matches("^\\s*[+-].*");
|
||||
Address baseAddr = relative ? fromAddress : null;
|
||||
for (Program program : getSearchPrograms()) {
|
||||
Address evalAddr = AddressEvaluator.evaluate(program, baseAddr, queryInput);
|
||||
if (evalAddr != null) {
|
||||
boolean success = goTo(program, new ProgramLocation(program, evalAddr));
|
||||
notifyListener(success);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private QueryData cleanupQuery(Program program, QueryData qData) {
|
||||
String input = qData.getQueryString();
|
||||
int colonPosition = input.indexOf("::");
|
||||
if (colonPosition >= 0) {
|
||||
String preColonString = input.substring(0, colonPosition);
|
||||
if (isAddressSpaceName(program, preColonString) ||
|
||||
isBlockName(program, preColonString)) {
|
||||
// strip off block name or the address space name part
|
||||
input = input.substring(colonPosition + 2); // 2 for both ':' chars
|
||||
qData =
|
||||
new QueryData(input, qData.isCaseSensitive(), qData.isIncludeDynamicLables());
|
||||
}
|
||||
}
|
||||
return qData;
|
||||
}
|
||||
|
||||
private boolean processWildCard() {
|
||||
if (!queryData.isWildCard()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Swing.runIfSwingOrRunLater(() -> {
|
||||
Program program = navigatable.getProgram();
|
||||
model = new GoToQueryResultsTableModel(program, cleanupQuery(program, queryData),
|
||||
plugin.getTool(), maxHits, monitor);
|
||||
model.addInitialLoadListener(tableModelListener);
|
||||
});
|
||||
return true;
|
||||
// none of the specialized query handlers matched, so try to process the query
|
||||
// as a symbol (label, function name, variable name, etc.)
|
||||
return processSymbols();
|
||||
}
|
||||
|
||||
private boolean processFileOffset() {
|
||||
@ -373,7 +108,6 @@ public class GoToQuery {
|
||||
if (matcher.matches()) {
|
||||
try {
|
||||
long offset = Long.decode(matcher.group(1));
|
||||
// NOTE: Addresses are parsed via AbstractAddressSpace.parseString(String addr)
|
||||
Program currentProgram = navigatable.getProgram();
|
||||
Memory mem = currentProgram.getMemory();
|
||||
List<Address> addresses = mem.locateAddressesForFileOffset(offset);
|
||||
@ -389,24 +123,138 @@ public class GoToQuery {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isAddressExpression(String input) {
|
||||
return (input.indexOf('+') >= 0 || input.indexOf('-') >= 0 || input.indexOf('*') > 0);
|
||||
}
|
||||
private boolean processAddressExpression() {
|
||||
String queryInput = queryData.getQueryString();
|
||||
if (!isAddressExpression(queryInput)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isAddressSpaceName(Program program, String input) {
|
||||
return program.getAddressFactory().getAddressSpace(input) != null;
|
||||
}
|
||||
|
||||
private boolean isBlockName(Program program, String input) {
|
||||
MemoryBlock[] blocks = program.getMemory().getBlocks();
|
||||
for (MemoryBlock element : blocks) {
|
||||
if (element.getName().equals(input)) {
|
||||
return true;
|
||||
// checking for leading "+" or "-", ignoring spaces.
|
||||
boolean relative = queryInput.matches("^\\s*[+-].*");
|
||||
Address baseAddr = relative ? fromAddress : null;
|
||||
for (Program program : getSearchPrograms()) {
|
||||
Address evalAddr = AddressEvaluator.evaluate(program, baseAddr, queryInput);
|
||||
if (evalAddr != null) {
|
||||
return goTo(program, new ProgramLocation(program, evalAddr));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean processAddress() {
|
||||
|
||||
String queryString = queryData.getQueryString();
|
||||
for (Program program : getSearchPrograms()) {
|
||||
Address[] addresses = program.parseAddress(queryString, queryData.isCaseSensitive());
|
||||
Address[] validAddresses = validateAddresses(program, addresses);
|
||||
if (validAddresses.length > 0) {
|
||||
return goToAddresses(program, validAddresses);
|
||||
}
|
||||
}
|
||||
|
||||
// check once more if the current location has an address for the address string. This
|
||||
// will catch the case where the current location is in FILE space.
|
||||
Program currentProgram = navigatable.getProgram();
|
||||
Address fileAddress = getFileAddress(currentProgram, queryString);
|
||||
if (fileAddress != null) {
|
||||
return goToAddresses(currentProgram, new Address[] { fileAddress });
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean processSymbols() {
|
||||
GoToSymbolSearchTask task =
|
||||
new GoToSymbolSearchTask(queryData, getSearchPrograms(), maxHits);
|
||||
TaskLauncher.launch(task);
|
||||
|
||||
List<ProgramLocation> locations = task.getResults();
|
||||
if (locations.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Program program = locations.get(0).getProgram();
|
||||
return goToProgramLocations(program, locations);
|
||||
}
|
||||
|
||||
private List<ProgramLocation> toProgramLocations(Address[] addresses, Program program) {
|
||||
return Arrays.stream(addresses).map(a -> new ProgramLocation(program, a)).toList();
|
||||
}
|
||||
|
||||
private Address getFileAddress(Program program, String addressString) {
|
||||
if (fromAddress == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
Address address = fromAddress.getAddressSpace().getAddress(addressString);
|
||||
if (address != null && program.getMemory().contains(address)) {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
catch (AddressFormatException e) {
|
||||
// ignore and return null
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean goToAddresses(Program program, Address[] validAddresses) {
|
||||
List<ProgramLocation> locations = toProgramLocations(validAddresses, program);
|
||||
return goToProgramLocations(program, locations);
|
||||
}
|
||||
|
||||
private boolean goToProgramLocations(Program program, List<ProgramLocation> locations) {
|
||||
|
||||
if (locations.size() == 1) {
|
||||
return goTo(program, locations.get(0));
|
||||
}
|
||||
|
||||
Swing.runIfSwingOrRunLater(() -> showResultsInTable(locations));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void showResultsInTable(List<ProgramLocation> locations) {
|
||||
Program program = locations.get(0).getProgram();
|
||||
if (locations.size() > maxHits) {
|
||||
showMaxSearchWarning(locations.size());
|
||||
}
|
||||
showModelInTable(new GoToQueryResultsTableModel(program, tool, locations, monitor));
|
||||
|
||||
}
|
||||
|
||||
private void showModelInTable(GhidraProgramTableModel<?> model) {
|
||||
|
||||
TableService service = tool.getService(TableService.class);
|
||||
TableComponentProvider<?> provider = service.showTable(
|
||||
"Goto " + queryData.getQueryString(), "Goto", model, "Go To", navigatable);
|
||||
provider.requestFocus();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the programs to search. If searching more than the current program, make sure
|
||||
* the current program is first in the list.
|
||||
* @return the list of program to search with the current program first
|
||||
*/
|
||||
private List<Program> getSearchPrograms() {
|
||||
Program currentProgram = navigatable.getProgram();
|
||||
List<Program> searchPrograms = new ArrayList<>();
|
||||
searchPrograms.add(currentProgram);
|
||||
if (!navigationOptions.isGoToRestrictedToCurrentProgram()) {
|
||||
ProgramManager programManager = plugin.getTool().getService(ProgramManager.class);
|
||||
Program[] allOpenPrograms = programManager.getAllOpenPrograms();
|
||||
for (Program program : allOpenPrograms) {
|
||||
if (program != currentProgram) {
|
||||
searchPrograms.add(program);
|
||||
}
|
||||
}
|
||||
}
|
||||
return searchPrograms;
|
||||
}
|
||||
|
||||
private boolean isAddressExpression(String input) {
|
||||
return (input.indexOf('+') >= 0 || input.indexOf('-') >= 0 || input.indexOf('*') > 0);
|
||||
}
|
||||
|
||||
private Address[] validateAddresses(Program program, Address[] addrs) {
|
||||
Memory memory = program.getMemory();
|
||||
ArrayList<Address> list = new ArrayList<>();
|
||||
@ -447,96 +295,6 @@ public class GoToQuery {
|
||||
return currentSpace.equals(address.getAddressSpace());
|
||||
}
|
||||
|
||||
private boolean goToSymbolInScope(String scopeName, String symbolStr) {
|
||||
for (Program program : getSearchPrograms()) {
|
||||
SymbolTable symTable = program.getSymbolTable();
|
||||
Namespace scope = getScope(program, program.getGlobalNamespace(), scopeName);
|
||||
if (scope != null) {
|
||||
List<Symbol> symbols = symTable.getSymbols(symbolStr, scope);
|
||||
if (!symbols.isEmpty()) {
|
||||
return gotoLabels(program, symbols);
|
||||
}
|
||||
}
|
||||
//else see if scopeName is really memoryBlock name.
|
||||
return goToSymbolInMemoryBlock(scopeName, symbolStr, program);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean gotoLabels(Program program, List<Symbol> symbols) {
|
||||
if (symbols.size() == 1) {
|
||||
return gotoLabel(program, symbols.get(0));
|
||||
}
|
||||
|
||||
List<ProgramLocation> programLocations = new ArrayList<>();
|
||||
|
||||
for (Symbol symbol : symbols) {
|
||||
ProgramLocation programLocation = symbol.getProgramLocation();
|
||||
if (programLocation != null) {
|
||||
programLocations.add(symbol.getProgramLocation());
|
||||
}
|
||||
}
|
||||
|
||||
goToProgramLocations(program, programLocations);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean goToSymbolInMemoryBlock(String scopeName, String symbolStr, Program program) {
|
||||
|
||||
List<Symbol> globalSymbols =
|
||||
program.getSymbolTable().getLabelOrFunctionSymbols(symbolStr, null);
|
||||
if (globalSymbols.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Symbol> matchingSymbols = new ArrayList<>();
|
||||
for (Symbol symbol : globalSymbols) {
|
||||
Address address = symbol.getAddress();
|
||||
MemoryBlock block = program.getMemory().getBlock(address);
|
||||
if (block != null && block.getName().equals(scopeName)) {
|
||||
matchingSymbols.add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingSymbols.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return gotoLabels(program, matchingSymbols);
|
||||
}
|
||||
|
||||
private Namespace getScope(Program program, Namespace parent, String scopeName) {
|
||||
int colonIndex = scopeName.lastIndexOf("::");
|
||||
if (colonIndex >= 0) {
|
||||
String parentScopeName = scopeName.substring(0, colonIndex);
|
||||
scopeName = scopeName.substring(colonIndex + 2);
|
||||
parent = getScope(program, parent, parentScopeName);
|
||||
if (parent == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
SymbolTable symTable = program.getSymbolTable();
|
||||
Namespace namespace = symTable.getNamespace(scopeName, parent);
|
||||
return namespace;
|
||||
}
|
||||
|
||||
private boolean gotoLabel(Program program, Symbol symbol) {
|
||||
if (symbol == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ProgramLocation loc = symbol.getProgramLocation();
|
||||
if (loc == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (goToService.goTo(navigatable, loc, program)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean goTo(Program program, ProgramLocation loc) {
|
||||
if (loc == null) {
|
||||
return false;
|
||||
@ -548,116 +306,10 @@ public class GoToQuery {
|
||||
return goToService.goTo(navigatable, loc, program);
|
||||
}
|
||||
|
||||
private boolean goTo(Program program, Address gotoAddress, Address refAddress) {
|
||||
if (program == null) {
|
||||
program = navigatable.getProgram();
|
||||
}
|
||||
|
||||
if (program.getMemory().contains(gotoAddress)) {
|
||||
goToService.goTo(navigatable, program, gotoAddress, refAddress);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void notifyListener(boolean hasData) {
|
||||
listener.gotoCompleted(queryData.getQueryString(), hasData);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
/**
|
||||
* A class to maintain our collection of open programs and to provide an <code>Iterator</code>
|
||||
* when we need to process the collection. The {@link #iterator()} method has a side-effect
|
||||
* of putting the current program at the front of the <code>Iterator</code> so that the current
|
||||
* program is always searched first when processing the collection of programs.
|
||||
*/
|
||||
protected class ProgramGroup implements Iterable<Program> {
|
||||
|
||||
private List<Program> programList;
|
||||
|
||||
public ProgramGroup(Program[] programs, Program navigatableProgram) {
|
||||
programList = new ArrayList<>(Arrays.asList(programs));
|
||||
if (!programList.contains(navigatableProgram)) {
|
||||
programList.add(navigatableProgram);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Program> iterator() {
|
||||
List<Program> newList = new ArrayList<>(programList);
|
||||
|
||||
Program currentProgram = navigatable.getProgram();
|
||||
int index = newList.indexOf(currentProgram);
|
||||
Collections.swap(newList, 0, index);
|
||||
|
||||
return newList.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
private class GoToQueryThreadedTableModelListener implements ThreadedTableModelListener {
|
||||
|
||||
@Override
|
||||
public void loadPending() {
|
||||
// don't care
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadingStarted() {
|
||||
// don't care
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadingFinished(boolean wasCancelled) {
|
||||
int rowCount = model.getRowCount();
|
||||
boolean hasData = rowCount > 0;
|
||||
if (!hasData) {
|
||||
notifyListener(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (rowCount == 1) {
|
||||
goTo(null, model.getProgramLocation(0, 0));
|
||||
notifyListener(true);
|
||||
return;
|
||||
}
|
||||
|
||||
PluginTool tool = plugin.getTool();
|
||||
if (tool == null) {
|
||||
return; // this can happen if a search is taking place when the tool is closed
|
||||
}
|
||||
|
||||
TableService service = tool.getService(TableService.class);
|
||||
TableComponentProvider<?> provider = service.showTable(
|
||||
"Goto " + queryData.getQueryString(), "Goto", model, "Go To", navigatable);
|
||||
if (model.getRowCount() >= maxHits) {
|
||||
showMaxSearchWarning(provider.getComponent(), model.getRowCount());
|
||||
}
|
||||
|
||||
notifyListener(true);
|
||||
}
|
||||
|
||||
private void showMaxSearchWarning(final Component parent, final int matchCount) {
|
||||
// to parent the following dialog properly, we must make sure the above query results
|
||||
// component has been shown (it gets shown in an invoke later during a docking windows update)
|
||||
SwingUtilities.invokeLater(() -> Msg.showWarn(getClass(), parent,
|
||||
"Search Limit Exceeded!",
|
||||
"Stopped search after finding " + matchCount + " matches.\n" +
|
||||
"The search limit can be changed at Edit->Tool Options, under Search."));
|
||||
}
|
||||
}
|
||||
|
||||
private class DummyGoToServiceListener implements GoToServiceListener {
|
||||
@Override
|
||||
public void gotoCompleted(String queryString, boolean foundResults) {
|
||||
// stubbed
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gotoFailed(Exception exc) {
|
||||
// stubbed
|
||||
}
|
||||
private void showMaxSearchWarning(int matchCount) {
|
||||
Msg.showWarn(getClass(), null,
|
||||
"Search Limit Exceeded!",
|
||||
"Stopped search after finding " + matchCount + " matches.\n" +
|
||||
"The search limit can be changed at Edit->Tool Options, under Search.");
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
@ -139,10 +139,14 @@ public class GoToServiceImpl implements GoToService {
|
||||
navigatable = defaultNavigatable;
|
||||
}
|
||||
|
||||
GoToQuery query = new GoToQuery(navigatable, plugin, this, queryData, fromAddr, listener,
|
||||
GoToQuery query = new GoToQuery(navigatable, plugin, this, queryData, fromAddr,
|
||||
helper.getOptions(), monitor);
|
||||
|
||||
return query.processQuery();
|
||||
boolean result = query.processQuery();
|
||||
if (listener != null) {
|
||||
listener.gotoCompleted(queryData.getQueryString(), result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,55 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.navigation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.services.QueryData;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.Task;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Task for searching for symbols. All the logic for the search is done by the
|
||||
* {@link SymbolSearcher}.
|
||||
*/
|
||||
public class GoToSymbolSearchTask extends Task {
|
||||
|
||||
private QueryData queryData;
|
||||
private List<Program> searchPrograms;
|
||||
private List<ProgramLocation> results;
|
||||
private int limit;
|
||||
|
||||
public GoToSymbolSearchTask(QueryData queryData, List<Program> searchPrograms, int limit) {
|
||||
super("Searching Symbols...");
|
||||
this.queryData = queryData;
|
||||
this.searchPrograms = searchPrograms;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(TaskMonitor monitor) throws CancelledException {
|
||||
SymbolSearcher searcher = new SymbolSearcher(queryData, limit, monitor);
|
||||
results = searcher.findMatchingSymbolLocations(searchPrograms);
|
||||
}
|
||||
|
||||
public List<ProgramLocation> getResults() {
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,258 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.navigation;
|
||||
|
||||
import static ghidra.util.UserSearchUtils.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import ghidra.app.services.QueryData;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.program.model.symbol.Namespace;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
|
||||
/**
|
||||
* Class for matching symbol names with or without namespace paths and wildcards.
|
||||
*/
|
||||
public class SymbolMatcher {
|
||||
|
||||
private String symbolName;
|
||||
private Pattern pattern;
|
||||
private boolean caseSensitive;
|
||||
private boolean isRelativePath;
|
||||
private boolean isPossibleMemoryBlockPattern;
|
||||
|
||||
public SymbolMatcher(String queryString, boolean caseSensitive) {
|
||||
// assume users entering spaces is a mistake, so just remove them.
|
||||
queryString = queryString.replaceAll("\\s", "");
|
||||
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.isRelativePath = !queryString.startsWith(Namespace.DELIMITER);
|
||||
this.symbolName = getSymbolName(queryString);
|
||||
this.pattern = createPattern(queryString);
|
||||
this.isPossibleMemoryBlockPattern = checkIfPossibleMemoryBlockPattern(queryString);
|
||||
}
|
||||
|
||||
private boolean checkIfPossibleMemoryBlockPattern(String queryString) {
|
||||
// A legacy feature is the ability to also be able to find a label in a particular memory
|
||||
// block using the same syntax as a symbol in a namespace. So something like
|
||||
// "block::bob" would find the symbol "bob" (regardless of its namespace) if it were
|
||||
// in a memory block named "block". (Also, this only worked if there wasn't also a
|
||||
// symbol in a namespace named "block"). Now that wildcards are supported in the namespace
|
||||
// specifications, this feature becomes even more confusing. To avoid this, the legacy
|
||||
// memory block feature will not support wildcards and probably should be removed
|
||||
// at some point.
|
||||
|
||||
int lastIndexOf = queryString.lastIndexOf(Namespace.DELIMITER);
|
||||
|
||||
// if no delimiter exists or it starts with a delimiter, then it can't match a memory block
|
||||
if (lastIndexOf < 1) {
|
||||
return false;
|
||||
}
|
||||
String qualifierPart = queryString.substring(0, lastIndexOf);
|
||||
|
||||
// if the qualifier is a multi part path, then it can't match a memory block
|
||||
if (qualifierPart.indexOf(Namespace.DELIMITER) >= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// we don't support wildcard when matching against memory block names
|
||||
return !qualifierPart.contains("*") && !qualifierPart.contains("?");
|
||||
}
|
||||
|
||||
private String getSymbolName(String queryString) {
|
||||
int index = queryString.lastIndexOf(Namespace.DELIMITER);
|
||||
if (index < 0) {
|
||||
return queryString;
|
||||
}
|
||||
return queryString.substring(index + Namespace.DELIMITER.length());
|
||||
}
|
||||
|
||||
private Pattern createPattern(String userInput) {
|
||||
// We only support globbing characters in the query, any other regex characters need
|
||||
// to be escaped before we feed it to Java's regex Pattern class. But we need to do it
|
||||
// before we begin our substitutions as we will be adding some of those character into
|
||||
// the query string and we don't want those to be escaped.
|
||||
String s = escapeNonGlobbingRegexCharacters(userInput);
|
||||
s = replaceNamespaceDelimiters(s);
|
||||
s = removeExcessStars(s);
|
||||
s = convertNameGlobingToRegEx(s);
|
||||
s = convertPathGlobingToRegEx(s);
|
||||
s = convertRelativePathToRegEx(s);
|
||||
|
||||
return Pattern.compile(s, createRegexOptions());
|
||||
}
|
||||
|
||||
private String removeExcessStars(String s) {
|
||||
// There is never a reason to have 3 or more stars in the query. To avoid errors
|
||||
// creating a regex pattern, replace runs of 3 or more starts with two stars.
|
||||
// Later, the method that handles path globbing (**) chars, will either convert
|
||||
// ** to a path matching expression, or if not valid in its location, to a
|
||||
// single * regex pattern.
|
||||
|
||||
int start = s.indexOf("***");
|
||||
while (start >= 0) {
|
||||
int end = findFirstNonStar(s, start);
|
||||
s = s.substring(0, start + 2) + s.substring(end);
|
||||
start = s.indexOf("***");
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
private int findFirstNonStar(String query, int index) {
|
||||
while (index < query.length() && query.charAt(index) == '*') {
|
||||
index++;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
private String replaceNamespaceDelimiters(String s) {
|
||||
// To make regex processing easier, replace any namespace delimiter ("::") with
|
||||
// a single character delimiter. We chose the space character because spaces can't
|
||||
// exist in namespace names or symbol names.
|
||||
|
||||
// also we remove any starting delimiters
|
||||
if (!isRelativePath) {
|
||||
s = s.substring(Namespace.DELIMITER.length());
|
||||
}
|
||||
|
||||
return s.replaceAll(Namespace.DELIMITER, " ");
|
||||
}
|
||||
|
||||
private String convertPathGlobingToRegEx(String s) {
|
||||
// Path globbing uses "**" to match any number of namespace elements in the symbol path
|
||||
// Valid examples of path globbing are "a::**::b", "**::a", or "a::**".
|
||||
//
|
||||
// In order to handle the case where it matches zero path elements ("a::**::b" should
|
||||
// match "a::b"), we need to remove either the starting delimiter or the ending delimiter.
|
||||
// Also note that we are doing this replacement after all "::" have been replaced by spaces.
|
||||
|
||||
// First replace " ** " with a regex pattern that matches either: a space followed by one
|
||||
// or more characters followed by another space; or a single space. The second case
|
||||
// handles when the ** matches zero elements such as "a::**::b" matches "a::b".
|
||||
s = s.replaceAll(" \\*\\* ", "( .* | )");
|
||||
|
||||
// If the string starts with "** ", replace it with the regex pattern that matches either:
|
||||
// anything followed by a space; or nothing at all.
|
||||
s = s.replaceAll("^\\*\\* ", "(.* |)");
|
||||
|
||||
// If the string ends with " **", replace it with the regex pattern that matches a space
|
||||
// followed by anything.
|
||||
s = s.replaceAll(" \\*\\*$", " .*");
|
||||
|
||||
// Finally, any other "**", not handled is considered a mistake and is treated as if a
|
||||
// single start was entered, which is mapped to the regex expression any number of
|
||||
// non-space characters.
|
||||
s = s.replaceAll("\\*\\*", "[^ ]*");
|
||||
return s;
|
||||
}
|
||||
|
||||
private String convertNameGlobingToRegEx(String s) {
|
||||
// Name globing here refers to using the "*" or "?" globbing characters. However,
|
||||
// we only want them to apply to a single namespace or symbol name element. In other words
|
||||
// we can't use the reg-ex ".*" because it would match across delimiters which we don't
|
||||
// want. The alternative is to use the "match everything but" construct where we use
|
||||
// "[^ ] which means match anything but spaces which is the delimiter we are using.
|
||||
//
|
||||
// There is a wrinkle for this substitution. We are replacing only single "*" characters,
|
||||
// but we need to avoid doubles (**) as those will be handled later by the path globbing.
|
||||
// To do this we used look ahead and look behind regex to only match single stars and not
|
||||
// double stars.
|
||||
|
||||
// replace single "*" with the regex pattern that matches everything but spaces
|
||||
s = s.replaceAll("(?<!\\*)\\*(?!\\*)", "[^ ]*");
|
||||
|
||||
// replace "?" with regex pattern that matches a single character
|
||||
s = s.replaceAll("\\?", ".");
|
||||
return s;
|
||||
}
|
||||
|
||||
private String convertRelativePathToRegEx(String s) {
|
||||
// If the query is relative, add a "match anything" regex pattern to the front of the query
|
||||
// so that it will match any number of parent namespaces containing the specified
|
||||
// symbol/namespace path.
|
||||
if (!s.isBlank() && isRelativePath) {
|
||||
s = ".*" + s;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public String getSymbolName() {
|
||||
return symbolName;
|
||||
}
|
||||
|
||||
private int createRegexOptions() {
|
||||
if (!caseSensitive) {
|
||||
return Pattern.CASE_INSENSITIVE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the symbol name part of the query string has no wildcards and is
|
||||
* case sensitive.
|
||||
* @return true if the query has no wildcards and is case sensitive.
|
||||
*/
|
||||
public boolean hasFullySpecifiedName() {
|
||||
return !QueryData.hasWildCards(symbolName) && caseSensitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there are wildcards in the symbol name.
|
||||
* @return true if there are wildcards in the symbol name
|
||||
*/
|
||||
public boolean hasWildCardsInSymbolName() {
|
||||
return QueryData.hasWildCards(symbolName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given symbol matches the query specification for this matcher.
|
||||
* @param symbol the symbol to test
|
||||
* @return true if the given symbol matches the query specification for this matcher
|
||||
*/
|
||||
public boolean matches(Symbol symbol) {
|
||||
String path = createSymbolPathWithSpaces(symbol);
|
||||
if (pattern.matcher(path).matches()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// legacy feature where the query may have specified a memory block name instead of a
|
||||
// namespace path.
|
||||
return checkMemoryBlockName(symbol);
|
||||
}
|
||||
|
||||
private String createSymbolPathWithSpaces(Symbol symbol) {
|
||||
String[] path = symbol.getPath();
|
||||
return Arrays.stream(path).collect(Collectors.joining(" "));
|
||||
}
|
||||
|
||||
private boolean checkMemoryBlockName(Symbol symbol) {
|
||||
if (!isPossibleMemoryBlockPattern) {
|
||||
return false;
|
||||
}
|
||||
Program program = symbol.getProgram();
|
||||
|
||||
MemoryBlock block = program.getMemory().getBlock(symbol.getAddress());
|
||||
if (block != null) {
|
||||
String blockNamePath = block.getName() + " " + symbol.getName();
|
||||
return pattern.matcher(blockNamePath).matches();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,227 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.navigation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.app.nav.NavigationUtils;
|
||||
import ghidra.app.plugin.core.gotoquery.GoToHelper;
|
||||
import ghidra.app.services.QueryData;
|
||||
import ghidra.program.model.address.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Class for searching for symbols that match a given query string.
|
||||
* <P>
|
||||
* The query string may include full or partial (absolute or relative) namespace path information.
|
||||
* The standard namespace delimiter ("::") is used to separate the query into it separate pieces,
|
||||
* with each piece used to either match a namespace or a symbol name, with the symbol
|
||||
* name piece always being the last piece (or the only piece).
|
||||
* <P>
|
||||
* Both the namespace pieces and the symbol name piece may contain wildcards ("*" or "?") and those
|
||||
* wildcards only apply to a single element. For example, if a symbol's full path was "a::b::c::d"
|
||||
* and the query was "a::*::d", it would not match as the "*" can only match one element.
|
||||
* <P>
|
||||
* By default all queries are considered relative. In other words, the first namespace element
|
||||
* does not need to be at the root global level. For example, in the "a::b::c::d" example, the "d"
|
||||
* symbol could be found by "d", "c::d", "b::c::d". To avoid this behavior, the query may begin
|
||||
* with a "::" delimiter which means the path is absolute and the first element must be at the
|
||||
* root level. So, in the previous example, "::a::b::c::d" would match but, "::c::d" would not.
|
||||
* <P>
|
||||
* There are also two parameters in the QueryData object that affect how the search algorithm is
|
||||
* conducted. One is "Case Sensitive" and the other is "Include Dynamic Labels". If the search
|
||||
* is case insensitive or there are wild cards in the symbol name, the only option is to do a full
|
||||
* search of all defined symbols, looking for matches. If that is not the case, the search can
|
||||
* do a direct look up of matching symbols using the program database's symbol index.
|
||||
* <P>
|
||||
* If the "Include Dynamic Labels" options is on, then a brute force of the defined references is
|
||||
* also performed, looking at all addresses that a reference points to, getting the dynamic
|
||||
* (not stored) symbol at that address and checking if it matches.
|
||||
* <P>
|
||||
* One last behavior to note is that the search takes a list of programs to search. However, it
|
||||
* only returns results from the FIRST program to have any results. If the need to search all
|
||||
* programs completely is ever needed, a second "find" method could easily be added.
|
||||
*/
|
||||
public class SymbolSearcher {
|
||||
|
||||
private SymbolMatcher symbolMatcher;
|
||||
private QueryData queryData;
|
||||
private int limit;
|
||||
private TaskMonitor monitor;
|
||||
|
||||
public SymbolSearcher(QueryData data, int limit, TaskMonitor monitor) {
|
||||
this.queryData = data;
|
||||
this.limit = limit;
|
||||
this.monitor = monitor;
|
||||
this.symbolMatcher =
|
||||
new SymbolMatcher(queryData.getQueryString(), queryData.isCaseSensitive());
|
||||
}
|
||||
|
||||
public List<ProgramLocation> findMatchingSymbolLocations(List<Program> searchPrograms) {
|
||||
|
||||
List<ProgramLocation> locations = new ArrayList<>();
|
||||
for (Program program : searchPrograms) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
if (findMatchingSymbolLocations(program, locations)) {
|
||||
return locations;
|
||||
}
|
||||
}
|
||||
|
||||
return locations;
|
||||
}
|
||||
|
||||
private boolean findMatchingSymbolLocations(Program program, List<ProgramLocation> locations) {
|
||||
|
||||
if (!findSymbolsByDirectLookup(program, locations)) {
|
||||
findSymbolsByBruteForce(program, locations);
|
||||
}
|
||||
|
||||
return !locations.isEmpty();
|
||||
}
|
||||
|
||||
private boolean findSymbolsByDirectLookup(Program program, List<ProgramLocation> locations) {
|
||||
|
||||
// can only do direct lookup of symbol name if it has no wildcards and is case sensitive
|
||||
if (!symbolMatcher.hasFullySpecifiedName()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String symbolName = symbolMatcher.getSymbolName();
|
||||
return scanSymbols(program, program.getSymbolTable().getSymbols(symbolName), locations);
|
||||
}
|
||||
|
||||
private void findSymbolsByBruteForce(Program program, List<ProgramLocation> locations) {
|
||||
|
||||
// only need to do this if the name is fuzzy; otherwise a direct lookup already happened
|
||||
if (!symbolMatcher.hasFullySpecifiedName()) {
|
||||
searchDefinedSymbols(program, locations);
|
||||
}
|
||||
|
||||
// if dynamic symbols are on, we also need to search through references, looking for default
|
||||
// symbol names (LAB*, FUN*, etc.)
|
||||
if (queryData.isIncludeDynamicLables()) {
|
||||
searchDynamicSymbolsByReference(program, locations);
|
||||
}
|
||||
}
|
||||
|
||||
private void searchDynamicSymbolsByReference(Program program, List<ProgramLocation> locations) {
|
||||
|
||||
if (!symbolMatcher.hasWildCardsInSymbolName()) {
|
||||
// if no wild cards, just parse off the address from the string and go there.
|
||||
parseDynamic(program, locations);
|
||||
return;
|
||||
}
|
||||
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
ReferenceManager refMgr = program.getReferenceManager();
|
||||
AddressSet addressSet = program.getAddressFactory().getAddressSet();
|
||||
AddressIterator addrIt = refMgr.getReferenceDestinationIterator(addressSet, true);
|
||||
while (addrIt.hasNext() && locations.size() < limit) {
|
||||
if (monitor.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
Address addr = addrIt.next();
|
||||
Symbol s = symbolTable.getPrimarySymbol(addr);
|
||||
if (s.isDynamic()) {
|
||||
addSymbolIfMatches(s, locations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean addSymbolIfMatches(Symbol s, List<ProgramLocation> locations) {
|
||||
if (symbolMatcher.matches(s)) {
|
||||
ProgramLocation programLocation = getProgramLocationForSymbol(s);
|
||||
if (programLocation != null) {
|
||||
locations.add(programLocation);
|
||||
return true;
|
||||
}
|
||||
return addExternalLinkageLocations(s, locations);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean addExternalLinkageLocations(Symbol symbol, List<ProgramLocation> locations) {
|
||||
boolean addedLocations = false;
|
||||
Program program = symbol.getProgram();
|
||||
Address[] externalLinkageAddresses =
|
||||
NavigationUtils.getExternalLinkageAddresses(program, symbol.getAddress());
|
||||
for (Address address : externalLinkageAddresses) {
|
||||
ProgramLocation location = GoToHelper.getProgramLocationForAddress(address, program);
|
||||
if (location != null) {
|
||||
addedLocations = true;
|
||||
locations.add(location);
|
||||
}
|
||||
}
|
||||
return addedLocations;
|
||||
}
|
||||
|
||||
private void parseDynamic(Program program, List<ProgramLocation> locations) {
|
||||
AddressFactory addressFactory = program.getAddressFactory();
|
||||
String symbolName = symbolMatcher.getSymbolName();
|
||||
Address address = SymbolUtilities.parseDynamicName(addressFactory, symbolName);
|
||||
|
||||
if (address == null) {
|
||||
return;
|
||||
}
|
||||
Symbol s = program.getSymbolTable().getPrimarySymbol(address);
|
||||
addSymbolIfMatches(s, locations);
|
||||
}
|
||||
|
||||
private void searchDefinedSymbols(Program program, List<ProgramLocation> locations) {
|
||||
String symbolName = symbolMatcher.getSymbolName();
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
SymbolIterator it = symbolTable.getSymbolIterator(symbolName, queryData.isCaseSensitive());
|
||||
|
||||
scanSymbols(program, it, locations);
|
||||
}
|
||||
|
||||
private boolean scanSymbols(Program program, SymbolIterator it,
|
||||
List<ProgramLocation> locations) {
|
||||
|
||||
boolean addedSymbols = false;
|
||||
while (it.hasNext() && locations.size() < limit) {
|
||||
if (monitor.isCancelled()) {
|
||||
break;
|
||||
}
|
||||
Symbol symbol = it.next();
|
||||
addedSymbols |= addSymbolIfMatches(symbol, locations);
|
||||
}
|
||||
return addedSymbols;
|
||||
}
|
||||
|
||||
private ProgramLocation getProgramLocationForSymbol(Symbol symbol) {
|
||||
Address symbolAddress = symbol.getAddress();
|
||||
|
||||
if (symbolAddress.isExternalAddress()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Memory memory = symbol.getProgram().getMemory();
|
||||
if ((symbolAddress.isMemoryAddress() && !memory.contains(symbolAddress))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return symbol.getProgramLocation();
|
||||
}
|
||||
|
||||
}
|
@ -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,9 +17,9 @@ package ghidra.app.plugin.core.navigation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JCheckBox;
|
||||
|
||||
import org.junit.*;
|
||||
@ -38,20 +38,17 @@ import ghidra.app.cmd.label.CreateNamespacesCmd;
|
||||
import ghidra.app.cmd.refs.AddMemRefCmd;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||
import ghidra.app.plugin.core.gotoquery.GoToQueryResultsTableModel;
|
||||
import ghidra.app.plugin.core.progmgr.MultiTabPlugin;
|
||||
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.app.plugin.core.table.TableServicePlugin;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.app.services.QueryData;
|
||||
import ghidra.app.util.MemoryBlockUtils;
|
||||
import ghidra.app.util.SearchConstants;
|
||||
import ghidra.app.util.bin.ByteArrayProvider;
|
||||
import ghidra.app.util.navigation.GoToAddressLabelDialog;
|
||||
import ghidra.framework.options.*;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.ServiceProviderStub;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.database.mem.FileBytes;
|
||||
@ -65,10 +62,8 @@ import ghidra.program.model.symbol.*;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.program.util.VariableNameFieldLocation;
|
||||
import ghidra.test.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.table.GhidraProgramTableModel;
|
||||
import ghidra.util.table.field.LabelTableColumn;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import util.CollectionUtils;
|
||||
|
||||
@ -81,7 +76,6 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
|
||||
private GoToAddressLabelDialog dialog;
|
||||
private CodeBrowserPlugin cbPlugin;
|
||||
private CodeViewerProvider provider;
|
||||
private JButton okButton;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@ -97,7 +91,6 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
|
||||
provider = cbPlugin.getProvider();
|
||||
showTool(tool);
|
||||
dialog = plugin.getDialog();
|
||||
okButton = (JButton) TestUtils.getInstanceField("okButton", dialog);
|
||||
setCaseSensitive(true);
|
||||
}
|
||||
|
||||
@ -628,14 +621,9 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
|
||||
tx(program, () -> {
|
||||
symbol.setName("COmlg32.dll_PageSetupDlgW", SourceType.USER_DEFINED);
|
||||
});
|
||||
|
||||
JCheckBox cb = findComponent(dialog, JCheckBox.class);
|
||||
runSwing(() -> {
|
||||
cb.setSelected(false);
|
||||
dialog.setText("COm*");
|
||||
|
||||
dialog.okCallback();
|
||||
});
|
||||
setCaseSensitive(false);
|
||||
setText("COm*");
|
||||
performOkCallback();
|
||||
|
||||
GhidraProgramTableModel<?> model = waitForModel();
|
||||
assertEquals(3, model.getRowCount());
|
||||
@ -809,16 +797,13 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
|
||||
//
|
||||
|
||||
loadProgram("x86");
|
||||
setText("*");
|
||||
setCaseSensitive(true);
|
||||
performOkCallback();
|
||||
|
||||
boolean caseSensitive = true;
|
||||
List<String> list = search("*", caseSensitive);
|
||||
assertTrue("A wildcard search did not find all symbols - found: " + list, list.size() > 20);
|
||||
|
||||
list = search("dat*", caseSensitive);
|
||||
assertEquals(0, list.size());
|
||||
|
||||
list = search("DAT*", caseSensitive);
|
||||
assertItemsStartWtih(list, "DAT");
|
||||
GhidraProgramTableModel<?> model = waitForModel();
|
||||
List<?> list = model.getModelData();
|
||||
assertTrue("A wildcard search did not find all symbols, found " + list, list.size() > 20);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -862,50 +847,6 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
|
||||
});
|
||||
}
|
||||
|
||||
private void assertItemsStartWtih(List<String> list, String prefix) {
|
||||
for (String s : list) {
|
||||
assertTrue(String.format("List item '%s' does not start with prefix '%s'", s, prefix),
|
||||
s.startsWith(prefix));
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> search(String text, boolean caseSensitive) {
|
||||
|
||||
QueryData queryData = new QueryData(text, caseSensitive, true);
|
||||
|
||||
GoToQueryResultsTableModel model = runSwing(() -> {
|
||||
int maxHits = 100;
|
||||
return new GoToQueryResultsTableModel(program, queryData, new ServiceProviderStub(),
|
||||
maxHits, TaskMonitor.DUMMY);
|
||||
});
|
||||
|
||||
waitForTableModel(model);
|
||||
|
||||
int columnIndex = model.getColumnIndex(LabelTableColumn.class);
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
List<ProgramLocation> data = model.getModelData();
|
||||
|
||||
if (data.isEmpty()) {
|
||||
// debug
|
||||
Msg.debug(this, "No search results found *or* failure to wait for threaded table " +
|
||||
"model - " + "trying again");
|
||||
printOpenWindows();
|
||||
|
||||
waitForTableModel(model);
|
||||
data = model.getModelData();
|
||||
|
||||
Msg.debug(this, "\twaited again--still empty? - size: " + data.size());
|
||||
}
|
||||
|
||||
for (ProgramLocation loc : data) {
|
||||
String label = (String) model.getColumnValueForRow(loc, columnIndex);
|
||||
results.add(label);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private void loadProgram(String programName) throws Exception {
|
||||
|
||||
program = doLoadProgram(programName);
|
||||
@ -1048,21 +989,11 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
|
||||
}
|
||||
|
||||
private GhidraProgramTableModel<?> waitForModel() throws Exception {
|
||||
int i = 0;
|
||||
while (i++ < 50) {
|
||||
TableComponentProvider<?>[] providers = getProviders();
|
||||
if (providers.length > 0) {
|
||||
GThreadedTablePanel<?> panel = (GThreadedTablePanel<?>) TestUtils
|
||||
.getInstanceField("threadedPanel", providers[0]);
|
||||
GTable table = panel.getTable();
|
||||
while (panel.isBusy()) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
return (GhidraProgramTableModel<?>) table.getModel();
|
||||
}
|
||||
Thread.sleep(50);
|
||||
}
|
||||
throw new Exception("Unable to get threaded table model");
|
||||
TableComponentProvider<?> tableProvider =
|
||||
waitForComponentProvider(TableComponentProvider.class);
|
||||
GhidraProgramTableModel<?> model = tableProvider.getModel();
|
||||
waitForTableModel(model);
|
||||
return model;
|
||||
}
|
||||
|
||||
private TableComponentProvider<?>[] getProviders() {
|
||||
@ -1102,14 +1033,6 @@ public class GoToAddressLabelPluginTest extends AbstractGhidraHeadedIntegrationT
|
||||
|
||||
private void performOkCallback() throws Exception {
|
||||
runSwing(() -> dialog.okCallback());
|
||||
|
||||
waitForSwing();
|
||||
waitForOKCallback();
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void waitForOKCallback() {
|
||||
waitForCondition(() -> runSwing(() -> okButton.isEnabled()));
|
||||
}
|
||||
|
||||
private void assumeCurrentAddressSpace(boolean b) {
|
||||
|
@ -0,0 +1,239 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.plugin.core.navigation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import javax.swing.JCheckBox;
|
||||
|
||||
import org.junit.*;
|
||||
|
||||
import generic.test.TestUtils;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeBrowserPlugin;
|
||||
import ghidra.app.plugin.core.codebrowser.CodeViewerProvider;
|
||||
import ghidra.app.plugin.core.progmgr.MultiTabPlugin;
|
||||
import ghidra.app.plugin.core.progmgr.ProgramManagerPlugin;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.app.util.navigation.GoToAddressLabelDialog;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.test.*;
|
||||
import ghidra.util.Swing;
|
||||
import ghidra.util.table.GhidraProgramTableModel;
|
||||
|
||||
public class GoToAddressLabelPluginWithNamespaceTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
private TestEnv env;
|
||||
private PluginTool tool;
|
||||
private AddressFactory addrFactory;
|
||||
private Program program;
|
||||
private GoToAddressLabelPlugin plugin;
|
||||
private GoToAddressLabelDialog dialog;
|
||||
private CodeBrowserPlugin cbPlugin;
|
||||
private CodeViewerProvider provider;
|
||||
private GhidraProgramTableModel<?> model;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
env = new TestEnv();
|
||||
tool = env.getTool();
|
||||
tool.addPlugin(GoToAddressLabelPlugin.class.getName());
|
||||
tool.addPlugin(ProgramManagerPlugin.class.getName());
|
||||
tool.addPlugin(CodeBrowserPlugin.class.getName());
|
||||
tool.addPlugin(MultiTabPlugin.class.getName());
|
||||
|
||||
plugin = env.getPlugin(GoToAddressLabelPlugin.class);
|
||||
cbPlugin = env.getPlugin(CodeBrowserPlugin.class);
|
||||
provider = cbPlugin.getProvider();
|
||||
showTool(tool);
|
||||
buildProgram();
|
||||
final ProgramManager pm = tool.getService(ProgramManager.class);
|
||||
runSwing(() -> pm.openProgram(program.getDomainFile()));
|
||||
|
||||
dialog = plugin.getDialog();
|
||||
setCaseSensitive(true);
|
||||
showDialog();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
closeAllWindows();
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExactSearch() throws Exception {
|
||||
setText("billy");
|
||||
performOkCallback();
|
||||
assertEquals(addr("00001000"), cbPlugin.getCurrentAddress());
|
||||
|
||||
model = waitForModel();
|
||||
assertEquals(4, model.getRowCount());
|
||||
|
||||
assertRow(0, "00001000", "billy");
|
||||
assertRow(1, "00001001", "billy");
|
||||
assertRow(2, "00001002", "billy");
|
||||
assertRow(3, "00001003", "billy");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWildInSymbolName() throws Exception {
|
||||
setText("*ll*");
|
||||
performOkCallback();
|
||||
|
||||
model = waitForModel();
|
||||
assertEquals(8, model.getRowCount());
|
||||
|
||||
assertRow(0, "00001000", "billy");
|
||||
assertRow(1, "00001001", "billy");
|
||||
assertRow(2, "00001002", "billy");
|
||||
assertRow(3, "00001003", "billy");
|
||||
assertRow(4, "00001004", "sally");
|
||||
assertRow(5, "00001005", "sally");
|
||||
assertRow(6, "00001006", "sally");
|
||||
assertRow(7, "00001007", "sally");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWildWithSpecifiedNamespace() throws Exception {
|
||||
setText("parent::*");
|
||||
performOkCallback();
|
||||
assertEquals(addr("00001000"), cbPlugin.getCurrentAddress());
|
||||
|
||||
model = waitForModel();
|
||||
assertEquals(6, model.getRowCount());
|
||||
|
||||
assertRow(0, "00001001", "billy");
|
||||
assertRow(1, "00001002", "billy");
|
||||
assertRow(2, "00001003", "billy");
|
||||
assertRow(3, "00001005", "sally");
|
||||
assertRow(4, "00001006", "sally");
|
||||
assertRow(5, "00001007", "sally");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWildSymbolNameForSpecificParent() throws Exception {
|
||||
setText("parent2::*");
|
||||
performOkCallback();
|
||||
|
||||
model = waitForModel();
|
||||
assertEquals(3, model.getRowCount());
|
||||
|
||||
assertRow(0, "00001009", "bob");
|
||||
assertRow(1, "0000100a", "bob");
|
||||
assertRow(2, "0000100b", "bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWildSymbolNameAndWildParentInSpecifiedUpperNamesapce() throws Exception {
|
||||
setText("middle::*::*");
|
||||
performOkCallback();
|
||||
|
||||
model = waitForModel();
|
||||
assertEquals(6, model.getRowCount());
|
||||
|
||||
assertRow(0, "00001002", "billy");
|
||||
assertRow(1, "00001003", "billy");
|
||||
assertRow(2, "00001006", "sally");
|
||||
assertRow(3, "00001007", "sally");
|
||||
assertRow(4, "0000100a", "bob");
|
||||
assertRow(5, "0000100b", "bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAbsoluteNamespacePathWithWilds() throws Exception {
|
||||
setText("::middle::*::*");
|
||||
performOkCallback();
|
||||
|
||||
model = waitForModel();
|
||||
assertEquals(3, model.getRowCount());
|
||||
|
||||
assertRow(0, "00001002", "billy");
|
||||
assertRow(1, "00001006", "sally");
|
||||
assertRow(2, "0000100a", "bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoMatches() throws Exception {
|
||||
setText("xyz");
|
||||
performOkCallback();
|
||||
waitForTasks();
|
||||
assertTrue(dialog.isVisible());
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
private void assertRow(int row, String address, String name) {
|
||||
assertEquals(address, model.getValueAt(row, 0).toString());
|
||||
assertEquals(name, model.getValueAt(row, 1));
|
||||
}
|
||||
|
||||
private void buildProgram() throws Exception {
|
||||
ToyProgramBuilder builder = new ToyProgramBuilder("Test", false);
|
||||
|
||||
builder.createMemory("Block1", "0x1000", 1000);
|
||||
|
||||
builder.createLabel("0x1000", "billy");
|
||||
builder.createLabel("0x1001", "billy", "parent");
|
||||
builder.createLabel("0x1002", "billy", "middle::parent");
|
||||
builder.createLabel("0x1003", "billy", "top::middle::parent");
|
||||
builder.createLabel("0x1004", "sally");
|
||||
builder.createLabel("0x1005", "sally", "parent");
|
||||
builder.createLabel("0x1006", "sally", "middle::parent");
|
||||
builder.createLabel("0x1007", "sally", "top::middle::parent");
|
||||
builder.createLabel("0x1008", "bob");
|
||||
builder.createLabel("0x1009", "bob", "parent2");
|
||||
builder.createLabel("0x100a", "bob", "middle::parent2");
|
||||
builder.createLabel("0x100b", "bob", "top::middle::parent2");
|
||||
program = builder.getProgram();
|
||||
addrFactory = program.getAddressFactory();
|
||||
}
|
||||
|
||||
private GhidraProgramTableModel<?> waitForModel() throws Exception {
|
||||
TableComponentProvider<?> tableProvider =
|
||||
waitForComponentProvider(TableComponentProvider.class);
|
||||
GhidraProgramTableModel<?> tableModel = tableProvider.getModel();
|
||||
waitForTableModel(tableModel);
|
||||
return tableModel;
|
||||
}
|
||||
|
||||
private void setCaseSensitive(final boolean selected) {
|
||||
final JCheckBox checkBox =
|
||||
(JCheckBox) TestUtils.getInstanceField("caseSensitiveBox", dialog);
|
||||
runSwing(() -> checkBox.setSelected(selected));
|
||||
}
|
||||
|
||||
private Address addr(String address) {
|
||||
return addrFactory.getAddress(address);
|
||||
}
|
||||
|
||||
private void showDialog() {
|
||||
Swing.runLater(() -> dialog.show(provider, cbPlugin.getCurrentAddress(), tool));
|
||||
waitForSwing();
|
||||
}
|
||||
|
||||
private void setText(final String text) throws Exception {
|
||||
runSwing(() -> dialog.setText(text));
|
||||
}
|
||||
|
||||
private void performOkCallback() throws Exception {
|
||||
runSwing(() -> dialog.okCallback());
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
@ -251,6 +251,6 @@ public class OrganizationNodeTest extends AbstractDockingTest {
|
||||
}
|
||||
|
||||
private GTreeNode node(String name) {
|
||||
return new CodeSymbolNode(null, new StubSymbol(name, null));
|
||||
return new CodeSymbolNode(null, new StubSymbol(name));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,482 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.navigation;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import ghidra.program.model.StubProgram;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
|
||||
public class SymbolMatcherTest {
|
||||
|
||||
private SymbolMatcher matcher;
|
||||
|
||||
@Test
|
||||
public void testNoNamespaceQueryCaseSensitive() {
|
||||
matcher = new SymbolMatcher("bob", true);
|
||||
|
||||
assertMatches(matcher, "bob");
|
||||
assertMatches(matcher, "a::bob");
|
||||
assertMatches(matcher, "a::b::bob");
|
||||
assertMatches(matcher, "a::b::c::bob");
|
||||
|
||||
assertNotMatches(matcher, "Bob");
|
||||
assertNotMatches(matcher, "a::Bob");
|
||||
assertNotMatches(matcher, "a::b::Bob");
|
||||
assertNotMatches(matcher, "a::b::bob:joe");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNamespaceQueryCaseInsenstive() {
|
||||
matcher = new SymbolMatcher("bob", false);
|
||||
|
||||
assertMatches(matcher, "bob");
|
||||
assertMatches(matcher, "a::bob");
|
||||
assertMatches(matcher, "a::b::bob");
|
||||
assertMatches(matcher, "a::b::c::bob");
|
||||
assertMatches(matcher, "Bob");
|
||||
assertMatches(matcher, "a::Bob");
|
||||
assertMatches(matcher, "a::b::Bob");
|
||||
assertMatches(matcher, "a::b::c::Bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNamespaceQueryWildCardsCaseSensitive() {
|
||||
matcher = new SymbolMatcher("bo*", true);
|
||||
|
||||
assertMatches(matcher, "bob");
|
||||
assertMatches(matcher, "a::bob");
|
||||
assertMatches(matcher, "a::b::bob");
|
||||
assertMatches(matcher, "a::b::c::bob");
|
||||
|
||||
assertNotMatches(matcher, "Bob");
|
||||
assertNotMatches(matcher, "a::Bob");
|
||||
assertNotMatches(matcher, "a::b::Bob");
|
||||
assertNotMatches(matcher, "a::b::c::Bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNamespaceQueryWildCardsCaseInsensitive() {
|
||||
matcher = new SymbolMatcher("bo*", false);
|
||||
assertMatches(matcher, "bob");
|
||||
assertMatches(matcher, "a::bob");
|
||||
assertMatches(matcher, "a::b::bob");
|
||||
assertMatches(matcher, "a::b::c::bob");
|
||||
|
||||
assertMatches(matcher, "Bob");
|
||||
assertMatches(matcher, "a::Bob");
|
||||
assertMatches(matcher, "a::b::Bob");
|
||||
assertMatches(matcher, "a::b::c::Bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNamespaceQuerySingleCharWildCardsCaseSensitive() {
|
||||
matcher = new SymbolMatcher("b?b", true);
|
||||
|
||||
assertMatches(matcher, "bob");
|
||||
assertMatches(matcher, "a::bob");
|
||||
assertMatches(matcher, "a::b::bob");
|
||||
assertMatches(matcher, "a::b::c::bob");
|
||||
|
||||
assertNotMatches(matcher, "Bob");
|
||||
assertNotMatches(matcher, "a::Bob");
|
||||
assertNotMatches(matcher, "a::b::Bob");
|
||||
assertNotMatches(matcher, "a::b::c::Bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoNamespaceQuerySingleCharWildCardsCaseInsensitive() {
|
||||
matcher = new SymbolMatcher("b?b", false);
|
||||
|
||||
assertMatches(matcher, "bob");
|
||||
assertMatches(matcher, "a::bob");
|
||||
assertMatches(matcher, "a::b::bob");
|
||||
assertMatches(matcher, "a::b::c::bob");
|
||||
|
||||
assertMatches(matcher, "Bob");
|
||||
assertMatches(matcher, "a::Bob");
|
||||
assertMatches(matcher, "a::b::Bob");
|
||||
assertMatches(matcher, "a::b::c::Bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNamespace() {
|
||||
matcher = new SymbolMatcher("apple::bob", false);
|
||||
|
||||
assertMatches(matcher, "apple::bob");
|
||||
assertMatches(matcher, "x::apple::bob");
|
||||
assertMatches(matcher, "x::y::apple::bob");
|
||||
|
||||
assertNotMatches(matcher, "bob");
|
||||
assertNotMatches(matcher, "Bob");
|
||||
assertNotMatches(matcher, "dog::Bob");
|
||||
assertNotMatches(matcher, "apple::x::Bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithNamespaceTwoLevels() {
|
||||
matcher = new SymbolMatcher("apple::car::bob", false);
|
||||
|
||||
assertMatches(matcher, "apple::car::bob");
|
||||
assertMatches(matcher, "x::apple::car::bob");
|
||||
assertMatches(matcher, "x::y::apple::car::bob");
|
||||
|
||||
assertNotMatches(matcher, "bob");
|
||||
assertNotMatches(matcher, "apple::bob");
|
||||
assertNotMatches(matcher, "apple::x::bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithFullWildNamespace() {
|
||||
matcher = new SymbolMatcher("*::bob", false);
|
||||
|
||||
assertMatches(matcher, "apple::bob");
|
||||
assertMatches(matcher, "dog::bob");
|
||||
assertMatches(matcher, "x::y::bob");
|
||||
|
||||
assertNotMatches(matcher, "bob");
|
||||
assertNotMatches(matcher, "joe");
|
||||
assertNotMatches(matcher, "bo");
|
||||
assertNotMatches(matcher, "bobby");
|
||||
assertNotMatches(matcher, "x::boby");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithPartialWildNamespace() {
|
||||
matcher = new SymbolMatcher("*a*::bob", false);
|
||||
|
||||
assertMatches(matcher, "apple::bob");
|
||||
assertMatches(matcher, "banana::bob");
|
||||
assertMatches(matcher, "x::car::bob");
|
||||
|
||||
assertNotMatches(matcher, "bob");
|
||||
assertNotMatches(matcher, "apple::bo");
|
||||
assertNotMatches(matcher, "apple::x::bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithWildNamespaceAbsolutePath() {
|
||||
matcher = new SymbolMatcher("::*::bob", false);
|
||||
|
||||
assertMatches(matcher, "apple::bob");
|
||||
assertMatches(matcher, "x::bob");
|
||||
|
||||
assertNotMatches(matcher, "bob");
|
||||
assertNotMatches(matcher, "x::apple::bo");
|
||||
assertNotMatches(matcher, "apple::x::bob");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyPath() {
|
||||
matcher = new SymbolMatcher("", false);
|
||||
|
||||
assertNotMatches(matcher, "bob");
|
||||
assertNotMatches(matcher, "a:b");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathGlobbing() {
|
||||
matcher = new SymbolMatcher("Apple::**::dog", false);
|
||||
|
||||
assertMatches(matcher, "Apple::dog");
|
||||
assertMatches(matcher, "Apple::x::dog");
|
||||
assertMatches(matcher, "Apple::x::y::dog");
|
||||
assertMatches(matcher, "Apple::x::Apple::dog");
|
||||
assertMatches(matcher, "a::b::Apple::x::y::dog");
|
||||
|
||||
assertNotMatches(matcher, "dog");
|
||||
assertNotMatches(matcher, "x::dog");
|
||||
assertNotMatches(matcher, "Apple::x::doggy");
|
||||
assertNotMatches(matcher, "Applebob::x::dog");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMulitiplePathGlobbing() {
|
||||
matcher = new SymbolMatcher("Apple::**::cat::**::dog", false);
|
||||
|
||||
assertMatches(matcher, "Apple::cat::dog");
|
||||
assertMatches(matcher, "Apple::x::cat::dog");
|
||||
assertMatches(matcher, "Apple::x::cat::y::dog");
|
||||
assertMatches(matcher, "Apple::cat::x::Apple::dog");
|
||||
assertMatches(matcher, "Apple::x::Apple::cat::dog");
|
||||
assertMatches(matcher, "a::b::Apple::x::cat::dog");
|
||||
|
||||
assertNotMatches(matcher, "dog");
|
||||
assertNotMatches(matcher, "Apple::dog");
|
||||
assertNotMatches(matcher, "cat::dog");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathGlobbingAtEnd() {
|
||||
matcher = new SymbolMatcher("Apple::**", false);
|
||||
|
||||
assertMatches(matcher, "Apple::dog");
|
||||
assertMatches(matcher, "Apple::cat::dog");
|
||||
assertMatches(matcher, "Apple::x::cat::dog");
|
||||
assertMatches(matcher, "Apple::x::cat::y::dog");
|
||||
assertMatches(matcher, "Apple::cat::x::Apple::dog");
|
||||
assertMatches(matcher, "Apple::x::Apple::cat::dog");
|
||||
assertMatches(matcher, "a::b::Apple::x::cat::dog");
|
||||
|
||||
assertNotMatches(matcher, "dog");
|
||||
assertNotMatches(matcher, "Apple");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathGlobbingAtStart() {
|
||||
// note that this really should be no different than the query "dog", but it should still
|
||||
// work if you put the "**::" at the beginning
|
||||
matcher = new SymbolMatcher("**::dog", false);
|
||||
|
||||
assertMatches(matcher, "dog");
|
||||
assertMatches(matcher, "Apple::dog");
|
||||
assertMatches(matcher, "Apple::cat::dog");
|
||||
assertMatches(matcher, "Apple::x::cat::dog");
|
||||
assertMatches(matcher, "Apple::x::cat::y::dog");
|
||||
assertMatches(matcher, "Apple::cat::x::Apple::dog");
|
||||
assertMatches(matcher, "Apple::x::Apple::cat::dog");
|
||||
assertMatches(matcher, "a::b::Apple::x::cat::dog");
|
||||
|
||||
assertNotMatches(matcher, "Apple");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadDoubleStartActsLikeSingleStar() {
|
||||
// Should behave as if the query was "Apple*::dog because we only support "**" when
|
||||
// it is completely surrounded by delimiters. In this case the ** is treated as if
|
||||
// the user made a mistake and meant to input just a single *.
|
||||
matcher = new SymbolMatcher("Apple**::dog", false);
|
||||
|
||||
assertMatches(matcher, "Apple::x::Apple::dog");
|
||||
assertMatches(matcher, "Apple::dog");
|
||||
assertMatches(matcher, "Applebob::dog");
|
||||
|
||||
assertNotMatches(matcher, "Apple::x::dog");
|
||||
assertNotMatches(matcher, "Apple::x::y::dog");
|
||||
assertNotMatches(matcher, "a::b::Apple::x::y::dog");
|
||||
|
||||
assertNotMatches(matcher, "dog");
|
||||
assertNotMatches(matcher, "x::dog");
|
||||
assertNotMatches(matcher, "Apple::x::doggy");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPathGlobbingWithNameGlobbing() {
|
||||
matcher = new SymbolMatcher("Ap*le::**::do*", false);
|
||||
|
||||
assertMatches(matcher, "Apple::dog");
|
||||
assertMatches(matcher, "Apple::x::dog");
|
||||
assertMatches(matcher, "Apple::x::y::dog");
|
||||
assertMatches(matcher, "Apple::x::Apple::dog");
|
||||
assertMatches(matcher, "a::b::Apple::x::y::dog");
|
||||
assertMatches(matcher, "Apple::x::doggy");
|
||||
|
||||
assertNotMatches(matcher, "dog");
|
||||
assertNotMatches(matcher, "x::dog");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameGlobingAfterDotInName() {
|
||||
// We don't support the regex ".*" directly from user input. If the user
|
||||
// enters "*.*::bob", the "." should only match the literal '.' character.
|
||||
matcher = new SymbolMatcher("*.*::bob", false);
|
||||
|
||||
assertMatches(matcher, "a.a::bob");
|
||||
assertNotMatches(matcher, "a.a::c::bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNameGlobingExcessStars() {
|
||||
// 3 stars is assumed to be a mistake and will be treated as though it were a single *
|
||||
matcher = new SymbolMatcher("a***b::bob", false);
|
||||
|
||||
assertMatches(matcher, "axxxb::bob");
|
||||
assertNotMatches(matcher, "a::b::bob");
|
||||
|
||||
// In this context, where the extended *s are enclosed in delimiters, we assume the user
|
||||
// meant **
|
||||
matcher = new SymbolMatcher("a::***::bob", false);
|
||||
|
||||
assertMatches(matcher, "a::b::bob");
|
||||
assertNotMatches(matcher, "axxxb::bob");
|
||||
|
||||
matcher = new SymbolMatcher("a****b::bob", false);
|
||||
|
||||
assertMatches(matcher, "axxxb::bob");
|
||||
assertNotMatches(matcher, "a::b::bob");
|
||||
|
||||
matcher = new SymbolMatcher("a::****::bob", false);
|
||||
|
||||
assertMatches(matcher, "a::b::bob");
|
||||
assertNotMatches(matcher, "axxxb::bob");
|
||||
|
||||
matcher = new SymbolMatcher("bob*****", false);
|
||||
assertMatches(matcher, "bobby");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockNameMatches() {
|
||||
// all of our symbols are stubbed to be in the ".text" block
|
||||
matcher = new SymbolMatcher(".text::bob", false);
|
||||
|
||||
// since ".text is the block name for all of our symbols in this test, any
|
||||
// "bob" symbol, regardless of its namespace should match the query ".text::bob"
|
||||
assertMatches(matcher, "bob");
|
||||
assertMatches(matcher, "aaa::bob");
|
||||
assertMatches(matcher, "x::y::z::bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockNameMatchesDontSupportWildsInBlockName() {
|
||||
// All of our symbols are stubbed to be in the ".text" block. Legacy code allowed
|
||||
// users to search for symbols in memory blocks int the form <block name>::<symbol name>.
|
||||
// We still support that, but didn't add wildcard support as that might be even more
|
||||
// confusing that it already is.
|
||||
|
||||
matcher = new SymbolMatcher(".t*xt::bob", false);
|
||||
assertNotMatches(matcher, "bob");
|
||||
assertNotMatches(matcher, "aaa::bob");
|
||||
assertNotMatches(matcher, "x::y::z::bob");
|
||||
|
||||
matcher = new SymbolMatcher(".t?xt::bob", false);
|
||||
assertNotMatches(matcher, "bob");
|
||||
assertNotMatches(matcher, "aaa::bob");
|
||||
assertNotMatches(matcher, "x::y::z::bob");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockNameMatchesSupportWildsInSymbolName() {
|
||||
// all of our symbols are stubbed to be in the ".text" block
|
||||
matcher = new SymbolMatcher(".text::bob*", false);
|
||||
|
||||
assertMatches(matcher, "bob");
|
||||
assertMatches(matcher, "bobx");
|
||||
assertMatches(matcher, "bobyy");
|
||||
assertMatches(matcher, "aaa::bobz");
|
||||
assertMatches(matcher, "x::y::z::bobby");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetSymbolName() {
|
||||
matcher = new SymbolMatcher("a::b::c", false);
|
||||
assertEquals("c", matcher.getSymbolName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasFullySpecifiedName() {
|
||||
matcher = new SymbolMatcher("a::b::c", false);
|
||||
assertFalse(matcher.hasFullySpecifiedName());
|
||||
|
||||
matcher = new SymbolMatcher("a::b::c", true);
|
||||
assertTrue(matcher.hasFullySpecifiedName());
|
||||
|
||||
matcher = new SymbolMatcher("a::b::c*", true);
|
||||
assertFalse(matcher.hasFullySpecifiedName());
|
||||
|
||||
matcher = new SymbolMatcher("a::b*::c", true);
|
||||
assertTrue(matcher.hasFullySpecifiedName());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasWildcardsInSymbolName() {
|
||||
// This is testing the SymbolMatcher.hasWidCardsInSymbolName() method, which is used
|
||||
// to optimize symbol searching when symbol names don't have wildcard characters.
|
||||
|
||||
matcher = new SymbolMatcher("a::b::c", false);
|
||||
assertFalse(matcher.hasWildCardsInSymbolName());
|
||||
|
||||
matcher = new SymbolMatcher("a::b::c", true);
|
||||
assertFalse(matcher.hasWildCardsInSymbolName());
|
||||
|
||||
matcher = new SymbolMatcher("a::b::c*", true);
|
||||
assertTrue(matcher.hasWildCardsInSymbolName());
|
||||
|
||||
matcher = new SymbolMatcher("a::b*::c", true);
|
||||
assertFalse(matcher.hasWildCardsInSymbolName());
|
||||
|
||||
}
|
||||
|
||||
private Symbol symbol(String path) {
|
||||
String[] split = path.split(Namespace.DELIMITER);
|
||||
String name = split[split.length - 1];
|
||||
Namespace namespace = getNamespace(split);
|
||||
return new TestSymbol(name, namespace);
|
||||
|
||||
}
|
||||
|
||||
private Namespace getNamespace(String[] split) {
|
||||
Namespace namespace = null;
|
||||
for (int i = 0; i < split.length - 1; i++) {
|
||||
namespace = new StubNamespace(split[i], namespace);
|
||||
}
|
||||
return namespace;
|
||||
}
|
||||
|
||||
private void assertMatches(SymbolMatcher symbolMatcher, String path) {
|
||||
Symbol s = symbol(path);
|
||||
assertTrue(symbolMatcher.matches(s));
|
||||
}
|
||||
|
||||
private void assertNotMatches(SymbolMatcher symbolMatcher, String path) {
|
||||
Symbol s = symbol(path);
|
||||
assertFalse(symbolMatcher.matches(s));
|
||||
}
|
||||
|
||||
private class TestMemoryBlock extends MemoryBlockStub {
|
||||
@Override
|
||||
public String getName() {
|
||||
return ".text";
|
||||
}
|
||||
}
|
||||
|
||||
private class TestMemory extends StubMemory {
|
||||
@Override
|
||||
public MemoryBlock getBlock(Address addr) {
|
||||
return new TestMemoryBlock();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestProgram extends StubProgram {
|
||||
@Override
|
||||
public Memory getMemory() {
|
||||
return new TestMemory();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestSymbol extends StubSymbol {
|
||||
|
||||
public TestSymbol(String name, Namespace parent) {
|
||||
super(name, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Program getProgram() {
|
||||
return new TestProgram();
|
||||
}
|
||||
}
|
||||
}
|
@ -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.
|
||||
@ -238,9 +238,13 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol {
|
||||
}
|
||||
}
|
||||
|
||||
private void fillListWithNamespacePath(Namespace namespace, ArrayList<String> list) {
|
||||
private void fillListWithNamespacePath(Namespace namespace, List<String> list) {
|
||||
if (namespace == null || namespace.getID() == Namespace.GLOBAL_NAMESPACE_ID) {
|
||||
// we don't include the global namespace name in the path
|
||||
return;
|
||||
}
|
||||
Namespace parentNamespace = namespace.getParentNamespace();
|
||||
if (parentNamespace != null && parentNamespace.getID() != Namespace.GLOBAL_NAMESPACE_ID) {
|
||||
if (parentNamespace != null) {
|
||||
fillListWithNamespacePath(parentNamespace, list);
|
||||
}
|
||||
list.add(namespace.getName());
|
||||
|
@ -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.
|
||||
@ -41,7 +41,7 @@ public interface Symbol {
|
||||
|
||||
/**
|
||||
* Gets the full path name for this symbol as an ordered array of strings ending
|
||||
* with the symbol name. The global symbol will return an empty array.
|
||||
* with the symbol name.
|
||||
* @return the array indicating the full path name for this symbol.
|
||||
*/
|
||||
public String[] getPath();
|
||||
|
@ -0,0 +1,77 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.program.model.symbol;
|
||||
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.listing.CircularDependencyException;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
import ghidra.util.exception.InvalidInputException;
|
||||
|
||||
public class StubNamespace implements Namespace {
|
||||
|
||||
private Namespace parent;
|
||||
private String name;
|
||||
|
||||
public StubNamespace(String name, Namespace parent) {
|
||||
this.name = name;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Symbol getSymbol() {
|
||||
return new StubSymbol(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isExternal() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(boolean includeNamespacePath) {
|
||||
if (!includeNamespacePath || parent == null) {
|
||||
return name;
|
||||
}
|
||||
return parent.getName(true) + Namespace.DELIMITER + name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getID() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Namespace getParentNamespace() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AddressSetView getBody() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setParentNamespace(Namespace parentNamespace)
|
||||
throws DuplicateNameException, InvalidInputException, CircularDependencyException {
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
@ -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,6 +15,9 @@
|
||||
*/
|
||||
package ghidra.program.model.symbol;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.CircularDependencyException;
|
||||
import ghidra.program.model.listing.Program;
|
||||
@ -30,6 +33,11 @@ public class StubSymbol implements Symbol {
|
||||
private long id;
|
||||
private String name;
|
||||
private Address address;
|
||||
private Namespace parent;
|
||||
|
||||
public StubSymbol(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public StubSymbol(String name, Address address) {
|
||||
this.name = name;
|
||||
@ -37,6 +45,12 @@ public class StubSymbol implements Symbol {
|
||||
id = nextId++;
|
||||
}
|
||||
|
||||
public StubSymbol(String name, Namespace parent) {
|
||||
this.name = name;
|
||||
this.parent = parent;
|
||||
id = nextId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return address;
|
||||
@ -49,7 +63,12 @@ public class StubSymbol implements Symbol {
|
||||
|
||||
@Override
|
||||
public String[] getPath() {
|
||||
return new String[] { name };
|
||||
|
||||
ArrayList<String> list = new ArrayList<>();
|
||||
fillListWithNamespacePath(getParentNamespace(), list);
|
||||
list.add(getName());
|
||||
String[] path = list.toArray(new String[list.size()]);
|
||||
return path;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -64,7 +83,7 @@ public class StubSymbol implements Symbol {
|
||||
|
||||
@Override
|
||||
public Namespace getParentNamespace() {
|
||||
return null;
|
||||
return parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -225,4 +244,15 @@ public class StubSymbol implements Symbol {
|
||||
return id == other.id;
|
||||
}
|
||||
|
||||
private void fillListWithNamespacePath(Namespace namespace, List<String> list) {
|
||||
if (namespace == null || namespace.getID() == Namespace.GLOBAL_NAMESPACE_ID) {
|
||||
// we don't include the global namespace name in the path
|
||||
return;
|
||||
}
|
||||
Namespace parentNamespace = namespace.getParentNamespace();
|
||||
if (parentNamespace != null) {
|
||||
fillListWithNamespacePath(parentNamespace, list);
|
||||
}
|
||||
list.add(namespace.getName());
|
||||
}
|
||||
}
|
||||
|
@ -309,7 +309,7 @@ public class UserSearchUtils {
|
||||
String escaped = escapeEscapeCharacters(input);
|
||||
|
||||
if (allowGlobbing) {
|
||||
escaped = escapeSomeRegexCharacters(escaped, GLOB_CHARACTERS);
|
||||
escaped = escapeNonGlobbingRegexCharacters(input);
|
||||
escaped = convertGlobbingCharactersToRegex(escaped);
|
||||
}
|
||||
else {
|
||||
@ -376,6 +376,15 @@ public class UserSearchUtils {
|
||||
return Pattern.quote(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes all special regex characters except globbing chars (*?)
|
||||
* @param input the string to sanitize
|
||||
* @return a new string with all non-globing regex characters escaped.
|
||||
*/
|
||||
public static String escapeNonGlobbingRegexCharacters(String input) {
|
||||
return escapeSomeRegexCharacters(input, GLOB_CHARACTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Escapes all regex characters with the '\' character, except for those in the given exclusion
|
||||
* array.
|
||||
@ -384,7 +393,7 @@ public class UserSearchUtils {
|
||||
* @param doNotEscape an array of characters that should not be escaped
|
||||
* @return A new regex string with special characters escaped.
|
||||
*/
|
||||
// note: 'package' for testing
|
||||
// package for testing
|
||||
static String escapeSomeRegexCharacters(String input, char[] doNotEscape) {
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
|
Loading…
Reference in New Issue
Block a user