- |
+ |

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*xyz |
+
+ Matches any symbol whose name starts with "abc" and ends with "xyz" |
+
+
+
+ | abc?xyz |
+
+ Matches any symbol whose name starts with "abc" and ends with "xyz" and has
+ exactly 1 char between them |
+
+
+
+ | a::b |
+
+ Matches any symbol named "b" whose immediate parent namespace is named "a" |
+
+
+
+ | ::a |
+
+ Matches any global symbol named "a" |
+
+
+
+ | ::a::b |
+
+ Matches 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::*::z |
+
+ Matches any symbol named "z" whose namespace two levels up is named "a" |
+
+
+
+ | *::*::z |
+
+ Matches any symbol named "z" that is at least two levels below the global
+ namespace |
+
+
+
+ | a::**::z |
+
+ Matches 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, abcdef |
+
+ bcd, bc |
+
+
+
+ | abc*xyz |
+
+ abcxyz, abc12xyz |
+
+ abcz |
+
+
+
+ | abc?xyz |
+
+ abcdxyz, abcexyz |
+
+ abcxyz, abc12xyz |
+
+
+
+ | a::b |
+
+ a::b, x::a::b, x::y::a::b |
+
+ a::x::b |
+
+
+
+ | ::a |
+
+ a |
+
+ x::a, a::b |
+
+
+
+ | ::a::b |
+
+ a::b |
+
+ x::a::b |
+
+
+
+ | a::* |
+
+ a::b, a::c, x::a::d |
+
+ x::b, x::a |
+
+
+
+ | a::*::z |
+
+ a::b::z, a::c::z, x::a::b::z |
+
+ a::b::c::z |
+
+
+
+ | *::*::z |
+
+ a::b::z, x::a::b::z |
+
+ a::z |
+
+
+
+ | **::*::z |
+
+ a::b::z, x::a::b::z, a::z |
+
+ z |
+
+
+
+ | ::**::z |
+
+ a::b::z, x::a::b::z, a::z |
+
+ z |
+
+
+
+ | a::**::z |
+
+ a::z, a::b::z, a::b::c::z |
+
+ b::z, a::z::x |
+
+
+
+ | a::** |
+
+ a::z, a::b::z, a::b::c::z, a::z::x |
+
+ b::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
+
+
+ | ENTRY+10 |
-
- 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.
-
+ Positions the cursor at the address 0x10 addresses past the symbol ENTRY |
+
- Asterisk (*)
+
+ | 0x100000+30 |
-
- You can use the asterisk as a substitute for zero or more characters.
+ Positions the cursor at address 0x100030 |
+
- Example
+
+ | 0x100000+(2*10) |
- If you're looking for a label that you know starts with "gloss", type the
- following:
+ Positions the cursor at address 0x100020 |
+
-
- gloss*
-
+
+ | +20 |
- 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.
-
+ Positions 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
|
@@ -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 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 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,
. 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, . 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,
. 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,
- .
-
- 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, .
+
+ 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 ( ) action will move the cursor to the
- next function in the current direction. If inside a function and the direction is towards
+ This ( ) 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 ( ) 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 ( ) 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 ( ) will attempt to navigate to the next
+ matching byte pattern of the code unit under the cursor.
-
-
- This task ( ) 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,
- . You may use the pull-down menu to choose a
- specific type of bookmark ( ,
- ,
- ,
- ,
- ,
- )
- to navigate to as opposed to all types.
-
+ To move the cursor to the next bookmark, click on the Navigate by Bookmark icon, . You may use the pull-down menu to choose a specific
+ type of bookmark ( , , , , , ) 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:
- - In the tool-bar, click either the Go to previous location (
) button or the Go to next location ( ) button
+ - In the tool-bar, click either the Go to previous location (
) button or the Go to next location ( ) button
- 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++) {
|