diff --git a/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm b/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm index bedf7dd0e7..90e606a7a0 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/Navigation/Navigation.htm @@ -2,6 +2,9 @@ + + Navigation @@ -12,23 +15,23 @@

Often, users need to navigate to specific locations in a program.  Ghidra provides several different ways to do this: 

-
-

Go To Address, Label, or Expression

@@ -37,19 +40,19 @@

To Perform a Go To: 

    -
  1. In the menu-bar of a tool, select Navigation In the menu-bar of a tool, select Navigation Go To...
  2. The Go To dialog will be displayed, as shown below:
  3. Enter either an address, label, - expression, or file offset + expression, or file offset as specified below and press "OK"
  4. -
  5. If the address, label, expression, or file offset is valid, the Code Browser will be +
  6. If the address, label, expression, or file offset is valid, the Code Browser will be repositioned to that location and the dialog will be dismissed
  7. -
  8. If the address, label, expression, or file offset is not valid, the dialog will display +
  9. If the address, label, expression, or file offset is not valid, the dialog will display an error message
@@ -57,7 +60,7 @@ - @@ -417,13 +613,13 @@ previous Instruction, Data, Undefined, Function or Non Function. The search starts at the current cursor location and proceeds either forward (next) or backwards (previous).

-
-

When searching for Instructions, Data or - Undefined items, Ghidra will skip all contiguous items of the same type. For example, if the - cursor is on an address with an Instruction, and you go to the next Instruction, then all - Instructions immediately following the current one will be skipped until a non-Instruction is - found. Once that non-instruction is found, then Ghidra will take you to the next Instruction - after the address of that non-Instruction.

+
+

When searching for Instructions, Data + or Undefined items, Ghidra will skip all contiguous items of the same type. For example, if + the cursor is on an address with an Instruction, and you go to the next Instruction, then + all Instructions immediately following the current one will be skipped until a + non-Instruction is found. Once that non-instruction is found, then Ghidra will take you to + the next Instruction after the address of that non-Instruction.

Search Direction

@@ -434,18 +630,18 @@ "up_arrow" border="0"> icon indicates the search will be performed in the backward (previous) direction. To change the direction of the code unit search, toggle the arrow icon on the toolbar.

-

Alternatively, holding the SHIFT key when clicking a navigation button will + +

Alternatively, holding the SHIFT key when clicking a navigation button will temporarily invert the direction of the search.

- +

Invert Search Logic

-

The down_arrow icon indicates the - search logic will be inverted / negated. The exact meaning of this depends upon the type - of search performed, as described below. +

The down_arrow icon indicates the + search logic will be inverted / negated. The exact meaning of this depends upon the type of + search performed, as described below.

-

Navigate to Instruction

@@ -453,12 +649,10 @@

To move the cursor to the next instruction click on the Navigate by Instruction icon, I. This icon is disabled when no more instructions exist in the current search direction.

- -

When inverted, this task, if on an instruction, will attempt to navigate to - the next data or undefined data. If not on an instruction, then this task - will find the next instruction and then find the data or undefined data after that. -

- + +

When inverted, this task, if on an instruction, will attempt to navigate to the next + data or undefined data. If not on an instruction, then this task will find the next + instruction and then find the data or undefined data after that.

Navigate to Data

@@ -467,12 +661,10 @@

To move the cursor to the next data code unit, click on the Navigate by Data icon, D. This icon is disabled when no more data code units exist in the current search direction.

- -

When inverted, this task, if on a data code unit, will attempt to navigate to - the next instruction or undefined data. If not on a data, then this task - will find the next defined data and then find the instruction or undefined data after that. -

- + +

When inverted, this task, if on a data code unit, will attempt to navigate to the next + instruction or undefined data. If not on a data, then this task will find the next defined + data and then find the instruction or undefined data after that.

Navigate to Undefined

@@ -481,131 +673,113 @@

To move the cursor to the next undefined code unit, click on the Navigate by Data icon, U. This icon is disabled when no more undefined code units exist in the current search direction.

- -

When inverted, this task, if on an undefined code unit, will attempt to navigate to - the next instruction or data. If not on an undefined, then this task - will find the next undefined and then find the instruction or data after that. -

- + +

When inverted, this task, if on an undefined code unit, will attempt to navigate to the + next instruction or data. If not on an undefined, then this task will find the next + undefined and then find the instruction or data after that.

-

Navigate to Label

+

Navigate to Label

-

To move the cursor to the next Label, click on the Navigate by Label icon, - L.

- -

When inverted, this task, if on an address with a label, will attempt to navigate to - the next code unit without a label. If not on an address with a label, then this task - will find the next label and then find the next code unit after that without a label. -

- +

To move the cursor to the next Label, click on the Navigate by Label icon, L.

+ +

When inverted, this task, if on an address with a label, will attempt to navigate to the + next code unit without a label. If not on an address with a label, then this task will find + the next label and then find the next code unit after that without a label.

Navigate to Function

-

This (F) action will move the cursor to the - next function in the current direction. If inside a function and the direction is towards +

This (F) action will move the cursor to the + next function in the current direction. If inside a function and the direction is towards lower addresses, then this action will go to the current function's entry point.

-

When inverted, this task (F) will attempt to - the navigate to the next instruction block not contained in a function. - This can be useful when manually creating functions and - stepping over them to identify potential function candidates.

-

- +

When inverted, this task (F) will attempt to the + navigate to the next instruction block not contained in a function. This can be useful when + manually creating functions and stepping over them to identify potential function + candidates.

+

Navigate to Matching Byte Values

-

Navigate to Matching Byte Values

+
+

This task (V) will attempt to navigate to the next + matching byte pattern of the code unit under the cursor.

-
- -

This task (V) will attempt to navigate to the next matching - byte pattern of the code unit under the cursor. -

- -

When inverted, this task will attempt to - the navigate to the first code-unit where the byte value is different from the byte value - of the first byte of the current code unit. This can be useful when trying to navigate - past a series of 0s or FFs

- +

When inverted, this task will attempt to the navigate to the first code-unit where the + byte value is different from the byte value of the first byte of the current code unit. + This can be useful when trying to navigate past a series of 0s or FFs

- +

Navigate to Bookmark

-

To move the cursor to the next bookmark, click on the Navigate by Bookmark icon, - B. You may use the pull-down menu to choose a - specific type of bookmark (B, - B, - B, - B, - B, - B) - to navigate to as opposed to all types.

- +

To move the cursor to the next bookmark, click on the Navigate by Bookmark icon, B. You may use the pull-down menu to choose a specific + type of bookmark (B, B, B, B, B, B) to navigate to as + opposed to all types.

+

When inverted, this task will attempt to navigate to then next address with a bookmark - different than what is selected in the pull-down menu of the icon. If the 'all bookmarks' - state is selected, then this task will simply navigate to the next address that has no - bookmark. -

- + different than what is selected in the pull-down menu of the icon. If the 'all bookmarks' + state is selected, then this task will simply navigate to the next address that has no + bookmark.

-

 

Provided by: Go To Next-Previous Code Unit plugin

-
- - -
-
-
-
-
+
+
+
+
+
+
+

Next/Previous Function

-

Navigating to the next or previous function is a commonly used feature. As such, separate - actions have been created so that keybindings can be assigned for each direction. -

+

Navigating to the next or previous function is a commonly used feature. As such, separate + actions have been created so that keybindings can be assigned for each direction.

Next Function

-

This action navigates the cursor to the closest function entry point that is at an - address greater than the current address. The default keybinding is Control-Down Arrow.

+

This action navigates the cursor to the closest function entry point that is at an + address greater than the current address. The default keybinding is + Control-Down Arrow.

Previous Function

-

This action navigates the cursor to the closest function entry point that is at an - address less than the current address. The default keybinding is Control-Up Arrow.

+

This action navigates the cursor to the closest function entry point that is at an + address less than the current address. The default keybinding is + Control-Up Arrow.

- -

Provided by: CodeBrowser plugin

-
- - -
-
-
-
-
+

Provided by: CodeBrowser plugin

+
+
+
+
+
+
+

Navigation History

@@ -614,16 +788,16 @@ navigation history stack.  The navigation history feature allows the user to revisit previous locations.

-

Go To Next/Previous Location

+

Go To + Next/Previous Location

To traverse the history stack:

    -
  1. In the tool-bar, click either the Go to previous location ( ) button or the Go to next location () button
  2. +
  3. In the tool-bar, click either the Go to previous location ( ) button or the Go to next location () button
  4. The Code Browser will be repositioned to the saved location
@@ -648,207 +822,174 @@ href="help/topics/Search/Search_Program_Text.htm">Program Text, etc) -

The - button is only enabled after performing a

+

The button is only enabled after performing a

-

Go To Next/Previous Function in History

+

Go To + Next/Previous Function in History

-

- These actions allow you to navigate to the next/previous functions in the history - list, skipping over any locations that are not in functions or are in the current - function. -

- -
-

- The behavior of the previous action will - vary slightly depending upon what component is focused. It is possible for a - non-Listing view to be showing a function that is not the current function in the - Listing. In this case, if the Listing has focus, then the previously visited function - will be the navigation destination. Alternatively, if a non-Listing widget - (e.g., the Decompiler) has focus and is showing a function, then that function - being displayed will be ignored when navigating to the previous function. -

-
+

These actions allow you to navigate to the next/previous functions in the history list, + skipping over any locations that are not in functions or are in the current function.

+ +
+

The behavior of the previous action + will vary slightly depending upon what component is focused. It is possible for a + non-Listing view to be showing a function that is not the current function in the + Listing. In this case, if the Listing has focus, then the previously visited function + will be the navigation destination. Alternatively, if a non-Listing widget (e.g., the + Decompiler) has focus and is showing a function, then that function being displayed will + be ignored when navigating to the previous function.

+

Clear History

-

To clear the navigation history stack, select Navigation To clear the navigation history stack, select Navigation Clear History

-

After clearing the history, the  and buttons are disabled

-
- -

Provided by: Next/Previous plugin

+

After clearing the history, the  and + buttons are disabled

- -
+

Provided by: Next/Previous plugin

+
-
-
-
-
- - - +
+
+
+
+
+

Component Provider Navigation

-

- This section lists actions that allow the user to navigate between component providers. -

+

This section lists actions that allow the user to navigate between component + providers.

Go To Last Active Component

-

- Allows the user to switch focus back to the previously focused component provider. -

+

Allows the user to switch focus back to the previously focused component provider.

- -

Provided by: ProviderNavigation plugin

-
- -
-
-
-
-
+

Provided by: ProviderNavigation plugin

+
+
+
+
+
+
+ +
- -
- -

Navigation Options

- -
+

Navigation Options

-

- 'Go To' in Current Program Only - - 'Go To' service will only search for and navigate to locations in the current program. - If this option is off and the search location is not found in the current program, the - 'Go To' action will search other open programs, - possibly resulting in the listing view switching to a different open program tab. - By default, this option is on, thereby guaranteeing that the listing view will not - change to a different program when performing a - 'Go To' action. -

- -

- External Navigation - - Determines the behavior for navigation to external symbols and references. - By default, navigating to an external will attempt to navigate within the - current program to the first linkage reference (pointer or thunk). - Alternatively, if an external program has been associated with an - import Library, then that program will be opened and positioned to the selected - external location if found. -

- -

- Follow Indirection - - Determines the behavior for navigation on indirect flow references. - By default, this option is disabled providing navigation to the - referenced pointer data. If enabled, the pointer will be followed - to its referenced destination if contained within the program's memory. -

- -

- Prefer Current Address Space - - Determines if the 'Go To' action prefers the current address space when entering address offsets. - For example, if your program has multiple address spaces such as 'RAM' or 'DATA' and you - enter 1000 into the 'Go To' field, you could mean RAM:1000 or DATA:1000. If this option - is on, then it will go to the address with the address space that matches the current - cursor location. Otherwise, it will show a list of possible addresses for the given offset. - The default is on for this option. -

- -

- Range Navigation - - Determines how - navigation of ranges - (i.e., selection ranges and highlight ranges) takes place. By default, navigating - to ranges will place the cursor at the top of the - next range. You may use this option to navigate to both the top and the bottom of each - range being navigated. -

- -

Starting Program Location Options

-

The starting location for newly opened programs can be configured using the following - options:

-
-

Start At - Choose a starting program location option: -

    -
  • Lowest Address - The program will open at the lowest address.
  • -
  • Lowest Code Block Address - The program will open at the first executable - memory block. If no executable block found, it will go to lowest address.
  • -
  • Preferred Symbol Name - The program will open at the first symbol name it - finds that matches a name in the "Start Symbols" option. If no symbol is found, - it will try the lowest code block, and finally the lowest address.
  • -
  • Location When Last Closed - The program will open to the location it was at the - last time the program was closed. If this is the first time the program - has been opened, it will try to find a preferred symbol, then it will look for the - lowest code block, and finally lowest overall address
  • -
+
+

'Go To' in Current Program Only - 'Go To' service will only search for and navigate + to locations in the current program. If this option is off and the search location is not + found in the current program, the 'Go To' action will search other open programs, possibly + resulting in the listing view switching to a different open program tab. By default, this + option is on, thereby guaranteeing that the listing view will not change to a different + program when performing a 'Go To' action.

-

Start Symbols - A comma separated list of symbol names to be be used as the - starting location for the program if the "Preferred Symbol Name" option is selected - above. The first matching symbol found will be used as the starting location for - newly opened programs.

+

External Navigation - Determines the behavior for navigation to external symbols + and references. By default, navigating to an external will attempt to navigate within the + current program to the first linkage reference (pointer or thunk). Alternatively, if an + external program has been associated with an import Library, then that program will be opened + and positioned to the selected external location if found.

-

Use Underscores - If selected, each of the preferred symbols listed above - will also be searched for with either one or two underscores prepended to the name. For - example, if selected, it will search for "_main" and "__main" in addition to "main" - when trying to find a starting symbol.

- -
-

Initial Analysis Navigation Options

-

These options control the behavior of the tool after the initial analysis has - completed.

-
-

Ask To Reposition Program - If selected, the user will be prompted if they - would like the program to be positioned to any newly discovered starting symbols as - specified in the Start Symbols option.

+

Follow Indirection - Determines the behavior for navigation on indirect flow + references. By default, this option is disabled providing navigation to the referenced + pointer data. If enabled, the pointer will be followed to its referenced destination if + contained within the program's memory.

-

Auto Reposition If Not Moved - If selected, the program will automatically - be reposition to any newly discovered starting symbols as specified in the - Start Symbols option, provided the user has - not manually moved the cursor off the starting location address. If they have manually - moved the cursor, then the behavior will revert to the setting of the "Ask To - Reposition Program" option above.

- -
- - - -
- +

Prefer Current Address Space - Determines if the 'Go To' action prefers the current + address space when entering address offsets. For example, if your program has multiple + address spaces such as 'RAM' or 'DATA' and you enter 1000 into the 'Go To' field, you could + mean RAM:1000 or DATA:1000. If this option is on, then it will go to the address with the + address space that matches the current cursor location. Otherwise, it will show a list of + possible addresses for the given offset. The default is on for this option.

- -
+

Range Navigation - Determines how navigation of ranges (i.e., + selection ranges and highlight ranges) takes place. By default, navigating to ranges will + place the cursor at the top of the next range. You may use this option to navigate to both + the top and the bottom of each range being navigated.

+ +

Starting Program Location Options

+ +

The starting location for newly opened programs can be configured using the following + options:

+ +
+

Start At - Choose a starting program location option:

+ +
    +
  • Lowest Address - The program will open at the lowest address.
  • + +
  • Lowest Code Block Address - The program will open at the first executable memory + block. If no executable block found, it will go to lowest address.
  • + +
  • Preferred Symbol Name - The program will open at the first symbol name it finds that + matches a name in the "Start Symbols" option. If no symbol is found, it will try the + lowest code block, and finally the lowest address.
  • + +
  • Location When Last Closed - The program will open to the location it was at the last + time the program was closed. If this is the first time the program has been opened, it + will try to find a preferred symbol, then it will look for the lowest code block, and + finally lowest overall address
  • +
+ +

Start Symbols - A comma separated list of symbol + names to be be used as the starting location for the program if the "Preferred Symbol Name" + option is selected above. The first matching symbol found will be used as the starting + location for newly opened programs.

+ +

Use Underscores - If selected, each of the preferred symbols listed above will + also be searched for with either one or two underscores prepended to the name. For example, + if selected, it will search for "_main" and "__main" in addition to "main" when trying to + find a starting symbol.

+
+ +

Initial Analysis Navigation Options

+ +

These options control the behavior of the tool after the initial analysis has + completed.

+ +
+

Ask To Reposition Program - If selected, the user will be prompted if they would + like the program to be positioned to any newly discovered starting symbols as specified in + the Start Symbols option.

+ +

Auto Reposition If Not Moved - If selected, the program will automatically be + reposition to any newly discovered starting symbols as specified in the Start Symbols option, provided the user has not manually moved the + cursor off the starting location address. If they have manually moved the cursor, then the + behavior will revert to the setting of the "Ask To Reposition Program" option above.

+
+

-
-
-
-
- +
+
+
+
+
+

Related Topics:

diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToQueryResultsTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToQueryResultsTableModel.java index 99bc7d15d3..fca2dab001 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToQueryResultsTableModel.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/gotoquery/GoToQueryResultsTableModel.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,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 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 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 accumulator, - TaskMonitor monitor) throws CancelledException { - - searchDefinedSymbols(accumulator, monitor); - searchDynamicSymbols(accumulator, monitor); - } - - private void searchDynamicSymbols(Accumulator 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 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 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; } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/services/QueryData.java b/Ghidra/Features/Base/src/main/java/ghidra/app/services/QueryData.java index 7456c9f50a..1aff401a17 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/services/QueryData.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/services/QueryData.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -20,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); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToQuery.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToQuery.java index 2dcad4f502..dbbd62894f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToQuery.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToQuery.java @@ -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 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 searchPrograms) { - for (Program program : searchPrograms) { - List locations = getValidSymbolLocationsForProgram(program); - if (!locations.isEmpty()) { - goToProgramLocations(program, locations); - return true; - } - } - return false; - } - - private List getValidSymbolLocationsForProgram(Program program) { - - List 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 getExtenalLinkageLocations(Symbol symbol) { - - Collection 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 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
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 locations = task.getResults(); + if (locations.isEmpty()) { + return false; + } + + Program program = locations.get(0).getProgram(); + return goToProgramLocations(program, locations); + } + + private List 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 locations = toProgramLocations(validAddresses, program); + return goToProgramLocations(program, locations); + } + + private boolean goToProgramLocations(Program program, List locations) { + + if (locations.size() == 1) { + return goTo(program, locations.get(0)); + } + + Swing.runIfSwingOrRunLater(() -> showResultsInTable(locations)); + return true; + } + + private void showResultsInTable(List 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 getSearchPrograms() { + Program currentProgram = navigatable.getProgram(); + List 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
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 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 symbols) { - if (symbols.size() == 1) { - return gotoLabel(program, symbols.get(0)); - } - - List 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 globalSymbols = - program.getSymbolTable().getLabelOrFunctionSymbols(symbolStr, null); - if (globalSymbols.isEmpty()) { - return false; - } - - List 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 Iterator - * 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 Iterator so that the current - * program is always searched first when processing the collection of programs. - */ - protected class ProgramGroup implements Iterable { - - private List programList; - - public ProgramGroup(Program[] programs, Program navigatableProgram) { - programList = new ArrayList<>(Arrays.asList(programs)); - if (!programList.contains(navigatableProgram)) { - programList.add(navigatableProgram); - } - } - - @Override - public Iterator iterator() { - List 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."); } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToServiceImpl.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToServiceImpl.java index 3f56cc5e88..f2e0d8d486 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToServiceImpl.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToServiceImpl.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -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 diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToSymbolSearchTask.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToSymbolSearchTask.java new file mode 100644 index 0000000000..f4e069e78b --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/GoToSymbolSearchTask.java @@ -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 searchPrograms; + private List results; + private int limit; + + public GoToSymbolSearchTask(QueryData queryData, List 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 getResults() { + return results; + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/SymbolMatcher.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/SymbolMatcher.java new file mode 100644 index 0000000000..607a35016e --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/navigation/SymbolMatcher.java @@ -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("(? + * 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). + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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 findMatchingSymbolLocations(List searchPrograms) { + + List 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 locations) { + + if (!findSymbolsByDirectLookup(program, locations)) { + findSymbolsByBruteForce(program, locations); + } + + return !locations.isEmpty(); + } + + private boolean findSymbolsByDirectLookup(Program program, List 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 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 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 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 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 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 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 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(); + } + +} diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginTest.java index 87aeac4139..60315294a7 100644 --- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginTest.java +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -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 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 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 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 results = new ArrayList<>(); - List 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) { diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginWithNamespaceTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginWithNamespaceTest.java new file mode 100644 index 0000000000..3c7993cc6b --- /dev/null +++ b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/navigation/GoToAddressLabelPluginWithNamespaceTest.java @@ -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()); + } + +} diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/symboltree/nodes/OrganizationNodeTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/symboltree/nodes/OrganizationNodeTest.java index 50e7985e9b..be4066f335 100644 --- a/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/symboltree/nodes/OrganizationNodeTest.java +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/plugin/core/symboltree/nodes/OrganizationNodeTest.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -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)); } } diff --git a/Ghidra/Features/Base/src/test/java/ghidra/app/util/navigation/SymbolMatcherTest.java b/Ghidra/Features/Base/src/test/java/ghidra/app/util/navigation/SymbolMatcherTest.java new file mode 100644 index 0000000000..30fe219b0f --- /dev/null +++ b/Ghidra/Features/Base/src/test/java/ghidra/app/util/navigation/SymbolMatcherTest.java @@ -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 ::. + // 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(); + } + } +} diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDB.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDB.java index 20c3dcb8cc..1dcdbff45c 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDB.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/database/symbol/SymbolDB.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -238,9 +238,13 @@ public abstract class SymbolDB extends DatabaseObject implements Symbol { } } - private void fillListWithNamespacePath(Namespace namespace, ArrayList list) { + private void fillListWithNamespacePath(Namespace namespace, List 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()); diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Symbol.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Symbol.java index 8ff048cc84..f5bee575aa 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Symbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/symbol/Symbol.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -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(); diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/StubNamespace.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/StubNamespace.java new file mode 100644 index 0000000000..9c0836cfbe --- /dev/null +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/StubNamespace.java @@ -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 + } + +} diff --git a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/StubSymbol.java b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/StubSymbol.java index 0c8d0b253c..2d4cd2c866 100644 --- a/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/StubSymbol.java +++ b/Ghidra/Framework/SoftwareModeling/src/test/java/ghidra/program/model/symbol/StubSymbol.java @@ -4,9 +4,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -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 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 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()); + } } diff --git a/Ghidra/Framework/Utility/src/main/java/ghidra/util/UserSearchUtils.java b/Ghidra/Framework/Utility/src/main/java/ghidra/util/UserSearchUtils.java index 4bd372ea8c..1913274ddc 100644 --- a/Ghidra/Framework/Utility/src/main/java/ghidra/util/UserSearchUtils.java +++ b/Ghidra/Framework/Utility/src/main/java/ghidra/util/UserSearchUtils.java @@ -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++) {

+

Go To Address, Label, Expression, or File Offset @@ -77,9 +80,9 @@

Enter an address into the text area of the dialog. The value entered will be assumed to be in hexadecimal. That is, "0x1000" and "1000" are the same value.

-

When the program has multiple address - spaces and the destination address is ambiguous (based on the current location), a query - results dialog will be displayed.

+

When the program has multiple + address spaces and the destination address is ambiguous (based on the current location), a + query results dialog will be displayed.

Consider the following examples:

@@ -87,7 +90,8 @@

Given:

-

A program with the following memory blocks which reside in different address spaces:

+

A program with the following memory blocks which reside in different address + spaces:

@@ -145,8 +149,6 @@ - -
@@ -173,22 +175,295 @@
-
- - - + + +
-

+ + + + - - -
+

-

Ambiguous Destination Address

-
+

Ambiguous Destination Address

+

Go To Label

-

Enter the name of an existing label into the text area of the dialog. 

+

Enter the name or namespace path of an existing label into the text field of the dialog. + The name and/or path may contain wildcards (* ? **).

+ +

Namespace Paths

+ +
+

A label name can be specified with or without namespace paths. Use the "::" delimiter + to separate namespace elements and the symbol name, with the symbol name always being the + last element.

+ +

Namespace paths do not have to be fully specified from the global namespace, so for + example, "a::b" will find the symbol "b" in a namespace "a", regardless of whether or not + the namespace "a" is global or in some other namespace. In other words, the namespace + path is relative to any namespace.

+ +

To require that the specified path be absolute (fully specified from the global + namespace), start the path with the "::" delimiter. So, for example, "::a::b" will only + match the symbol "b" if it is in a namespace "a" which is itself in the global + namespace.

+
+ +

Wildcards

+ +
+

Wildcard characters(* ** ?) can be used to match namespace names and/or symbol names. + If more than one match is found, the results are displayed in a GoTo Query Results + table

+ +
Supported Wildcards
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Wildcard
+
Description
+
*Matches zero or more characters within a single namespace or + symbol name. Does not match across :: delimiters. Also, it must match something + at that level. So, "::*::*::a" only matches symbols that are exactly two + namespace levels deep.
?Matches exactly one characters within a single namespace or symbol name.
**Matches 0 or more path elements. The '**' must not be mixed with other + characters. The only valid forms are '::**::', '**::', or '::**'. See the examples + below for use of this syntax.
+
+ +
Wildcard Examples
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input
+
Descriptions
+
abc*Matches any symbol whose name starts with "abc"
abc*xyzMatches any symbol whose name starts with "abc" and ends with "xyz"
abc?xyzMatches any symbol whose name starts with "abc" and ends with "xyz" and has + exactly 1 char between them
a::bMatches any symbol named "b" whose immediate parent namespace is named "a"
::aMatches any global symbol named "a"
::a::bMatches any symbol named "b" whose immediate parent namespace is named "a" and + "a" is in the global namespace
a::*Matches any symbol whose immediate parent namespace is named "a"
a::*::zMatches any symbol named "z" whose namespace two levels up is named "a"
*::*::zMatches any symbol named "z" that is at least two levels below the global + namespace
a::**::zMatches any symbol named "z" that has a namespace named "a" anywhere in it's + namespace ancestry
a::**Matches any symbol that has a namespace named "a" anywhere in it's namespace + ancestry
+
+ +
Wildcard Examples (With sample full symbol paths of matching and not matching + symbols)
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Input
+
Matches
+
Does Not Match
+
abc*abc, abcdefbcd, bc
abc*xyzabcxyz, abc12xyzabcz
abc?xyzabcdxyz, abcexyzabcxyz, abc12xyz
a::ba::b, x::a::b, x::y::a::ba::x::b
::aax::a, a::b
::a::ba::bx::a::b
a::*a::b, a::c, x::a::dx::b, x::a
a::*::za::b::z, a::c::z, x::a::b::za::b::c::z
*::*::za::b::z, x::a::b::za::z
**::*::za::b::z, x::a::b::z, a::zz
::**::za::b::z, x::a::b::z, a::zz
a::**::za::z, a::b::z, a::b::c::zb::z, a::z::x
a::**a::z, a::b::z, a::b::c::z, a::z::xb::z, x::z
+
+

Case Sensitive

@@ -197,158 +472,79 @@ same as "lab1000."  If you want to find both of these labels, turn off the case sensitive option.  If more than one match is found, they are displayed in a Query Results dialog.

- -

Even if the case sensitive option is off, - if a label has an exact match, no other labels will be found.
-

- +

Dynamic Labels

-
-

This option only affects queries that could potentially result in multiple results, i.e - when a search must be performed versus a lookup. This occurs when either the case sensitive - is turned off or a wildcard is used. Specifically, this option tells Ghidra, when doing - a search versus a direct lookup, to consider all the Dynamic symbols (symbols that are not - stored, but are generated on the fly because of a reference to that location.) If - this option is off, only defined labels are searched. -

Turning off this option can result in - significantly faster results in larger programs.


-

- +
+

This option determines if the query includes generic dynamically generated labels + (LAB*, DAT*, FUN*, etc.). If this option is off, only defined labels are searched.

+ +

Turning off this option can result + in significantly faster results in larger programs.

+
+
+
-
- +

Go To File Offset

- +
-

Enter file(n), where n is a file offset of the program's source file bytes - (at time of import), into the text area of the dialog. The file offset entered will be - assumed to be in decimal unless it is preceeded by 0x. That is, "file(0x1000)" and +

Enter file(n), where n is a file offset of the program's source file bytes + (at time of import), into the text area of the dialog. The file offset entered will be + assumed to be in decimal unless it is preceeded by 0x. That is, "file(0x1000)" and "file(1000)" are different values.

-

Ghidra does not support storing source - file bytes for all file formats. Searching for a file offset in these programs will always - yield no results.

- -

When the program has multiple file byte - sources and the destination address is ambiguous, a query results dialog will be displayed. -

+

Ghidra does not support storing + source file bytes for all file formats. Searching for a file offset in these programs will + always yield no results.

+ +

When the program has multiple file + byte sources and the destination address is ambiguous, a query results dialog will be + displayed.

Go To Expression

-
-

Enter an arithmetic expression that can include addresses, symbols, or can be relative - to the current location. All numbers are assumed to be hexadecimal. Supported operator are - "+ - * / << >>".  Also, parentheses are supported to control order of - expression evaluation.
- For example:
-

- - - - - - - - - - - - - - - - - - - - -
- ENTRY+10 - Positions - the cursor at the address 0x10 addresses past the symbol ENTRY -
0x100000+30 - Positions the cursor at address 0x100030 -
0x100000+(2*10) - Positions the cursor at address 0x100020 -
+20 - Positions the cursor at an address that is 0x20 past the current location -
-
-
- - -
-

Executing a Query

-
-

A Query performs a case-insensitive search for all labels that match the criteria. A - Query is specified using wildcards.

+

Enter an arithmetic expression that can include addresses, symbols, or can be relative + to the current location. All numbers are assumed to be hexadecimal. Supported operator are + "+ - * / << >>".  Also, parentheses are supported to control order of + expression evaluation.
+ For example:
+

-

Using Wildcards

+ + + -
-

Wildcard characters ("?" or "*") can be used when searching for labels.  - Wildcards are useful if you don't know the full label name or don't want to type the - entire name.

-
+ + -

Asterisk (*)

+ + -
-

You can use the asterisk as a substitute for zero or more characters. 

+
+ -

Example  

+ + -

If you're looking for a label that you know starts with "gloss", type the - following:

+ + -
-

gloss*

-
+ + -

The Go To Address or Label dialog will locate all labels that begin with - "gloss" including Glossary.txt, Glossary.doc, and Glossy.doc. To - narrow the search to a specific extension, type:

- -
-

gloss*.doc

-
- -

In this case, the Go To Address or Label dialog will find all labels that begin - with gloss but have the extension .doc, such as Glossary.doc and - Glossy.doc.

- - -

Question Mark (?)

- -
-

Use the question mark as a substitute for a single character in a name. 

- -

Example 1

- -

If you typed gloss?.doc, the Go To Address or Label dialog would locate - the label Glossy.doc or Gloss1.doc, but not Glossary.doc.

- -

Example 2

- -

Suppose that two of the labels in a program were FUN_0040816d and - FUN_004081bd. A possible query string to match these two labels would be - FUN_004081?d. The results of the query are displayed in a Query Results - dialog, as shown below.

-
+ + +
ENTRY+10Positions the cursor at the address 0x10 addresses past the symbol ENTRY
0x100000+30Positions the cursor at address 0x100030
0x100000+(2*10)Positions the cursor at address 0x100020
+20Positions the cursor at an address that is 0x20 past the current location
-

- -

Query Results Dialog

-

Repeating a Previous Go To

@@ -362,7 +558,7 @@
-

+

Previous Go to List