mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-22 20:36:42 +08:00
Merge remote-tracking branch
'origin/GP-5310_ghidragon_search_and_replace--SQUASHED'
This commit is contained in:
+17
@@ -439,6 +439,13 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
||||
return getCommentAddresses(addrSet).getAddresses(forward);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCommentAddressCount() {
|
||||
return program.viewport.unionedAddresses(
|
||||
s -> program.trace.getCommentAdapter().getAddressSetView(Lifespan.at(s)))
|
||||
.getNumAddresses();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getComment(int commentType, Address address) {
|
||||
try (LockHold hold = program.trace.lockRead()) {
|
||||
@@ -447,6 +454,16 @@ public abstract class AbstractDBTraceProgramViewListing implements TraceProgramV
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodeUnitComments getAllComments(Address address) {
|
||||
CommentType[] types = CommentType.values();
|
||||
String[] comments = new String[types.length];
|
||||
for (CommentType type : types) {
|
||||
comments[type.ordinal()] = getComment(type, address);
|
||||
}
|
||||
return new CodeUnitComments(comments);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setComment(Address address, int commentType, String comment) {
|
||||
program.trace.getCommentAdapter()
|
||||
|
||||
+7
-2
@@ -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.
|
||||
@@ -249,4 +249,9 @@ public class DBTraceProgramViewFragment implements ProgramFragment {
|
||||
public void move(Address min, Address max) throws NotFoundException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeleted() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+7
-2
@@ -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.
|
||||
@@ -239,4 +239,9 @@ public class DBTraceProgramViewRootModule implements ProgramModule {
|
||||
public long getTreeID() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDeleted() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -516,6 +516,7 @@ src/main/help/help/topics/ScalarSearchPlugin/images/ScalarWindow.png||GHIDRA||||
|
||||
src/main/help/help/topics/ScalarSearchPlugin/images/SearchAllScalarsDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/Instruction_Mnemonic_Search.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/Query_Results_Dialog.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/SearchAndReplace.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/Search_Formats.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/Search_Instruction_Patterns.htm||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/Search_Memory.htm||GHIDRA||||END|
|
||||
@@ -532,6 +533,8 @@ src/main/help/help/topics/Search/images/MemorySearchProviderWithOptionsOn.png||G
|
||||
src/main/help/help/topics/Search/images/MemorySearchProviderWithScanPanelOn.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/images/MultipleSelectionError.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/images/QueryResultsSearch.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/images/SearchAndReplaceDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/images/SearchAndReplaceResults.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/images/SearchForAddressTables.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/images/SearchInstructionPatterns.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/Search/images/SearchInstructionPatternsControlPanel.png||GHIDRA||||END|
|
||||
|
||||
@@ -22,3 +22,4 @@ OverviewColorService
|
||||
DWARFFunctionFixup
|
||||
ElfInfoProducer
|
||||
FSBFileHandler
|
||||
SearchAndReplaceHandler
|
||||
|
||||
@@ -204,7 +204,7 @@ icon.plugin.merge.conflict.lock = lock.gif
|
||||
icon.plugin.merge.conflict.unlock = unlock.gif
|
||||
icon.plugin.merge.status.pending = bullet_green.png
|
||||
icon.plugin.merge.status.in.progress = right.png
|
||||
icon.plugin.merge.status.complete = checkmark_green.gif
|
||||
icon.plugin.merge.status.complete = icon.checkmark.green
|
||||
|
||||
icon.plugin.myprogramchanges.merge = vcMerge.png
|
||||
icon.plugin.myprogramchanges.checkin = vcCheckIn.png
|
||||
@@ -402,9 +402,7 @@ icon.base.mem.search.panel.options = view_left_right.png
|
||||
icon.base.mem.search.panel.scan = view_bottom.png
|
||||
icon.base.mem.search.panel.search = view_top_bottom.png
|
||||
|
||||
|
||||
|
||||
|
||||
icon.base.plugin.quickfix.done = icon.checkmark.green
|
||||
|
||||
|
||||
[Dark Defaults]
|
||||
|
||||
@@ -327,12 +327,13 @@
|
||||
<tocdef id="Program Search" sortgroup="p" text="Program Search" target="help/topics/Search/Searching.htm" >
|
||||
<tocdef id="Memory" sortgroup="a" text="Memory" target="help/topics/Search/Search_Memory.htm" />
|
||||
<tocdef id="Text" sortgroup="b" text="Text" target="help/topics/Search/Search_Program_Text.htm" />
|
||||
<tocdef id="Strings" sortgroup="c" text="Strings" target="help/topics/Search/Search_for_Strings.htm" />
|
||||
<tocdef id="Scalars" sortgroup="d" text="Scalars" target="help/topics/ScalarSearchPlugin/The_Scalar_Table.htm" />
|
||||
<tocdef id="Instruction Patterns" sortgroup="e" text="Instruction Patterns" target="help/topics/Search/Search_Instruction_Patterns.htm" />
|
||||
<tocdef id="Address Tables" sortgroup="f" text="Address Tables" target="help/topics/Search/Search_for_AddressTables.htm" />
|
||||
<tocdef id="Direct References" sortgroup="g" text="Direct References" target="help/topics/Search/Search_for_DirectReferences.htm" />
|
||||
<tocdef id="Search Results Window" sortgroup="h" text="Query Results Window" target="help/topics/Search/Query_Results_Dialog.htm" />
|
||||
<tocdef id="Search And Replace" sortgroup="c" text="Search and Replace" target="help/topics/Search/SearchAndReplace.htm" />
|
||||
<tocdef id="Strings" sortgroup="d" text="Strings" target="help/topics/Search/Search_for_Strings.htm" />
|
||||
<tocdef id="Scalars" sortgroup="e" text="Scalars" target="help/topics/ScalarSearchPlugin/The_Scalar_Table.htm" />
|
||||
<tocdef id="Instruction Patterns" sortgroup="f" text="Instruction Patterns" target="help/topics/Search/Search_Instruction_Patterns.htm" />
|
||||
<tocdef id="Address Tables" sortgroup="g" text="Address Tables" target="help/topics/Search/Search_for_AddressTables.htm" />
|
||||
<tocdef id="Direct References" sortgroup="h" text="Direct References" target="help/topics/Search/Search_for_DirectReferences.htm" />
|
||||
<tocdef id="Search Results Window" sortgroup="i" text="Query Results Window" target="help/topics/Search/Query_Results_Dialog.htm" />
|
||||
</tocdef>
|
||||
|
||||
<tocdef id="DWARF External Debug Files" sortgroup="q" text="DWARF External Debug Files" target="help/topics/DWARFExternalDebugFilesPlugin/DWARFExternalDebugFilesPlugin.html" >
|
||||
|
||||
@@ -597,6 +597,7 @@
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<DIV align="left">
|
||||
<TABLE border="0" width="100%">
|
||||
<TBODY>
|
||||
@@ -608,7 +609,15 @@
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
</DIV>
|
||||
<H3><A name="Navigation"></A><IMG alt="" src="images/locationIn.gif"> Auto Updating Selection
|
||||
by Location</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The <IMG alt="" src="images/locationIn.gif"> button controls whether a memory
|
||||
block is selected in the Memory Map table when the global program location changes such
|
||||
as when you click in the CodeBrowser, Byte Viewer, or Decompiler. </P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P class="providedbyplugin">Provided by: <I>Memory Map</I> Plugin<BR>
|
||||
</P>
|
||||
</BODY>
|
||||
|
||||
@@ -0,0 +1,478 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<META name="generator" content=
|
||||
"HTML Tidy for Java (vers. 2009-12-01), see jtidy.sourceforge.net">
|
||||
|
||||
<TITLE>Search And Replace</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
<META name="generator" content="Microsoft FrontPage 4.0">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
<BLOCKQUOTE>
|
||||
<A name="Search_And_Replace"></A>
|
||||
|
||||
<H1>Search And Replace</H1>
|
||||
|
||||
<P>The search and replace feature allows to users to search for specific text sequences in
|
||||
various Ghidra elements and replace that text sequence with a different text sequence. Using
|
||||
this feature, many different elements in Ghidra can be renamed including labels, functions,
|
||||
namespaces, parameters, datatypes, field elements, enum values, and others. This feature can
|
||||
also be used to change comments placed on items such as instructions or data, structure field
|
||||
element, or enum values.</P>
|
||||
|
||||
<P>By default, the search matches the text anywhere in an element ("contains"), but it has
|
||||
full <A href="Search_Formats.htm#RegularExpressions">regular expression</A> support where
|
||||
users can easily perform a "starts with" or "ends with" search. Regular expression capture
|
||||
groups are also supported which allows for complex replacing of disjoint strings. See the <A
|
||||
href="#Examples">examples</A> section below for details.</P>
|
||||
|
||||
<P>To initiate a search and replace operation, select <B>Search</B> <IMG alt="" border="0"
|
||||
src="help/shared/arrow.gif"><B>Search and Replace</B> from the main tool menu.</P>
|
||||
|
||||
<H2>Search and Replace Dialog</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The Search and Replace Dialog provides controls and options for performing and search
|
||||
and replace operation.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P align="center"><IMG src="images/SearchAndReplaceDialog.png" border="0" alt=""> </P>
|
||||
|
||||
<P align="center"><I>Search and Replace Dialog</I></P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H3>Find</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>This is a text field for entering the text to be searched for in the selected Ghidra
|
||||
elements. Elements are either the names of things such as labels, functions, datatypes,
|
||||
or a comment associated with some item such as an instruction comment or structure field
|
||||
comment.</P>
|
||||
|
||||
<P>Ghidra will find all the elements that contain the given text. To perform a "starts
|
||||
with" or "ends with" search, you must enter a regular expression here and select the
|
||||
regular expression option below.</P>
|
||||
|
||||
<P>There is also a drop down list of recent searches for this Ghidra session.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Replace</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>This is a text field for entering the replacement text. This text will be used to
|
||||
replace the text that matched the search text. For example, if a function was named
|
||||
"startCat" and the goal was to change it to "startDog", you would enter "Cat" in the
|
||||
Find field and "Dog" in this field.</P>
|
||||
|
||||
<P>This field also include a drop drow of the most recently enter replacement
|
||||
strings.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Options</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H4>Regular Expression</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>If selected, the search text will be interpreted as a regular expression. This
|
||||
allows for complex search and replace operations. See the general section on <A href=
|
||||
"Search_Formats.htm#RegularExpressions">regular expressions</A> for more information. If
|
||||
you want to anything other than a "contains" search, you must use a regular expression.
|
||||
See below for examples on how to do this.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4>Case Sensitive</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>If selected, the entered search text must match exactly. Otherwise, the entered
|
||||
search text can match regardless of the case of the search text or the target text.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H4>Whole Word</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>If selected, the target text must be an entire word. In other words it must be
|
||||
surrounded by white space or must be the first or last word in a word sequence. So,
|
||||
when applied to renaming elements, the search text must match the entire name of the
|
||||
element since element names cannot contain whitespace. But for comments, the search
|
||||
text must entirely match one word withing the comment.</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Search For</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>This section contains a group of checkboxes used to turn on or off the type of Ghidra
|
||||
elements to search. There are also buttons for selecting and deselecting all the element
|
||||
checkboxes. At least one checkbox must be selected to perform a search.</P>
|
||||
|
||||
<UL>
|
||||
<LI><B>Classes</B> - Search the names of classes.</LI>
|
||||
|
||||
<LI><B>Comments</B> - Search instruction or data comments. This includes pre-comments,
|
||||
plate comments, end of line comments, post-comments, and repeatable comments.</LI>
|
||||
|
||||
<LI><B>Datatype Categories</B> - Search for the names of categories in the data types
|
||||
tree.</LI>
|
||||
|
||||
<LI><B>Datatype Comments</B> - Search comments associated with datatypes. This includes
|
||||
descriptions on enums and structures, datatype field comments, and enum value
|
||||
comments.</LI>
|
||||
|
||||
<LI><B>Datatype Fields</B> - Search the names of structure or union field elements.</LI>
|
||||
|
||||
<LI><B>Datatypes</B> - Search the names of any nameable datatype (i.e., does
|
||||
not search built-in datatypes such as byte, word, string, etc.)</LI>
|
||||
|
||||
<LI><B>Enum Values</B> - Search the names of enum values.</LI>
|
||||
|
||||
<LI><B>Functions</B> - Search the names of functions. (Note: This search does not
|
||||
include external function names.)</LI>
|
||||
|
||||
<LI><B>Labels</B> - Search the names of labels. (Note: This search does not include
|
||||
external labels.)</LI>
|
||||
|
||||
<LI><B>Local Variables</B> - Search the names of function local variables. (Note: This
|
||||
does not include local variables derived by the decompiler that haven't been committed
|
||||
to the database.)</LI>
|
||||
|
||||
<LI><B>Memory Blocks</B> - Search the names of Memory Blocks.</LI>
|
||||
|
||||
<LI><B>Namespaces</B> - Search the names of namespaces.</LI>
|
||||
|
||||
<LI><B>Parameters</B> - Search the names of function parameters.</LI>
|
||||
|
||||
<LI><B>Program Trees</B> - Search the names of modules and fragments defined in program
|
||||
trees.</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<A name="Search_And_Replace_Results"></A>
|
||||
<H2>Results Window</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>After initiating a search and replace action, a results window will appear containing a
|
||||
table showing each search match as an entry in the table. At this point, no changes have
|
||||
been made to the program. This provides an opportunity to review the pending changes before
|
||||
they are applied. The changes can now be applied all at once or individually.</P>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<P align="center"><IMG src="images/SearchAndReplaceResults.png" border="0" alt=""> </P>
|
||||
|
||||
<P align="center"><I>Search and Replace Results Window</I></P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<BLOCKQUOTE>
|
||||
<H3>Table information</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Each entry in the table represents one changes that can be applied.</P>
|
||||
|
||||
<H4>Standard Columns</H4>
|
||||
|
||||
<UL>
|
||||
<LI><B>Original</B> - This column displays the original value of the matched
|
||||
element.</LI>
|
||||
|
||||
<LI><B>Preview</B> - This column displays a preview of the value if this change is
|
||||
applied.</LI>
|
||||
|
||||
<LI><B>Action</B> - The change to be applied. (either Rename for names or Update for
|
||||
comments changes.)</LI>
|
||||
|
||||
<LI><B>Type</B> - This column displays the type of element being changed (label,
|
||||
function, comment, etc.)</LI>
|
||||
|
||||
<LI><B>Status</B> - The icon displayed in this column indicates the status of this
|
||||
change.</LI>
|
||||
</UL>
|
||||
|
||||
<H4>Status Icons</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The status column will have one of the following icons to indicate item's
|
||||
status:</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<UL>
|
||||
<LI><B>Blank</B> - The change has not been applied.</LI>
|
||||
|
||||
<LI><IMG alt="" src="Icons.WARNING_ICON"> - The change has some associated warning.
|
||||
Hover on the status to get a detailed message of the issue.</LI>
|
||||
|
||||
<LI><IMG alt="" src="Icons.ERROR_ICON"> - The change can't be applied. This status can
|
||||
appear either before or after an attempt to apply has been made. Hover on the status
|
||||
for more information.</LI>
|
||||
|
||||
<LI><IMG alt="" src="icon.base.plugin.quickfix.done"> - The changes has been applied.</LI>
|
||||
</UL>
|
||||
|
||||
<H4>Optional Columns</H4>
|
||||
|
||||
<UL>
|
||||
<LI>Current - Displays the current value of the element.</LI>
|
||||
|
||||
<LI>Address - Displays the elements address if applicable, blank otherwise</LI>
|
||||
|
||||
<LI>Path - Displays any path associated with the element, if applicable. The type of
|
||||
path varies greatly with the element type.</LI>
|
||||
</UL>
|
||||
|
||||
<H4>Path Column Information</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>The Path column shows different path information depending on the element type:</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<UL>
|
||||
<LI>Classes - the namespace path.</LI>
|
||||
|
||||
<LI>Datatype Categories - the parent category path.</LI>
|
||||
|
||||
<LI>Datatype Comments - the parent category path.</LI>
|
||||
|
||||
<LI>Datatype Names - the parent category path.</LI>
|
||||
|
||||
<LI>Enum Values - the category path of the enum.</LI>
|
||||
|
||||
<LI>Field Names - the category path of the structure or union.</LI>
|
||||
|
||||
<LI>Functions - the namespace path.</LI>
|
||||
|
||||
<LI>Labels - the namespace path.</LI>
|
||||
|
||||
<LI>Local Variables - the namespace path.</LI>
|
||||
|
||||
<LI>Namespaces - the parent namespace path.</LI>
|
||||
|
||||
<LI>Parameters - the namespace path.</LI>
|
||||
|
||||
<LI>Program Trees - the program tree module path</LI>
|
||||
</UL>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Applying Changes</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Changes can be applied in bulk or individually.</P>
|
||||
|
||||
<H4>Apply All Button</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Press this button to apply all items in the table, regardless of what is
|
||||
selected.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<A name="Apply_Selected"></A>
|
||||
<H4>Apply Selected Action</H4>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Press the <IMG alt="" src="icon.base.plugin.quickfix.done"> toolbar button or use the
|
||||
popup action <B>Execute Selected Action(s)</B> to apply just the selected entries in
|
||||
the table. If only one item is selected when this this is done, the selected item will
|
||||
move to the next item in the table to facilitate a one at a time workflow.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<A name="Auto_Delete"></A>
|
||||
<P><IMG alt="" border="0" src="help/shared/note.png"> There is also a popup toggle action
|
||||
to turn on an option to auto delete applied entries from the table.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Navigation</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>If the <IMG alt="" src="Icons.NAVIGATE_ON_INCOMING_EVENT_ICON"> toolbar button is
|
||||
selected, selecting items in the table will attempt to navigate to that item in the tool
|
||||
if possible.</P>
|
||||
|
||||
<P>Double clicking (or pressing return key) will also attempt to navigate the tool to the
|
||||
selected item. In addition, if the item is related to a datatype, an editor for that
|
||||
datatype will be shown.</P>
|
||||
</BLOCKQUOTE><A name="Examples"></A>
|
||||
|
||||
<H2>Search Examples</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<H3>Basic Searches</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Without using regular expressions, you can find matches that contain the search text
|
||||
or fully match the search text by turning on the "whole word" option. However, to
|
||||
perform a "starts with" or "ends with" search, you must use a regular expression. Also,
|
||||
you can do advanced match and replace using regular expressions capture groups.</P>
|
||||
|
||||
<P>The following examples assume we are trying to replace label names and we have the
|
||||
following labels in our program:</P>
|
||||
|
||||
<UL>
|
||||
<LI>Apple</LI>
|
||||
|
||||
<LI>myApple</LI>
|
||||
|
||||
<LI>AppleJuice</LI>
|
||||
</UL>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<TABLE border="1" cellspacing="2" cellpadding="6">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="300">Search Type<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="5">RegEx<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="5">Whole Word<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="10">Search For<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="10">Replace With<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="10">Matches<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="10">Results<BR>
|
||||
</TH>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD><B>Contains</B></TD>
|
||||
|
||||
<TD>Off</TD>
|
||||
|
||||
<TD>Off</TD>
|
||||
|
||||
<TD>Apple</TD>
|
||||
|
||||
<TD>Pear</TD>
|
||||
|
||||
<TD>Apple, myApple, AppleJuice</TD>
|
||||
|
||||
<TD>Pear, myPear, PearJuice</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD><B>Matches Fully</B></TD>
|
||||
|
||||
<TD>Off</TD>
|
||||
|
||||
<TD>On</TD>
|
||||
|
||||
<TD>Apple</TD>
|
||||
|
||||
<TD>Pear</TD>
|
||||
|
||||
<TD>Apple</TD>
|
||||
|
||||
<TD>Pear</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD><B>Starts With</B></TD>
|
||||
|
||||
<TD>On</TD>
|
||||
|
||||
<TD>N/A</TD>
|
||||
|
||||
<TD>^Apple</TD>
|
||||
|
||||
<TD>Pear</TD>
|
||||
|
||||
<TD>Apple, AppleJuice</TD>
|
||||
|
||||
<TD>Pear, PearJuice</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD><B>Ends With</B></TD>
|
||||
|
||||
<TD>On</TD>
|
||||
|
||||
<TD>N/A</TD>
|
||||
|
||||
<TD>Apple$</TD>
|
||||
|
||||
<TD>Pear</TD>
|
||||
|
||||
<TD>Apple, MyApple</TD>
|
||||
|
||||
<TD>Pear, MyPear</TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Advanced RegEx Searches</H3>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>Regular Expression can do many advanced types of matching and replacing which is
|
||||
beyond the scope of this document. However, a simple example using capture groups will
|
||||
be given as follows:</P>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<TABLE border="1" cellspacing="2" cellpadding="6">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="10">Search For<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="10">Replace With<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="10">Matches<BR>
|
||||
</TH>
|
||||
|
||||
<TH valign="top" bgcolor="#c0c0c0" width="10">Results<BR>
|
||||
</TH>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD>Red(.*)Blue(.*)</TD>
|
||||
|
||||
<TD>Green$1Purple$2</TD>
|
||||
|
||||
<TD>RedApplesBlueBerries</TD>
|
||||
|
||||
<TD>GreenApplesPurpleBerries</TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
</TABLE>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
</BLOCKQUOTE>
|
||||
<P class="providedbyplugin">Provided by: <I>SearchAndReplacePlugin</I><BR>
|
||||
</P>
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
|
||||
<UL>
|
||||
<LI>
|
||||
<P class="relatedtopic"><A href=
|
||||
"help/topics/Search/Searching.htm">Searching</A></P>
|
||||
</LI>
|
||||
<LI>
|
||||
<P class="relatedtopic"><A href=
|
||||
"help/topics/Search/Search_Program_Text.htm">Search Program Text</A></P>
|
||||
</LI>
|
||||
|
||||
</UL><BR>
|
||||
<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
|
||||
</BODY>
|
||||
</HTML>
|
||||
@@ -17,9 +17,12 @@
|
||||
<P>Ghidra offers a variety of searching capabilities. The <I>Search Program Memory</I>
|
||||
feature performs fast searching for byte patterns in program memory. The <I><A href=
|
||||
"Search_Program_Text.htm">Search Program Text</A></I> feature searches for text strings in
|
||||
various parts of the listing such as comments, labels, mnemonics, and operands. The <I><A
|
||||
href="Search_for_Strings.htm">Search For Strings</A></I> feature automatically finds potential
|
||||
ascii strings within the program memory.</P>
|
||||
various parts of the listing such as comments, labels, mnemonics, and operands. The
|
||||
<I><A href="SearchAndReplace.htm">Search and Replace</A></I> features allows for globally
|
||||
searching and replacing names or comments on many different types of program elements such as
|
||||
Labels, Functions, Datatypes, Enum Values, and and many others. The
|
||||
<I><A href="Search_for_Strings.htm">Search For Strings</A></I> feature
|
||||
automatically finds potential ascii strings within the program memory.</P>
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
|
||||
@@ -28,6 +31,8 @@
|
||||
|
||||
<LI><A href="Search_Program_Text.htm">Search Program Text</A></LI>
|
||||
|
||||
<LI><A href="SearchAndReplace.htm">Search And Replace</A></LI>
|
||||
|
||||
<LI><A href="Search_for_Strings.htm">Search For Strings</A></LI>
|
||||
|
||||
<LI><A href="Search_for_AddressTables.htm">Search For Address Tables</A></LI>
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
+8
-3
@@ -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.
|
||||
@@ -55,7 +55,7 @@ public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
|
||||
}
|
||||
|
||||
@Override
|
||||
public void edit(Structure structure, String fieldName) {
|
||||
public void edit(Composite compposite, String fieldName) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@@ -100,6 +100,11 @@ public class DefaultDataTypeManagerService extends DefaultDataTypeArchiveService
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCategorySelected(Category category) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DataType> getSelectedDatatypes() {
|
||||
throw new UnsupportedOperationException();
|
||||
|
||||
+4
-5
@@ -1,13 +1,12 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* 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.
|
||||
@@ -98,7 +97,7 @@ public class CommentsActionFactory {
|
||||
if (!isCommentSupported(loc)) {
|
||||
return false;
|
||||
}
|
||||
return CommentType.isCommentAllowed(getCodeUnit(actionContext), loc);
|
||||
return CommentTypeUtils.isCommentAllowed(getCodeUnit(actionContext), loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,7 +131,7 @@ public class CommentsActionFactory {
|
||||
@Override
|
||||
protected int getEditCommentType(ActionContext context) {
|
||||
CodeUnit cu = getCodeUnit(context);
|
||||
return CommentType.getCommentType(cu, getLocationForContext(context), CodeUnit.NO_COMMENT);
|
||||
return CommentTypeUtils.getCommentType(cu, getLocationForContext(context), CodeUnit.NO_COMMENT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -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.
|
||||
@@ -129,7 +129,7 @@ public class CommentsPlugin extends Plugin implements OptionsChangeListener {
|
||||
* @param loc the {@link ProgramLocation} for which to delete the comment
|
||||
*/
|
||||
void deleteComments(Program program, ProgramLocation loc) {
|
||||
int commentType = CommentType.getCommentType(null, loc, CodeUnit.EOL_COMMENT);
|
||||
int commentType = CommentTypeUtils.getCommentType(null, loc, CodeUnit.EOL_COMMENT);
|
||||
SetCommentCmd cmd = new SetCommentCmd(loc.getByteAddress(), commentType, null);
|
||||
tool.execute(cmd, program);
|
||||
}
|
||||
@@ -138,7 +138,7 @@ public class CommentsPlugin extends Plugin implements OptionsChangeListener {
|
||||
if (codeUnit == null) {
|
||||
return false;
|
||||
}
|
||||
int commentType = CommentType.getCommentType(null, loc, CodeUnit.NO_COMMENT);
|
||||
int commentType = CommentTypeUtils.getCommentType(null, loc, CodeUnit.NO_COMMENT);
|
||||
return (commentType != CodeUnit.NO_COMMENT && codeUnit.getComment(commentType) != null);
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ public class CommentsPlugin extends Plugin implements OptionsChangeListener {
|
||||
else {
|
||||
historyAction.getPopupMenuData().setMenuPath(HISTORY_MENUPATH);
|
||||
}
|
||||
historyAction.setEnabled(CommentType.isCommentAllowed(context.getCodeUnit(), loc));
|
||||
historyAction.setEnabled(CommentTypeUtils.isCommentAllowed(context.getCodeUnit(), loc));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -227,7 +227,7 @@ public class CommentsPlugin extends Plugin implements OptionsChangeListener {
|
||||
private void showCommentHistory(ListingActionContext context) {
|
||||
CodeUnit cu = context.getCodeUnit();
|
||||
ProgramLocation loc = context.getLocation();
|
||||
int commentType = CommentType.getCommentType(null, loc, CodeUnit.EOL_COMMENT);
|
||||
int commentType = CommentTypeUtils.getCommentType(null, loc, CodeUnit.EOL_COMMENT);
|
||||
CommentHistoryDialog historyDialog = new CommentHistoryDialog(cu, commentType);
|
||||
tool.showDialog(historyDialog, context.getComponentProvider());
|
||||
}
|
||||
|
||||
+6
@@ -214,6 +214,12 @@ public abstract class CompositeEditorProvider extends ComponentProviderAdapter
|
||||
return new DefaultActionContext(this, null);
|
||||
}
|
||||
|
||||
public void selectField(String fieldName) {
|
||||
if (fieldName != null) {
|
||||
editorPanel.selectField(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HelpLocation getHelpLocation() {
|
||||
return new HelpLocation(getHelpTopic(), getHelpName());
|
||||
|
||||
-6
@@ -97,12 +97,6 @@ public class StructureEditorProvider extends CompositeEditorProvider {
|
||||
return "DataTypeEditors";
|
||||
}
|
||||
|
||||
public void selectField(String fieldName) {
|
||||
if (fieldName != null) {
|
||||
editorPanel.selectField(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void closeDependentEditors() {
|
||||
if (bitFieldEditor != null && bitFieldEditor.isVisible()) {
|
||||
|
||||
+11
-2
@@ -498,8 +498,8 @@ public class DataTypeManagerPlugin extends ProgramPlugin
|
||||
}
|
||||
|
||||
@Override
|
||||
public void edit(Structure dt, String fieldName) {
|
||||
editorManager.edit(dt, fieldName);
|
||||
public void edit(Composite composite, String fieldName) {
|
||||
editorManager.edit(composite, fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -620,6 +620,15 @@ public class DataTypeManagerPlugin extends ProgramPlugin
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCategorySelected(Category category) {
|
||||
if (provider.isVisible()) {
|
||||
// this is a service method, ensure it is on the Swing thread, since it interacts with
|
||||
// Swing components
|
||||
Swing.runIfSwingOrRunLater(() -> provider.setCategorySelected(category));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DataType> getSelectedDatatypes() {
|
||||
if (provider.isVisible()) {
|
||||
|
||||
+39
@@ -781,6 +781,45 @@ public class DataTypesProvider extends ComponentProviderAdapter {
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the given data type category in the tree of data types. This method will cause the
|
||||
* data type tree to come to the front, scroll to the category and then to select the tree
|
||||
* node that represents the category. If the category is null, the selection is cleared.
|
||||
*
|
||||
* @param category the category to select; may be null
|
||||
*/
|
||||
public void setCategorySelected(Category category) {
|
||||
DataTypeArchiveGTree gTree = getGTree();
|
||||
if (category == null) { // clear the selection
|
||||
gTree.clearSelectionPaths();
|
||||
return;
|
||||
}
|
||||
|
||||
DataTypeManager dataTypeManager = category.getDataTypeManager();
|
||||
if (dataTypeManager == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
ArchiveRootNode rootNode = (ArchiveRootNode) gTree.getViewRoot();
|
||||
ArchiveNode archiveNode = rootNode.getNodeForManager(dataTypeManager);
|
||||
if (archiveNode == null) {
|
||||
plugin.setStatus("Cannot find archive '" + dataTypeManager.getName() + "'. It may " +
|
||||
"be filtered out of view or may have been closed (Data Type Manager)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Note: passing 'true' here forces a load if needed. This could be slow for programs
|
||||
// with many types. If this locks the UI, then put this work into a GTreeTask.
|
||||
CategoryNode node = archiveNode.findCategoryNode(category, true);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
gTree.setSelectedNode(node);
|
||||
gTree.scrollPathToVisible(node.getTreePath());
|
||||
contextChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all the data types selected in the data types tree
|
||||
* @return a list of all the data types selected in the data types tree
|
||||
|
||||
+11
-7
@@ -126,20 +126,24 @@ public class DataTypeEditorManager implements EditorListener {
|
||||
* Displays a data type editor for editing the given Structure. If the structure is already
|
||||
* being edited then it is brought to the front. Otherwise, a new editor is created and
|
||||
* displayed.
|
||||
* @param structure the structure.
|
||||
* @param composite the structure.
|
||||
* @param fieldName the optional name of the field to select in the editor.
|
||||
*/
|
||||
public void edit(Structure structure, String fieldName) {
|
||||
public void edit(Composite composite, String fieldName) {
|
||||
|
||||
StructureEditorProvider editor = (StructureEditorProvider) getEditor(structure);
|
||||
CompositeEditorProvider editor = (CompositeEditorProvider) getEditor(composite);
|
||||
if (editor != null) {
|
||||
reuseExistingEditor(structure);
|
||||
reuseExistingEditor(composite);
|
||||
editor.selectField(fieldName);
|
||||
return;
|
||||
}
|
||||
|
||||
editor = new StructureEditorProvider(plugin, structure,
|
||||
showStructureNumbersInHex());
|
||||
if (composite instanceof Union) {
|
||||
editor = new UnionEditorProvider(plugin, (Union) composite, showUnionNumbersInHex());
|
||||
}
|
||||
else if (composite instanceof Structure) {
|
||||
editor = new StructureEditorProvider(plugin, (Structure) composite,
|
||||
showStructureNumbersInHex());
|
||||
}
|
||||
editor.selectField(fieldName);
|
||||
editor.addEditorListener(this);
|
||||
editorList.add(editor);
|
||||
|
||||
+7
-2
@@ -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.
|
||||
@@ -129,6 +129,11 @@ public class MemoryMapPlugin extends ProgramPlugin implements DomainObjectListen
|
||||
provider.setProgram(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void locationChanged(ProgramLocation location) {
|
||||
provider.locationChanged(location);
|
||||
}
|
||||
|
||||
MemoryMapManager getMemoryMapManager() {
|
||||
return memManager;
|
||||
}
|
||||
|
||||
+28
-4
@@ -25,9 +25,9 @@ import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingAction;
|
||||
import docking.action.ToolBarData;
|
||||
import docking.action.*;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.action.builder.ToggleActionBuilder;
|
||||
import docking.widgets.OptionDialog;
|
||||
import docking.widgets.table.*;
|
||||
import docking.widgets.textfield.GValidatedTextField.MaxLengthField;
|
||||
@@ -40,12 +40,13 @@ import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.OverlayAddressSpace;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.exception.UsrException;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
import ghidra.util.table.actions.MakeProgramSelectionAction;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* Provider for the memory map Component.
|
||||
@@ -72,6 +73,9 @@ class MemoryMapProvider extends ComponentProviderAdapter {
|
||||
private Program program;
|
||||
private MemoryMapManager memManager;
|
||||
|
||||
private boolean followLocationChanges;
|
||||
private ToggleDockingAction toggleNavigateAction;
|
||||
|
||||
MemoryMapProvider(MemoryMapPlugin plugin) {
|
||||
super(plugin.getTool(), "Memory Map", plugin.getName(), ProgramActionContext.class);
|
||||
this.plugin = plugin;
|
||||
@@ -104,6 +108,17 @@ class MemoryMapProvider extends ComponentProviderAdapter {
|
||||
return new ProgramActionContext(this, program, table);
|
||||
}
|
||||
|
||||
void locationChanged(ProgramLocation location) {
|
||||
if (!followLocationChanges || location == null || location.getAddress() == null) {
|
||||
return;
|
||||
}
|
||||
Memory memory = program.getMemory();
|
||||
MemoryBlock block = memory.getBlock(location.getAddress());
|
||||
if (block != null) {
|
||||
filterPanel.setSelectedItem(block);
|
||||
}
|
||||
}
|
||||
|
||||
void setStatusText(String msg) {
|
||||
tool.setStatusInfo(msg);
|
||||
}
|
||||
@@ -320,6 +335,15 @@ class MemoryMapProvider extends ComponentProviderAdapter {
|
||||
MakeProgramSelectionAction action = new MakeProgramSelectionAction(plugin, table);
|
||||
action.getToolBarData().setToolBarGroup("B"); // the other actions are in group 'A'
|
||||
tool.addLocalAction(this, action);
|
||||
|
||||
toggleNavigateAction = new ToggleActionBuilder("Memory Map Navigation", plugin.getName())
|
||||
.toolBarIcon(Icons.NAVIGATE_ON_INCOMING_EVENT_ICON)
|
||||
.selected(false)
|
||||
.helpLocation(new HelpLocation("MemoryMapPlugin", "Navigation"))
|
||||
.description(HTMLUtilities.toHTML("Toggle <b>on</b> means to select the block" +
|
||||
" that contains the current location"))
|
||||
.onAction(c -> followLocationChanges = toggleNavigateAction.isSelected())
|
||||
.buildAndInstallLocal(this);
|
||||
}
|
||||
|
||||
private boolean checkExclusiveAccess() {
|
||||
|
||||
+6
-2
@@ -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.
|
||||
@@ -51,6 +51,10 @@ import ghidra.util.task.TaskMonitor;
|
||||
* for that address. The textual representation also maintains information about the field
|
||||
* that generated it so that the search can be constrained to specific fields such as the
|
||||
* label or comment field.
|
||||
*
|
||||
* <p> NOTE: This only searches defined instructions or data, which is possibly
|
||||
* a mistake since this is more of a WYSIWYG search. However, searching undefined code units could
|
||||
* make this slow search even more so.
|
||||
*
|
||||
*/
|
||||
class ListingDisplaySearcher implements Searcher {
|
||||
|
||||
+11
-4
@@ -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.
|
||||
@@ -39,10 +39,11 @@ import ghidra.program.util.ProgramLocation;
|
||||
"in a tree hierarchy. All symbols (except for the global namespace symbol)" +
|
||||
" have a parent symbol. From the tree, symbols can be renamed, deleted, or " +
|
||||
"reorganized.",
|
||||
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class }
|
||||
eventsConsumed = { ProgramActivatedPluginEvent.class, ProgramLocationPluginEvent.class, ProgramClosedPluginEvent.class },
|
||||
servicesProvided = { SymbolTreeService.class }
|
||||
)
|
||||
//@formatter:on
|
||||
public class SymbolTreePlugin extends Plugin {
|
||||
public class SymbolTreePlugin extends Plugin implements SymbolTreeService {
|
||||
|
||||
public static final String PLUGIN_NAME = "SymbolTreePlugin";
|
||||
|
||||
@@ -185,4 +186,10 @@ public class SymbolTreePlugin extends Plugin {
|
||||
tool.showComponentProvider(newProvider, true);
|
||||
return newProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void selectSymbol(Symbol symbol) {
|
||||
connectedProvider.selectSymbol(symbol);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -552,6 +552,10 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
return;
|
||||
}
|
||||
|
||||
selectSymbol(symbol);
|
||||
}
|
||||
|
||||
public void selectSymbol(Symbol symbol) {
|
||||
SymbolTreeRootNode rootNode = (SymbolTreeRootNode) tree.getViewRoot();
|
||||
tree.runTask(new SearchTask(tree, rootNode, symbol));
|
||||
}
|
||||
@@ -805,4 +809,5 @@ public class SymbolTreeProvider extends ComponentProviderAdapter {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/* ###
|
||||
* 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.symboltree;
|
||||
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
|
||||
/**
|
||||
* Service to interact with the Symbol Tree.
|
||||
*/
|
||||
public interface SymbolTreeService {
|
||||
/**
|
||||
* Selects the given symbol in the symbol tree.
|
||||
* @param symbol the symbol to select in the symbol tree
|
||||
*/
|
||||
public void selectSymbol(Symbol symbol);
|
||||
}
|
||||
@@ -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.
|
||||
@@ -96,14 +96,14 @@ public interface DataTypeManagerService extends DataTypeQueryService, DataTypeAr
|
||||
public void edit(DataType dt);
|
||||
|
||||
/**
|
||||
* Pop up an editor window for the given structure.
|
||||
* Pop up an editor window for the given structure or union
|
||||
*
|
||||
* @param structure the structure
|
||||
* @param fieldName the optional structure field name to select in the editor window
|
||||
* @param composite the structure or union
|
||||
* @param fieldName the optional field name to select in the editor window
|
||||
* @throws IllegalArgumentException if the given has not been resolved by a DataTypeManager;
|
||||
* in other words, if {@link DataType#getDataTypeManager()} returns null
|
||||
*/
|
||||
public void edit(Structure structure, String fieldName);
|
||||
public void edit(Composite composite, String fieldName);
|
||||
|
||||
/**
|
||||
* Selects the given data type in the display of data types. A null <code>dataType</code>
|
||||
@@ -113,6 +113,15 @@ public interface DataTypeManagerService extends DataTypeQueryService, DataTypeAr
|
||||
*/
|
||||
public void setDataTypeSelected(DataType dataType);
|
||||
|
||||
/**
|
||||
* Selects the given data type category in the tree of data types. This method will cause the
|
||||
* data type tree to come to the front, scroll to the category and then to select the tree
|
||||
* node that represents the category. If the category is null, the selection is cleared.
|
||||
*
|
||||
* @param category the category to select; may be null
|
||||
*/
|
||||
public void setCategorySelected(Category category);
|
||||
|
||||
/**
|
||||
* Returns the list of data types that are currently selected in the data types tree
|
||||
* @return the list of data types that are currently selected in the data types tree
|
||||
|
||||
@@ -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.
|
||||
@@ -28,30 +28,33 @@ import ghidra.program.util.GroupPath;
|
||||
*
|
||||
*
|
||||
*/
|
||||
@ServiceInfo(defaultProvider = ProgramTreePlugin.class, description = "Get the currently viewed address set")
|
||||
@ServiceInfo(
|
||||
defaultProvider = ProgramTreePlugin.class,
|
||||
description = "Get the currently viewed address set"
|
||||
)
|
||||
public interface ProgramTreeService {
|
||||
|
||||
|
||||
/**
|
||||
* Get the name of the tree currently being viewed.
|
||||
*/
|
||||
public String getViewedTreeName();
|
||||
|
||||
|
||||
/**
|
||||
* Set the current view to that of the given name. If treeName is not
|
||||
* a known view, then nothing happens.
|
||||
* @param treeName name of the view
|
||||
*/
|
||||
public void setViewedTree(String treeName);
|
||||
|
||||
|
||||
/**
|
||||
* Get the address set of the current view (what is currently being shown in
|
||||
* the Code Browser).
|
||||
*/
|
||||
public AddressSet getView();
|
||||
|
||||
public AddressSet getView();
|
||||
|
||||
/**
|
||||
* Set the selection to the given group paths.
|
||||
* @param gps paths to select
|
||||
* @param groupPaths paths to select
|
||||
*/
|
||||
public void setGroupSelection(GroupPath[] gps);
|
||||
public void setGroupSelection(GroupPath... groupPaths);
|
||||
}
|
||||
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.quickfix;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.TableModel;
|
||||
|
||||
import docking.*;
|
||||
import docking.action.ToggleDockingAction;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import docking.action.builder.ToggleActionBuilder;
|
||||
import docking.widgets.table.GTable;
|
||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.services.GoToService;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramTask;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.table.*;
|
||||
import ghidra.util.table.actions.DeleteTableRowAction;
|
||||
import ghidra.util.table.actions.MakeProgramSelectionAction;
|
||||
import ghidra.util.task.TaskLauncher;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Component Provider for displaying lists of {@link QuickFix}s and the actions to execute them
|
||||
* in bulk or individually.
|
||||
*/
|
||||
public class QuckFixTableProvider extends ComponentProvider {
|
||||
private static final Icon EXECUTE_ICON = new GIcon("icon.base.plugin.quickfix.done");
|
||||
private JComponent component;
|
||||
private QuickFixTableModel tableModel;
|
||||
private GhidraThreadedTablePanel<QuickFix> threadedPanel;
|
||||
private GhidraTableFilterPanel<QuickFix> tableFilterPanel;
|
||||
private GhidraTable table;
|
||||
private ToggleDockingAction toggleAutoDeleteAction;
|
||||
private boolean autoDelete;
|
||||
|
||||
public QuckFixTableProvider(PluginTool tool, String title, String owner, Program program,
|
||||
TableDataLoader<QuickFix> loader) {
|
||||
super(tool, title, owner);
|
||||
setIcon(new GIcon("icon.plugin.table.service"));
|
||||
setTransient();
|
||||
setTitle(title);
|
||||
|
||||
tableModel = new QuickFixTableModel(program, title, tool, loader);
|
||||
tableModel.addInitialLoadListener(b -> tableLoaded(b, loader));
|
||||
|
||||
component = buildMainPanel();
|
||||
|
||||
createActions(owner);
|
||||
|
||||
tableModel.addTableModelListener(e -> tableDataChanged());
|
||||
}
|
||||
|
||||
protected void tableLoaded(boolean wasCancelled, TableDataLoader<QuickFix> loader) {
|
||||
// used by subclasses
|
||||
}
|
||||
|
||||
private void updateSubTitle() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.append(" ");
|
||||
int count = tableModel.getUnfilteredRowCount();
|
||||
if (count > 0) {
|
||||
builder.append("(");
|
||||
builder.append(count);
|
||||
builder.append(count == 1 ? " item)" : " items)");
|
||||
}
|
||||
setSubTitle(builder.toString());
|
||||
}
|
||||
|
||||
protected void createActions(String owner) {
|
||||
new ActionBuilder("Apply Action", owner)
|
||||
.popupMenuPath("Apply Selected Items(s)")
|
||||
.popupMenuIcon(EXECUTE_ICON)
|
||||
.popupMenuGroup("aaa")
|
||||
.toolBarIcon(EXECUTE_ICON)
|
||||
.description("Applies the selected items")
|
||||
.helpLocation(new HelpLocation("Search", "Apply_Selected"))
|
||||
.keyBinding("ctrl e")
|
||||
.withContext(QuickFixActionContext.class)
|
||||
.enabledWhen(c -> c.getSelectedRowCount() > 0)
|
||||
.onAction(this::applySelectedItems)
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
toggleAutoDeleteAction = new ToggleActionBuilder("Toggle Auto Delete", owner)
|
||||
.popupMenuPath("Auto Delete Completed Items")
|
||||
.popupMenuGroup("settings")
|
||||
.helpLocation(new HelpLocation("Search", "Auto_Delete"))
|
||||
.description("If on, automatically remove completed items from the list")
|
||||
.onAction(this::toggleAutoDelete)
|
||||
.buildAndInstallLocal(this);
|
||||
|
||||
addLocalAction(new SelectionNavigationAction(owner, table));
|
||||
|
||||
GoToService service = dockingTool.getService(GoToService.class);
|
||||
if (service != null) {
|
||||
Navigatable navigatable = service.getDefaultNavigatable();
|
||||
addLocalAction(new MakeProgramSelectionAction(navigatable, owner, table, "bbb"));
|
||||
}
|
||||
DeleteTableRowAction deleteAction = new DeleteTableRowAction(table, owner, "bbb") {
|
||||
@Override
|
||||
public void actionPerformed(ActionContext context) {
|
||||
super.actionPerformed(context);
|
||||
updateSubTitle();
|
||||
}
|
||||
};
|
||||
addLocalAction(deleteAction);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ActionContext getActionContext(MouseEvent event) {
|
||||
return new QuickFixActionContext();
|
||||
}
|
||||
|
||||
private void tableDataChanged() {
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
private void updateTitle() {
|
||||
int rowCount = tableModel.getRowCount();
|
||||
int filteredRowCount = tableFilterPanel.getRowCount();
|
||||
setSubTitle("(" + filteredRowCount + " of " + rowCount + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeComponent() {
|
||||
super.closeComponent();
|
||||
tableFilterPanel.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
protected JPanel buildMainPanel() {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
threadedPanel = new GhidraThreadedTablePanel<>(tableModel) {
|
||||
protected GTable createTable(ThreadedTableModel<QuickFix, ?> model) {
|
||||
return new QuickFixGhidraTable(model);
|
||||
}
|
||||
};
|
||||
table = threadedPanel.getTable();
|
||||
table.getSelectionModel().addListSelectionListener(e -> {
|
||||
if (e.getValueIsAdjusting()) {
|
||||
return;
|
||||
}
|
||||
dockingTool.contextChanged(QuckFixTableProvider.this);
|
||||
});
|
||||
|
||||
table.setActionsEnabled(true);
|
||||
table.installNavigation(dockingTool);
|
||||
|
||||
panel.add(threadedPanel, BorderLayout.CENTER);
|
||||
panel.add(createFilterFieldPanel(), BorderLayout.SOUTH);
|
||||
panel.setPreferredSize(new Dimension(1000, 600));
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel createFilterFieldPanel() {
|
||||
tableFilterPanel = new GhidraTableFilterPanel<>(table, tableModel);
|
||||
return tableFilterPanel;
|
||||
}
|
||||
|
||||
public boolean isBusy(TableModel model) {
|
||||
|
||||
if (!(model instanceof ThreadedTableModel)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ThreadedTableModel<?, ?> threadedModel = (ThreadedTableModel<?, ?>) model;
|
||||
if (threadedModel.isBusy()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void applySelectedItems(QuickFixActionContext context) {
|
||||
List<QuickFix> selectedItems = tableFilterPanel.getSelectedItems();
|
||||
int nextIndex = selectedItems.size() == 1 ? table.getSelectedRow() : -1;
|
||||
|
||||
applyItems(selectedItems);
|
||||
|
||||
if (nextIndex >= 0) {
|
||||
int index = nextIndex + 1;
|
||||
if (index < table.getRowCount()) {
|
||||
table.selectRow(nextIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (autoDelete) {
|
||||
removeCompletedItems(selectedItems);
|
||||
}
|
||||
tableModel.fireTableDataChanged();
|
||||
}
|
||||
|
||||
private void toggleAutoDelete(ActionContext context) {
|
||||
autoDelete = toggleAutoDeleteAction.isSelected();
|
||||
if (autoDelete) {
|
||||
removeCompletedItems(tableModel.getModelData());
|
||||
}
|
||||
}
|
||||
|
||||
private void removeCompletedItems(List<QuickFix> items) {
|
||||
List<QuickFix> toDelete = new ArrayList<>();
|
||||
for (QuickFix item : items) {
|
||||
if (item.getStatus() == QuickFixStatus.DONE) {
|
||||
toDelete.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
for (QuickFix quickFix : toDelete) {
|
||||
tableModel.removeObject(quickFix);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyItems(List<QuickFix> quickFixList) {
|
||||
Program program = tableModel.getProgram();
|
||||
ProgramTask task = new ApplyItemsTask(program, getTaskTitle(), quickFixList);
|
||||
TaskLauncher.launch(task);
|
||||
}
|
||||
|
||||
public void executeAll() {
|
||||
List<QuickFix> allItems = tableModel.getModelData();
|
||||
applyItems(allItems);
|
||||
tableModel.fireTableDataChanged();
|
||||
}
|
||||
|
||||
protected String getTaskTitle() {
|
||||
return "Applying Items";
|
||||
}
|
||||
|
||||
public void programClosed(Program program) {
|
||||
if (program == tableModel.getProgram()) {
|
||||
this.closeComponent();
|
||||
}
|
||||
}
|
||||
|
||||
private static class ApplyItemsTask extends ProgramTask {
|
||||
|
||||
private List<QuickFix> quickFixList;
|
||||
|
||||
public ApplyItemsTask(Program program, String title, List<QuickFix> quickFixList) {
|
||||
super(program, title, true, true, true);
|
||||
this.quickFixList = quickFixList;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRun(TaskMonitor monitor) {
|
||||
for (QuickFix quickFix : quickFixList) {
|
||||
quickFix.performAction();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the table model.
|
||||
* @return the table model
|
||||
*/
|
||||
public QuickFixTableModel getTableModel() {
|
||||
return tableModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the selected rows in the table
|
||||
* @param start the index of the first row to select
|
||||
* @param end the index of the last row to select
|
||||
*/
|
||||
public void setSelection(int start, int end) {
|
||||
table.setRowSelectionInterval(start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected row in the table
|
||||
* @return the selected row in the table
|
||||
*/
|
||||
public int getSelectedRow() {
|
||||
return table.getSelectedRow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all the selected items.
|
||||
*/
|
||||
public void applySelected() {
|
||||
applySelectedItems(null);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
private class QuickFixActionContext extends DefaultActionContext {
|
||||
QuickFixActionContext() {
|
||||
super(QuckFixTableProvider.this, table);
|
||||
}
|
||||
|
||||
public int getSelectedRowCount() {
|
||||
return table.getSelectedRowCount();
|
||||
}
|
||||
}
|
||||
|
||||
private class QuickFixGhidraTable extends GhidraTable {
|
||||
boolean fromSelectionChange = false;
|
||||
|
||||
public QuickFixGhidraTable(ThreadedTableModel<QuickFix, ?> model) {
|
||||
super(model);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void navigate(int row, int column) {
|
||||
if (!doSpecialNavigate(row)) {
|
||||
super.navigate(row, column);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateOnCurrentSelection(int row, int column) {
|
||||
fromSelectionChange = true;
|
||||
try {
|
||||
super.navigateOnCurrentSelection(row, column);
|
||||
}
|
||||
finally {
|
||||
fromSelectionChange = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean doSpecialNavigate(int row) {
|
||||
QuickFix quickFix = tableFilterPanel.getRowObject(row);
|
||||
return quickFix.navigateSpecial(dockingTool, fromSelectionChange);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.quickfix;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
|
||||
/**
|
||||
* Generic base class for executable items to be displayed in a table that can be executed in bulk or
|
||||
* individually.
|
||||
*/
|
||||
public abstract class QuickFix {
|
||||
private long modificationNumber;
|
||||
private QuickFixStatus status = QuickFixStatus.NONE;
|
||||
private String statusMessage;
|
||||
protected final Program program;
|
||||
protected final String original;
|
||||
protected final String replacement;
|
||||
protected String current;
|
||||
|
||||
protected QuickFix(Program program, String original, String replacement) {
|
||||
this.program = program;
|
||||
this.original = original;
|
||||
this.replacement = replacement;
|
||||
this.current = original;
|
||||
this.modificationNumber = program.getModificationNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the general name of the action to be performed.
|
||||
* @return the general name of the action to be performed
|
||||
*/
|
||||
public abstract String getActionName();
|
||||
|
||||
/**
|
||||
* Returns the type of program element being affected (function, label, comment, etc.)
|
||||
* @return the type of program element being affected
|
||||
*/
|
||||
public abstract String getItemType();
|
||||
|
||||
/**
|
||||
* Returns the address of the affected program element if applicable or null otherwise.
|
||||
* @return the address of the affected program element if applicable or null otherwise
|
||||
*/
|
||||
public abstract Address getAddress();
|
||||
|
||||
/**
|
||||
* Returns a path (the meaning of the path varies with the item type) associated with the
|
||||
* affected program element if applicable or null otherwise.
|
||||
* @return a path associated with the affected program if applicable or null otherwise
|
||||
*/
|
||||
public abstract String getPath();
|
||||
|
||||
/**
|
||||
* Returns the original value of the affected program element.
|
||||
* @return the original value of the affected program element.
|
||||
*/
|
||||
public String getOriginal() {
|
||||
return original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the affected program element.
|
||||
* @return the current value of the affected program element.
|
||||
*/
|
||||
public final String getCurrent() {
|
||||
refresh();
|
||||
return current;
|
||||
}
|
||||
|
||||
protected void refresh() {
|
||||
if (program.getModificationNumber() == modificationNumber) {
|
||||
return;
|
||||
}
|
||||
modificationNumber = program.getModificationNumber();
|
||||
|
||||
// once in an error status, it must stay that way (to distinguish it from the
|
||||
// "not done" state, otherwise we would clear it when refresh the status)
|
||||
if (status == QuickFixStatus.ERROR) {
|
||||
return;
|
||||
}
|
||||
|
||||
current = doGetCurrent();
|
||||
updateStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a preview of what the affected element will be if this item is applied.
|
||||
* @return a preview of what the affected element will be if this item is applied
|
||||
*/
|
||||
public final String getPreview() {
|
||||
return replacement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the primary action of this QuickFix.
|
||||
*/
|
||||
public final void performAction() {
|
||||
if (status == QuickFixStatus.ERROR || status == QuickFixStatus.DONE) {
|
||||
return;
|
||||
}
|
||||
execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link QuickFixStatus} of this item.
|
||||
* @return the current {@link QuickFixStatus} of this item
|
||||
*/
|
||||
public final QuickFixStatus getStatus() {
|
||||
refresh();
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current status message of this item.
|
||||
* @return the current status message of this item
|
||||
*/
|
||||
public String getStatusMessage() {
|
||||
if (statusMessage != null) {
|
||||
return statusMessage;
|
||||
}
|
||||
switch (status) {
|
||||
case DONE:
|
||||
return "Applied";
|
||||
case ERROR:
|
||||
return "Error";
|
||||
case NONE:
|
||||
return "Not Applied";
|
||||
case WARNING:
|
||||
return "Warning";
|
||||
case CHANGED:
|
||||
return "Target changed externally";
|
||||
case DELETED:
|
||||
return "Target no longer exists";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the status of this item
|
||||
* @param status the new {@link QuickFixStatus} for this item.
|
||||
*/
|
||||
public void setStatus(QuickFixStatus status) {
|
||||
setStatus(status, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets both the {@link QuickFixStatus} and the status message for this item. Typically, used
|
||||
* to indicate a warning or error.
|
||||
* @param status the new QuickFixStatus
|
||||
* @param message the status message associated with the new status.
|
||||
*/
|
||||
public void setStatus(QuickFixStatus status, String message) {
|
||||
this.status = status;
|
||||
this.statusMessage = message;
|
||||
}
|
||||
|
||||
public abstract ProgramLocation getProgramLocation();
|
||||
|
||||
public Map<String, String> getCustomToolTipData() {
|
||||
return null;
|
||||
// do nothing - for subclasses to put specific info into the tooltip
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current value of the item.
|
||||
* @return the current value of the item
|
||||
*/
|
||||
protected abstract String doGetCurrent();
|
||||
|
||||
/**
|
||||
* Executes the action.
|
||||
*/
|
||||
protected abstract void execute();
|
||||
|
||||
/**
|
||||
* QuickFix items can override this method if they want to do some special navigation when the
|
||||
* table selection changes or the user double clicks (or presses <return> key) to navigate.
|
||||
* @param services the tool service provider
|
||||
* @param fromSelectionChange true if this call was caused by the table selection changing
|
||||
* @return true if this item handles the navigation and false if the QuickFix did not
|
||||
* handle the navigation and the client should attempt to do generic navigation.
|
||||
*/
|
||||
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
private void updateStatus() {
|
||||
QuickFixStatus newStatus = computeStatus();
|
||||
if (newStatus != status) {
|
||||
setStatus(newStatus);
|
||||
statusChanged(status);
|
||||
}
|
||||
}
|
||||
|
||||
protected void statusChanged(QuickFixStatus newStatus) {
|
||||
// do nothing - used by subclasses
|
||||
}
|
||||
|
||||
private QuickFixStatus computeStatus() {
|
||||
if (current == null) {
|
||||
return QuickFixStatus.DELETED;
|
||||
}
|
||||
if (current.equals(original)) {
|
||||
return QuickFixStatus.NONE;
|
||||
}
|
||||
if (current.equals(replacement)) {
|
||||
return QuickFixStatus.DONE;
|
||||
}
|
||||
return QuickFixStatus.CHANGED;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.quickfix;
|
||||
|
||||
/**
|
||||
* Enum for the possible status values of a {@link QuickFix}.
|
||||
*/
|
||||
public enum QuickFixStatus {
|
||||
NONE, // The item is unapplied and is ready to be executed
|
||||
WARNING, // The item is unapplied and has an associated warning
|
||||
CHANGED, // The item is unapplied, but has changed from its original value
|
||||
DELETED, // The item's target program element no longer exists
|
||||
ERROR, // The item can't be applied. This may occur before or after it is applied
|
||||
DONE // The item has been applied
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.quickfix;
|
||||
|
||||
import java.awt.Component;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import docking.widgets.table.GTableCellRenderingData;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
|
||||
import resources.Icons;
|
||||
|
||||
/**
|
||||
* Renderer for the {@link QuickFixStatus} column
|
||||
*/
|
||||
public class QuickFixStatusRenderer extends AbstractGhidraColumnRenderer<QuickFixStatus> {
|
||||
|
||||
private static final Icon DONE_ICON = new GIcon("icon.base.plugin.quickfix.done");
|
||||
private static final Icon ERROR_ICON = Icons.ERROR_ICON;
|
||||
private static final Icon WARNING_ICON = Icons.WARNING_ICON;
|
||||
private static final Icon DELETE_ICON = Icons.DELETE_ICON;
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
|
||||
QuickFixStatus status = (QuickFixStatus) data.getValue();
|
||||
|
||||
Icon icon = getIcon(status);
|
||||
renderer.setIcon(icon);
|
||||
renderer.setText("");
|
||||
QuickFix rowObject = (QuickFix) data.getRowObject();
|
||||
renderer.setToolTipText(rowObject.getStatusMessage());
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
private Icon getIcon(QuickFixStatus status) {
|
||||
switch (status) {
|
||||
case DONE:
|
||||
return DONE_ICON;
|
||||
case WARNING:
|
||||
case CHANGED:
|
||||
return WARNING_ICON;
|
||||
case ERROR:
|
||||
return ERROR_ICON;
|
||||
case DELETED:
|
||||
return DELETE_ICON;
|
||||
case NONE:
|
||||
return null;
|
||||
default:
|
||||
throw new AssertException("Unexpected QuickFix status: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(QuickFixStatus t, Settings settings) {
|
||||
return t.toString();
|
||||
}
|
||||
|
||||
}
|
||||
+346
@@ -0,0 +1,346 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.quickfix;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.swing.JLabel;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.model.DomainObjectChangedEvent;
|
||||
import ghidra.framework.model.DomainObjectListener;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.table.GhidraProgramTableModel;
|
||||
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
|
||||
import ghidra.util.table.column.GColumnRenderer;
|
||||
import ghidra.util.task.SwingUpdateManager;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Table model for {@link QuickFix}s
|
||||
*/
|
||||
public class QuickFixTableModel extends GhidraProgramTableModel<QuickFix>
|
||||
implements DomainObjectListener {
|
||||
private TableDataLoader<QuickFix> tableLoader;
|
||||
private SwingUpdateManager updateManager = new SwingUpdateManager(1000, this::refreshItems);
|
||||
private QuickFixRenderer quickFixRenderer = new QuickFixRenderer();
|
||||
|
||||
protected QuickFixTableModel(Program program, String modelName, ServiceProvider serviceProvider,
|
||||
TableDataLoader<QuickFix> loader) {
|
||||
super(modelName, serviceProvider, program, null);
|
||||
this.tableLoader = loader;
|
||||
|
||||
program.addListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
updateManager.dispose();
|
||||
if (program != null) {
|
||||
program.removeListener(this);
|
||||
}
|
||||
program = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<QuickFix> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
tableLoader.loadData(accumulator, monitor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<QuickFix> createTableColumnDescriptor() {
|
||||
TableColumnDescriptor<QuickFix> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(new OriginalValueColumn(), 1, true);
|
||||
descriptor.addHiddenColumn(new CurrentValueColumn());
|
||||
descriptor.addVisibleColumn(new PreviewColumn());
|
||||
descriptor.addVisibleColumn(new ActionColumn());
|
||||
descriptor.addVisibleColumn(new TypeColumn());
|
||||
descriptor.addHiddenColumn(new AddressColumn());
|
||||
descriptor.addHiddenColumn(new PathColumn());
|
||||
descriptor.addVisibleColumn(new QuickFixStatusColumn());
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getProgramLocation(int modelRow, int modelColumn) {
|
||||
if (modelRow < 0 || modelRow >= filteredData.size()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
QuickFix quickFix = filteredData.get(modelRow);
|
||||
return quickFix.getProgramLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void domainObjectChanged(DomainObjectChangedEvent ev) {
|
||||
updateManager.update();
|
||||
}
|
||||
|
||||
private void refreshItems() {
|
||||
fireTableDataChanged();
|
||||
}
|
||||
|
||||
private class QuickFixStatusColumn
|
||||
extends AbstractDynamicTableColumnStub<QuickFix, QuickFixStatus> {
|
||||
|
||||
QuickFixStatusRenderer renderer = new QuickFixStatusRenderer();
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Status";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 25;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QuickFixStatus getValue(QuickFix rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<QuickFixStatus> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
|
||||
private class TypeColumn
|
||||
extends AbstractDynamicTableColumnStub<QuickFix, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Type";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 100;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(QuickFix rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getItemType();
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionColumn
|
||||
extends AbstractDynamicTableColumnStub<QuickFix, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Action";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 75;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(QuickFix rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getActionName();
|
||||
}
|
||||
}
|
||||
|
||||
private class CurrentValueColumn
|
||||
extends AbstractDynamicTableColumnStub<QuickFix, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Current";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(QuickFix rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getCurrent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<String> getColumnRenderer() {
|
||||
return quickFixRenderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class OriginalValueColumn
|
||||
extends AbstractDynamicTableColumnStub<QuickFix, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Original";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(QuickFix rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getOriginal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<String> getColumnRenderer() {
|
||||
return quickFixRenderer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class PreviewColumn
|
||||
extends AbstractDynamicTableColumnStub<QuickFix, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Preview";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(QuickFix rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getPreview();
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<String> getColumnRenderer() {
|
||||
return quickFixRenderer;
|
||||
}
|
||||
}
|
||||
|
||||
private class AddressColumn
|
||||
extends AbstractDynamicTableColumnStub<QuickFix, Address> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Address";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 50;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getValue(QuickFix rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private class PathColumn
|
||||
extends AbstractDynamicTableColumnStub<QuickFix, String> {
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Path";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 150;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue(QuickFix rowObject, Settings settings,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getPath();
|
||||
}
|
||||
}
|
||||
|
||||
public class QuickFixRenderer extends AbstractGhidraColumnRenderer<String> {
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
|
||||
|
||||
QuickFix item = (QuickFix) data.getRowObject();
|
||||
StringBuilder buf = new StringBuilder();
|
||||
buf.append("<HTML>");
|
||||
buf.append("<TABLE>");
|
||||
addHtmlTableRow(buf, "Action", item.getActionName() + " " + item.getItemType());
|
||||
|
||||
Address address = item.getAddress();
|
||||
if (address != null && address.isMemoryAddress()) {
|
||||
addHtmlTableRow(buf, "Address", address.toString());
|
||||
}
|
||||
addCustomTableRows(buf, item.getCustomToolTipData());
|
||||
|
||||
addHtmlTableRow(buf, "Original", item.getOriginal());
|
||||
addHtmlTableRow(buf, "Preview", item.getPreview());
|
||||
addHtmlTableRow(buf, "Current", item.getCurrent());
|
||||
buf.append("</TABLE></HTML>");
|
||||
|
||||
renderer.setToolTipText(buf.toString());
|
||||
|
||||
return renderer;
|
||||
}
|
||||
|
||||
private void addCustomTableRows(StringBuilder buf, Map<String, String> dataMap) {
|
||||
if (dataMap == null) {
|
||||
return;
|
||||
}
|
||||
for (Entry<String, String> entry : dataMap.entrySet()) {
|
||||
addHtmlTableRow(buf, entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private void addHtmlTableRow(StringBuilder buf, String name, String value) {
|
||||
buf.append("<TR><TD><B>");
|
||||
buf.append(HTMLUtilities.escapeHTML(name));
|
||||
buf.append(":</B></TD><TD>");
|
||||
buf.append(HTMLUtilities.escapeHTML(value));
|
||||
buf.append("</TD></TR>");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(String t, Settings settings) {
|
||||
return t;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.quickfix;
|
||||
|
||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Generates table data for a {@link ThreadedTableModel}. Subclasses
|
||||
* of ThreadedTableModel can call a TableLoader to supply data in the model's doLoad() method. Also
|
||||
* has methods for the client to get feedback on the success of the load.
|
||||
* <P>
|
||||
* The idea is that instead of having to subclass the table model to overload the doLoad() method,
|
||||
* a general table model is sufficient and be handed a TableDataLoader to provide data to the model.
|
||||
*
|
||||
* @param <T> The type of objects to load into a table model.
|
||||
*/
|
||||
public interface TableDataLoader<T> {
|
||||
|
||||
/**
|
||||
* Loads data into the given accumulator
|
||||
* @param accumulator the the accumulator for storing table data
|
||||
* @param monitor the {@link TaskMonitor}
|
||||
* @throws CancelledException if the operation is cancelled
|
||||
*/
|
||||
public void loadData(Accumulator<T> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException;
|
||||
|
||||
/**
|
||||
* Returns true if at least one item was added to the accumulator.
|
||||
* @return true if at least one item was added to the accumulator
|
||||
*/
|
||||
public boolean didProduceData();
|
||||
|
||||
/**
|
||||
* Returns true if the load was terminated because the maximum number of items was
|
||||
* reached.
|
||||
* @return true if the load was terminated because the maximum number of items was
|
||||
* reached.
|
||||
*/
|
||||
public boolean maxDataSizeReached();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.features.base.quickfix.QuickFixStatus;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* Base class for QuickFix objects that rename Ghidra program elements.
|
||||
*/
|
||||
public abstract class RenameQuickFix extends QuickFix {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param program the program this applies to
|
||||
* @param name the original name of the element to rename
|
||||
* @param newName the new name for the element when this QuickFix is applied.
|
||||
*/
|
||||
public RenameQuickFix(Program program, String name, String newName) {
|
||||
super(program, name, newName);
|
||||
validateReplacementName();
|
||||
}
|
||||
|
||||
protected void validateReplacementName() {
|
||||
if (replacement.isBlank()) {
|
||||
setStatus(QuickFixStatus.ERROR, "Can't rename to \"\"");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getActionName() {
|
||||
return "Rename";
|
||||
}
|
||||
|
||||
}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import docking.DialogComponentProvider;
|
||||
import docking.widgets.combobox.GhidraComboBox;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.layout.*;
|
||||
|
||||
/**
|
||||
* Dialog for entering information to perform a search and replace operation.
|
||||
*/
|
||||
public class SearchAndReplaceDialog extends DialogComponentProvider {
|
||||
|
||||
private static final int MAX_HISTORY = 20;
|
||||
private List<SearchType> allTypes;
|
||||
private List<JCheckBox> typeCheckboxes = new ArrayList<>();
|
||||
private SearchAndReplaceQuery query;
|
||||
private Set<SearchType> selectedTypes = new HashSet<>();
|
||||
private GhidraComboBox<String> searchTextComboBox;
|
||||
private GhidraComboBox<String> replaceTextComboBox;
|
||||
private JCheckBox regexCheckbox;
|
||||
private JCheckBox caseSensitiveCheckbox;
|
||||
private JCheckBox wholeWordCheckbox;
|
||||
private int searchLimit;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param searchLimit the maximum number of search matches to find before stopping.
|
||||
*/
|
||||
public SearchAndReplaceDialog(int searchLimit) {
|
||||
super("Search And Replace");
|
||||
this.searchLimit = searchLimit;
|
||||
this.allTypes = new ArrayList<>(SearchType.getSearchTypes());
|
||||
Collections.sort(allTypes);
|
||||
addWorkPanel(buildMainPanel());
|
||||
addOKButton();
|
||||
addCancelButton();
|
||||
updateDialogStatus();
|
||||
setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Search And Replace"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new maximum number of search matches to find before stopping.
|
||||
* @param searchLimit the new maximum number of search matches to find before stopping.
|
||||
*/
|
||||
public void setSearchLimit(int searchLimit) {
|
||||
this.searchLimit = searchLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for initializing the dialog, showing it and returning the query.
|
||||
* @param tool the tool this dialog belongs to
|
||||
* @return the SearchAndReplaceQuery generated by the information in the dialog when the
|
||||
* "Ok" button is pressed, or null if the dialog was cancelled.
|
||||
*/
|
||||
public SearchAndReplaceQuery show(PluginTool tool) {
|
||||
query = null;
|
||||
searchTextComboBox.getTextField().selectAll();
|
||||
replaceTextComboBox.setText("");
|
||||
updateDialogStatus();
|
||||
tool.showDialog(this);
|
||||
return query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the query generated by the dialog when the "Ok" button is pressed or null if the
|
||||
* dialog was cancelled.
|
||||
* @return the SearchAndReplaceQuery generated by the information in the dialog when the
|
||||
* "Ok" button is pressed, or null if the dialog was cancelled.
|
||||
*/
|
||||
public SearchAndReplaceQuery getQuery() {
|
||||
return query;
|
||||
}
|
||||
|
||||
private JComponent buildMainPanel() {
|
||||
JPanel panel = new JPanel(new VerticalLayout(20));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
|
||||
panel.add(buildPatternsPanel());
|
||||
panel.add(buildOptionsPanel());
|
||||
panel.add(buildTypesPanel());
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JComponent buildTypesPanel() {
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.setBorder(BorderFactory.createTitledBorder("Search For:"));
|
||||
panel.add(buildInnerTypesPanel(), BorderLayout.CENTER);
|
||||
panel.add(buildSelectAllPanel(), BorderLayout.SOUTH);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private JPanel buildInnerTypesPanel() {
|
||||
JPanel panel = new JPanel(new GridLayout(0, 3, 10, 5));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
for (SearchType type : allTypes) {
|
||||
JCheckBox cb = new JCheckBox(type.getName());
|
||||
typeCheckboxes.add(cb);
|
||||
cb.setToolTipText(type.getDescription());
|
||||
cb.addActionListener(e -> typeCheckBoxChanged(cb, type));
|
||||
panel.add(cb);
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Component buildSelectAllPanel() {
|
||||
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 30, 10));
|
||||
JButton selectAllButton = new JButton("Select All");
|
||||
JButton deselectAllButton = new JButton("Deselect All");
|
||||
selectAllButton.addActionListener(e -> selectAllTypes());
|
||||
deselectAllButton.addActionListener(e -> deselectAllTypes());
|
||||
panel.add(selectAllButton);
|
||||
panel.add(deselectAllButton);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void selectAllTypes() {
|
||||
for (JCheckBox checkBox : typeCheckboxes) {
|
||||
checkBox.setSelected(true);
|
||||
}
|
||||
selectedTypes.addAll(allTypes);
|
||||
updateDialogStatus();
|
||||
}
|
||||
|
||||
private void deselectAllTypes() {
|
||||
for (JCheckBox checkBox : typeCheckboxes) {
|
||||
checkBox.setSelected(false);
|
||||
}
|
||||
selectedTypes.clear();
|
||||
updateDialogStatus();
|
||||
}
|
||||
|
||||
private void typeCheckBoxChanged(JCheckBox cb, SearchType type) {
|
||||
if (cb.isSelected()) {
|
||||
selectedTypes.add(type);
|
||||
}
|
||||
else {
|
||||
selectedTypes.remove(type);
|
||||
}
|
||||
updateDialogStatus();
|
||||
}
|
||||
|
||||
private void updateDialogStatus() {
|
||||
boolean isValid = hasValidInformation();
|
||||
setOkEnabled(isValid);
|
||||
}
|
||||
|
||||
private boolean hasValidInformation() {
|
||||
clearStatusText();
|
||||
String searchText = searchTextComboBox.getText().trim();
|
||||
if (searchText.isBlank()) {
|
||||
setStatusText("Please enter search text");
|
||||
return false;
|
||||
}
|
||||
if (selectedTypes.isEmpty()) {
|
||||
setStatusText("Please select at least one \"search for\" item to search!");
|
||||
return false;
|
||||
}
|
||||
return createQuery() != null;
|
||||
}
|
||||
|
||||
private JComponent buildOptionsPanel() {
|
||||
regexCheckbox = new JCheckBox("Regular expression");
|
||||
caseSensitiveCheckbox = new JCheckBox("Case sensitive");
|
||||
wholeWordCheckbox = new JCheckBox("Whole word");
|
||||
|
||||
regexCheckbox.addActionListener(e -> regExCheckboxChanged());
|
||||
|
||||
regexCheckbox.setToolTipText("Interprets search and replace text as regular expressions");
|
||||
caseSensitiveCheckbox.setToolTipText("Determines if search text is case sensitive");
|
||||
wholeWordCheckbox.setToolTipText("Sets if the input pattern must match whole words. For" +
|
||||
" names, this means the entire name. For comments, it means matching entire words.");
|
||||
|
||||
JPanel panel = new JPanel(new HorizontalLayout(10));
|
||||
Border titleBorder = BorderFactory.createTitledBorder("Options:");
|
||||
Border innerBorder = BorderFactory.createEmptyBorder(5, 5, 5, 5);
|
||||
panel.setBorder(BorderFactory.createCompoundBorder(titleBorder, innerBorder));
|
||||
panel.add(regexCheckbox);
|
||||
panel.add(caseSensitiveCheckbox);
|
||||
panel.add(wholeWordCheckbox);
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void regExCheckboxChanged() {
|
||||
wholeWordCheckbox.setEnabled(!regexCheckbox.isSelected());
|
||||
updateDialogStatus();
|
||||
}
|
||||
|
||||
private Component buildPatternsPanel() {
|
||||
searchTextComboBox = new GhidraComboBox<>();
|
||||
searchTextComboBox.setEditable(true);
|
||||
searchTextComboBox.setToolTipText("Enter the text to search for");
|
||||
searchTextComboBox.getTextField()
|
||||
.getDocument()
|
||||
.addDocumentListener(new DocumentListener() {
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
updateDialogStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
updateDialogStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
updateDialogStatus();
|
||||
}
|
||||
});
|
||||
|
||||
replaceTextComboBox = new GhidraComboBox<>();
|
||||
replaceTextComboBox.setEditable(true);
|
||||
replaceTextComboBox.setToolTipText("Enter the text to replace matched search text");
|
||||
|
||||
searchTextComboBox.addActionListener(e -> pressOk());
|
||||
replaceTextComboBox.addActionListener(e -> pressOk());
|
||||
|
||||
JPanel panel = new JPanel(new PairLayout(10, 10));
|
||||
panel.add(new JLabel("Find:", SwingConstants.RIGHT));
|
||||
panel.add(searchTextComboBox);
|
||||
panel.add(new JLabel("Replace with:", SwingConstants.RIGHT));
|
||||
panel.add(replaceTextComboBox);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private void pressOk() {
|
||||
if (isOKEnabled()) {
|
||||
okCallback();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void okCallback() {
|
||||
query = createQuery();
|
||||
updateHistory(searchTextComboBox);
|
||||
updateHistory(replaceTextComboBox);
|
||||
closeDialog();
|
||||
}
|
||||
|
||||
private SearchAndReplaceQuery createQuery() {
|
||||
String searchText = searchTextComboBox.getText().trim();
|
||||
String replacePattern = replaceTextComboBox.getText().trim();
|
||||
boolean isRegEx = regexCheckbox.isSelected();
|
||||
boolean isCaseSensitive = caseSensitiveCheckbox.isSelected();
|
||||
boolean isWholeWord = wholeWordCheckbox.isSelected();
|
||||
Set<SearchType> searchTypes = getSelectedSearchTypes();
|
||||
try {
|
||||
return new SearchAndReplaceQuery(searchText, replacePattern, searchTypes, isRegEx,
|
||||
isCaseSensitive, isWholeWord, searchLimit);
|
||||
}
|
||||
catch (PatternSyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Set<SearchType> getSelectedSearchTypes() {
|
||||
Set<SearchType> set = new HashSet<>();
|
||||
for (SearchType type : allTypes) {
|
||||
if (selectedTypes.contains(type)) {
|
||||
set.add(type);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
}
|
||||
|
||||
private void updateHistory(GhidraComboBox<String> combo) {
|
||||
String value = combo.getText();
|
||||
if (value.isBlank()) {
|
||||
return;
|
||||
}
|
||||
DefaultComboBoxModel<String> model =
|
||||
(DefaultComboBoxModel<String>) combo.getModel();
|
||||
model.removeElement(value);
|
||||
if (model.getSize() > MAX_HISTORY) {
|
||||
model.removeElementAt(model.getSize() - 1);
|
||||
}
|
||||
model.insertElementAt(value, 0);
|
||||
model.setSelectedItem(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the search and replace text fields with given values.
|
||||
* @param searchText the text to be put in the search field
|
||||
* @param replaceText the text to be put in the replace field
|
||||
*/
|
||||
public void setSarchAndReplaceText(String searchText, String replaceText) {
|
||||
searchTextComboBox.setText(searchText);
|
||||
replaceTextComboBox.setText(replaceText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the search type with the given name to be selected.
|
||||
* @param searchType the name of the search type to select
|
||||
*/
|
||||
public void selectSearchType(String searchType) {
|
||||
for (JCheckBox checkBox : typeCheckboxes) {
|
||||
if (checkBox.getText().equals(searchType)) {
|
||||
checkBox.setSelected(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (SearchType type : allTypes) {
|
||||
if (type.getName().equals(searchType)) {
|
||||
selectedTypes.add(type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
updateDialogStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the "ok" button is enabled.
|
||||
* @return true if the "ok" button is enabled.
|
||||
*/
|
||||
public boolean isOkEnabled() {
|
||||
return super.isOKEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the RegEx checkbox in the dialog.
|
||||
* @param b true to select RegEx, false to turn deselect it
|
||||
*/
|
||||
public void selectRegEx(boolean b) {
|
||||
regexCheckbox.setSelected(b);
|
||||
updateDialogStatus();
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Base class for discoverable SearchAndReplaceHandlers. A SearchAndReplaceHandler is responsible
|
||||
* for searching one or more specific program elements (referred to as {@link SearchType}) for a
|
||||
* given search pattern and generating the appropriate {@link QuickFix}.
|
||||
* <P>
|
||||
* Typically, one handler will handle related search elements for efficiency. For example, the
|
||||
* DataTypesSearchAndReplaceHandler is responsible for datatype names, field names, field comments,
|
||||
* etc. The idea is to only loop through all the datatypes once, regardless of what aspect of a
|
||||
* datatype you are searching for.
|
||||
*/
|
||||
public abstract class SearchAndReplaceHandler implements ExtensionPoint {
|
||||
private Set<SearchType> types = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Method to perform the search for the pattern and options as specified by the given
|
||||
* SearchAndReplaceQuery. As matches are found, appropriate {@link QuickFix}s are added to
|
||||
* the given accumulator.
|
||||
* @param program the program being searched
|
||||
* @param query contains the search pattern, replacement pattern, and options related to the
|
||||
* query.
|
||||
* @param accumulator the accumulator that resulting QuickFix items are added to as they are
|
||||
* found.
|
||||
* @param monitor a {@link TaskMonitor} for reporting progress and checking if the search has
|
||||
* been cancelled.
|
||||
* @throws CancelledException thrown if the operation has been cancelled via the taskmonitor
|
||||
*/
|
||||
public abstract void findAll(Program program, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException;
|
||||
|
||||
/**
|
||||
* Returns the set of {@link SearchType}s this handler supports.
|
||||
* @return the set of {@link SearchType}s this handler supports.
|
||||
*/
|
||||
public Set<SearchType> getSearchAndReplaceTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
protected void addType(SearchType type) {
|
||||
types.add(type);
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace;
|
||||
|
||||
import static ghidra.app.util.SearchConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.NavigatableActionContext;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.app.services.GoToService;
|
||||
import ghidra.app.services.ProgramManager;
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.app.util.SearchConstants;
|
||||
import ghidra.framework.options.OptionsChangeListener;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.PluginInfo;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.bean.opteditor.OptionsVetoException;
|
||||
|
||||
/**
|
||||
* Plugin to perform search and replace operations for many different program element types such
|
||||
* as labels, functions, classes, datatypes, memory blocks, and more.
|
||||
*/
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.SEARCH,
|
||||
shortDescription = "Search and replace text on program element names or comments.",
|
||||
description = "This plugin provides a search and replace capability for a variety of" +
|
||||
"program elements including functions, classes, namespaces, datatypes, field names, and" +
|
||||
"other.",
|
||||
servicesRequired = { ProgramManager.class, GoToService.class }
|
||||
)
|
||||
//@formatter:on
|
||||
public class SearchAndReplacePlugin extends ProgramPlugin {
|
||||
|
||||
private SearchAndReplaceDialog cachedDialog;
|
||||
private int searchLimit = DEFAULT_SEARCH_LIMIT;
|
||||
private OptionsChangeListener searchOptionsListener;
|
||||
private List<SearchAndReplaceProvider> providers = new ArrayList<>();
|
||||
|
||||
public SearchAndReplacePlugin(PluginTool plugintool) {
|
||||
super(plugintool);
|
||||
createActions();
|
||||
initializeOptions();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void programClosed(Program program) {
|
||||
List<SearchAndReplaceProvider> copy = new ArrayList<>(providers);
|
||||
for (SearchAndReplaceProvider provider : copy) {
|
||||
provider.programClosed(program);
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeOptions() {
|
||||
ToolOptions options = tool.getOptions(SearchConstants.SEARCH_OPTION_NAME);
|
||||
searchLimit = options.getInt(SEARCH_LIMIT_NAME, DEFAULT_SEARCH_LIMIT);
|
||||
|
||||
searchOptionsListener = this::searchOptionsChanged;
|
||||
options.addOptionsChangeListener(searchOptionsListener);
|
||||
}
|
||||
|
||||
private void searchOptionsChanged(ToolOptions options, String optionName, Object oldValue,
|
||||
Object newValue) {
|
||||
|
||||
if (optionName.equals(SEARCH_LIMIT_NAME)) {
|
||||
int limit = (int) newValue;
|
||||
if (limit <= 0) {
|
||||
throw new OptionsVetoException("Search limit must be greater than 0");
|
||||
}
|
||||
searchLimit = limit;
|
||||
if (cachedDialog != null) {
|
||||
cachedDialog.setSearchLimit(limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
new ActionBuilder("Search And Replace", getName())
|
||||
.menuPath("&Search", "Search And Replace...")
|
||||
.menuGroup("search", "d")
|
||||
.description("Search and replace names of various program elements")
|
||||
.helpLocation(new HelpLocation(HelpTopics.SEARCH, "Search And Replace"))
|
||||
.withContext(NavigatableActionContext.class, true)
|
||||
.onAction(this::searchAndReplace)
|
||||
.buildAndInstall(tool);
|
||||
}
|
||||
|
||||
private void searchAndReplace(NavigatableActionContext c) {
|
||||
SearchAndReplaceDialog dialog = getDialog();
|
||||
SearchAndReplaceQuery query = dialog.show(tool);
|
||||
if (query == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Program program = c.getProgram();
|
||||
providers.add(new SearchAndReplaceProvider(this, program, query));
|
||||
}
|
||||
|
||||
private SearchAndReplaceDialog getDialog() {
|
||||
if (cachedDialog == null) {
|
||||
cachedDialog = new SearchAndReplaceDialog(searchLimit);
|
||||
}
|
||||
return cachedDialog;
|
||||
}
|
||||
|
||||
void providerClosed(SearchAndReplaceProvider provider) {
|
||||
providers.remove(provider);
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
import ghidra.app.util.HelpTopics;
|
||||
import ghidra.features.base.quickfix.*;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Subclass of the {@link QuckFixTableProvider} that customizes it specifically for search and replace
|
||||
* operations.
|
||||
*/
|
||||
public class SearchAndReplaceProvider extends QuckFixTableProvider {
|
||||
|
||||
private SearchAndReplacePlugin plugin;
|
||||
private SearchAndReplaceQuery query;
|
||||
|
||||
public SearchAndReplaceProvider(SearchAndReplacePlugin plugin, Program program,
|
||||
SearchAndReplaceQuery query) {
|
||||
|
||||
super(plugin.getTool(), "Search And Replace", plugin.getName(), program,
|
||||
new SearchAndReplaceQuckFixTableLoader(program, query));
|
||||
this.plugin = plugin;
|
||||
this.query = query;
|
||||
setTitle(generateTitle());
|
||||
setTabText(getTabTitle());
|
||||
addToTool();
|
||||
setHelpLocation(new HelpLocation(HelpTopics.SEARCH, "Search_And_Replace_Results"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tableLoaded(boolean wasCancelled, TableDataLoader<QuickFix> loader) {
|
||||
if (!loader.didProduceData()) {
|
||||
Msg.showInfo(getClass(), getComponent(), "No Results Found!",
|
||||
"No results for \"" + query.getSearchText() + "\" found.");
|
||||
closeComponent();
|
||||
return;
|
||||
}
|
||||
setVisible(true);
|
||||
if (loader.maxDataSizeReached()) {
|
||||
Msg.showInfo(getClass(), getComponent(), "Search Limit Exceeded!",
|
||||
"Stopped search after finding " + query.getSearchLimit() + " matches.\n" +
|
||||
"The search limit can be changed at Edit->Tool Options, under Search.");
|
||||
}
|
||||
toFront();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeComponent() {
|
||||
super.closeComponent();
|
||||
plugin.providerClosed(this);
|
||||
}
|
||||
|
||||
private String getTabTitle() {
|
||||
return "\"" + query.getSearchText() + "\" -> \"" +
|
||||
query.getReplacementText() + "\"";
|
||||
}
|
||||
|
||||
private String generateTitle() {
|
||||
return "Search & Replace: " + getTabTitle();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JPanel buildMainPanel() {
|
||||
JPanel quickFixPanel = super.buildMainPanel();
|
||||
|
||||
JPanel panel = new JPanel(new BorderLayout());
|
||||
panel.add(quickFixPanel, BorderLayout.CENTER);
|
||||
panel.add(buildButtonPanel(), BorderLayout.SOUTH);
|
||||
|
||||
return panel;
|
||||
}
|
||||
|
||||
private Component buildButtonPanel() {
|
||||
JButton replaceButton = new JButton("Replace All");
|
||||
JButton dismissButton = new JButton("Dismiss");
|
||||
replaceButton.addActionListener(e -> executeAll());
|
||||
dismissButton.addActionListener(e -> closeComponent());
|
||||
|
||||
JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 0));
|
||||
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
|
||||
panel.add(replaceButton);
|
||||
panel.add(dismissButton);
|
||||
return panel;
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.features.base.quickfix.TableDataLoader;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.datastruct.*;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Class for loading search and replace items into a ThreadedTableModel.
|
||||
*/
|
||||
public class SearchAndReplaceQuckFixTableLoader implements TableDataLoader<QuickFix> {
|
||||
|
||||
private SearchAndReplaceQuery query;
|
||||
private Program program;
|
||||
private boolean hasResults;
|
||||
private boolean searchLimitExceeded;
|
||||
|
||||
public SearchAndReplaceQuckFixTableLoader(Program program, SearchAndReplaceQuery query) {
|
||||
this.program = program;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData(Accumulator<QuickFix> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
Accumulator<QuickFix> wrappedAccumulator =
|
||||
new SizeRestrictedAccumulatorWrapper<QuickFix>(accumulator, query.getSearchLimit());
|
||||
try {
|
||||
query.findAll(program, wrappedAccumulator, monitor);
|
||||
}
|
||||
catch (AccumulatorSizeException e) {
|
||||
searchLimitExceeded = true;
|
||||
}
|
||||
finally {
|
||||
hasResults = !accumulator.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean didProduceData() {
|
||||
return hasResults;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean maxDataSizeReached() {
|
||||
return searchLimitExceeded;
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.UserSearchUtils;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Immutable class for storing all related query information for performing a search and
|
||||
* replace operation. It includes the search pattern, the search pattern text, the search lmiit,
|
||||
* and the types of program elements to search.
|
||||
*/
|
||||
public class SearchAndReplaceQuery {
|
||||
private final String searchText;
|
||||
private final String replacementText;
|
||||
private final Pattern pattern;
|
||||
private final int searchLimit;
|
||||
private final Set<SearchType> selectedTypes = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param searchText the user entered search pattern text. It will be used to generate the
|
||||
* actual Pattern based on the various options.
|
||||
* @param replacementText the user entered replacement text.
|
||||
* @param searchTypes the types of program elements to search
|
||||
* @param isRegEx true if the given search text is to be interpreted as a regular expression.
|
||||
* @param isCaseSensitive true if the search text should be case sensitive
|
||||
* @param isWholeWord true, the search text should match the enter element in the case of a
|
||||
* rename, or an entire word within a larger sentence in the case of a comment.
|
||||
* @param searchLimit the maximum entries to find before terminating the search.
|
||||
*/
|
||||
public SearchAndReplaceQuery(String searchText, String replacementText,
|
||||
Set<SearchType> searchTypes, boolean isRegEx, boolean isCaseSensitive,
|
||||
boolean isWholeWord, int searchLimit) {
|
||||
this.searchText = searchText;
|
||||
this.replacementText = replacementText;
|
||||
this.pattern = createPattern(isRegEx, isCaseSensitive, isWholeWord);
|
||||
this.searchLimit = searchLimit;
|
||||
selectedTypes.addAll(searchTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to initiate the search.
|
||||
* @param program the program to search
|
||||
* @param accumulator the accumulator to store the generated {@link QuickFix}s
|
||||
* @param monitor the {@link TaskMonitor}
|
||||
* @throws CancelledException if the search is cancelled.
|
||||
*/
|
||||
public void findAll(Program program, Accumulator<QuickFix> accumulator,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
Set<SearchAndReplaceHandler> handlers = getHandlers();
|
||||
for (SearchAndReplaceHandler handler : handlers) {
|
||||
handler.findAll(program, this, accumulator, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search {@link Pattern} used to search program elements.
|
||||
* @return the search {@link Pattern} used to search program elements
|
||||
*/
|
||||
public Pattern getSearchPattern() {
|
||||
return pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the given SearchType is to be included in the search.
|
||||
* @param searchType the SearchType to check if it is included in the search
|
||||
* @return true if the given SearchType is to be included in the search.
|
||||
*/
|
||||
public boolean containsSearchType(SearchType searchType) {
|
||||
return selectedTypes.contains(searchType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the search text used to generate the pattern for this query.
|
||||
* @return the search text used to generate the pattern for this query
|
||||
*/
|
||||
public String getSearchText() {
|
||||
return searchText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the replacement text that will replace matched elements.
|
||||
* @return the replacement text that will replace matched elements
|
||||
*/
|
||||
public String getReplacementText() {
|
||||
return replacementText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a set of all the SearchTypes to be included in this query.
|
||||
* @return a set of all the SearchTypes to be included in this query
|
||||
*/
|
||||
public Set<SearchType> getSelectedSearchTypes() {
|
||||
return selectedTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum number of search matches to be found before stopping early.
|
||||
* @return the maximum number of search matches to be found before stopping early.
|
||||
*/
|
||||
public int getSearchLimit() {
|
||||
return searchLimit;
|
||||
}
|
||||
|
||||
private Pattern createPattern(boolean isRegEx, boolean isCaseSensitive, boolean isWholeWord) {
|
||||
int regExFlags = Pattern.DOTALL;
|
||||
if (!isCaseSensitive) {
|
||||
regExFlags |= Pattern.CASE_INSENSITIVE;
|
||||
}
|
||||
|
||||
if (isRegEx) {
|
||||
return Pattern.compile(searchText, regExFlags);
|
||||
}
|
||||
|
||||
String converted = UserSearchUtils.convertUserInputToRegex(searchText, false);
|
||||
if (isWholeWord) {
|
||||
converted = "\\b" + converted + "\\b";
|
||||
}
|
||||
|
||||
return Pattern.compile(converted, regExFlags);
|
||||
}
|
||||
|
||||
private Set<SearchAndReplaceHandler> getHandlers() {
|
||||
Set<SearchAndReplaceHandler> handlers = new HashSet<>();
|
||||
for (SearchType type : selectedTypes) {
|
||||
handlers.add(type.getHandler());
|
||||
}
|
||||
return handlers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import ghidra.util.classfinder.ClassSearcher;
|
||||
|
||||
/**
|
||||
* Represents a ghidra program element type that can be individually included or excluded when doing
|
||||
* a search and replace operation. The {@link SearchAndReplaceDialog} will include a checkbox for
|
||||
* each of these types.
|
||||
*/
|
||||
public class SearchType implements Comparable<SearchType> {
|
||||
private final SearchAndReplaceHandler handler;
|
||||
private final String name;
|
||||
private final String description;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param handler The {@link SearchAndReplaceHandler} that actually has the logic for doing
|
||||
* the search for this program element type.
|
||||
* @param name the name of element type that is searchable
|
||||
* @param description a description of this type which would be suitable to display as a tooltip
|
||||
*/
|
||||
public SearchType(SearchAndReplaceHandler handler, String name, String description) {
|
||||
this.handler = handler;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link SearchAndReplaceHandler} that can process this type.
|
||||
* @return the handler for processing this type
|
||||
*/
|
||||
public SearchAndReplaceHandler getHandler() {
|
||||
return handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of this search type.
|
||||
* @return the name of this search type
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a description of this search type.
|
||||
* @return a description of this search type
|
||||
*/
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static convenience method for finding all known SearchTypes. It uses the
|
||||
* {@link ClassSearcher} to find all {@link SearchAndReplaceHandler}s and then gathers up
|
||||
* all the SearchTypes that each handler supports.
|
||||
*
|
||||
* @return The set of all Known SearchTypes
|
||||
*/
|
||||
public static Set<SearchType> getSearchTypes() {
|
||||
List<SearchAndReplaceHandler> handlers =
|
||||
ClassSearcher.getInstances(SearchAndReplaceHandler.class);
|
||||
|
||||
Set<SearchType> types = new HashSet<>();
|
||||
|
||||
for (SearchAndReplaceHandler handler : handlers) {
|
||||
types.addAll(handler.getSearchAndReplaceTypes());
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SearchType o) {
|
||||
return name.compareTo(o.name);
|
||||
}
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace.handler;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.features.base.replace.*;
|
||||
import ghidra.features.base.replace.items.*;
|
||||
import ghidra.program.model.data.*;
|
||||
import ghidra.program.model.data.Enum;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* {@link SearchAndReplaceHandler} for handling search and replace for datatype names,
|
||||
* structure and union field names, structure and union field comments, enum value names,
|
||||
* and enum value comments.
|
||||
*/
|
||||
public class DataTypesSearchAndReplaceHandler extends SearchAndReplaceHandler {
|
||||
DataTypeSearchType nameType;
|
||||
DataTypeSearchType datatypeCommentsType;
|
||||
DataTypeSearchType fieldNameType;
|
||||
DataTypeSearchType enumValueNameType;
|
||||
|
||||
public DataTypesSearchAndReplaceHandler() {
|
||||
nameType = new NameSearchType(this);
|
||||
datatypeCommentsType = new DataTypeCommentsSearchType(this);
|
||||
fieldNameType = new FieldNameSearchType(this);
|
||||
enumValueNameType = new EnumValueSearchType(this);
|
||||
|
||||
addType(nameType);
|
||||
addType(datatypeCommentsType);
|
||||
addType(fieldNameType);
|
||||
addType(enumValueNameType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findAll(Program program, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
ProgramBasedDataTypeManager dataTypeManager = program.getDataTypeManager();
|
||||
List<DataType> allDataTypes = new ArrayList<>();
|
||||
dataTypeManager.getAllDataTypes(allDataTypes);
|
||||
|
||||
monitor.initialize(allDataTypes.size(), "Searching DataTypes...");
|
||||
|
||||
boolean doNames = query.containsSearchType(nameType);
|
||||
boolean doDatatypeComments = query.containsSearchType(datatypeCommentsType);
|
||||
boolean doFieldNames = query.containsSearchType(fieldNameType);
|
||||
boolean doEnumValueNames = query.containsSearchType(enumValueNameType);
|
||||
|
||||
for (DataType dataType : allDataTypes) {
|
||||
monitor.increment();
|
||||
if (dataType instanceof Pointer || dataType instanceof Array) {
|
||||
continue;
|
||||
}
|
||||
if (doNames) {
|
||||
nameType.search(program, dataType, query, accumulator);
|
||||
}
|
||||
if (doDatatypeComments) {
|
||||
datatypeCommentsType.search(program, dataType, query, accumulator);
|
||||
}
|
||||
if (doFieldNames) {
|
||||
fieldNameType.search(program, dataType, query, accumulator);
|
||||
}
|
||||
if (doEnumValueNames) {
|
||||
enumValueNameType.search(program, dataType, query, accumulator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract static class DataTypeSearchType extends SearchType {
|
||||
public DataTypeSearchType(SearchAndReplaceHandler handler, String name,
|
||||
String description) {
|
||||
super(handler, name, description);
|
||||
}
|
||||
|
||||
protected abstract void search(Program program, DataType dataType,
|
||||
SearchAndReplaceQuery query, Accumulator<QuickFix> accumulator);
|
||||
|
||||
}
|
||||
|
||||
private static class NameSearchType extends DataTypeSearchType {
|
||||
public NameSearchType(SearchAndReplaceHandler handler) {
|
||||
super(handler, "Datatypes", "Search and replace datatype names");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void search(Program program, DataType dataType, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator) {
|
||||
|
||||
Pattern searchPattern = query.getSearchPattern();
|
||||
Matcher matcher = searchPattern.matcher(dataType.getName());
|
||||
if (matcher.find()) {
|
||||
String newName = matcher.replaceAll(query.getReplacementText());
|
||||
RenameDataTypeQuickFix item =
|
||||
new RenameDataTypeQuickFix(program, dataType, newName);
|
||||
accumulator.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class FieldNameSearchType extends DataTypeSearchType {
|
||||
public FieldNameSearchType(SearchAndReplaceHandler handler) {
|
||||
super(handler, "Datatype Fields",
|
||||
"Search and replace structure and union member names");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void search(Program program, DataType dataType, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator) {
|
||||
|
||||
if (!(dataType instanceof Composite composite)) {
|
||||
return;
|
||||
}
|
||||
DataTypeComponent[] definedComponents = composite.getDefinedComponents();
|
||||
Pattern searchPattern = query.getSearchPattern();
|
||||
|
||||
for (int i = 0; i < definedComponents.length; i++) {
|
||||
DataTypeComponent component = definedComponents[i];
|
||||
String name = getFieldName(component);
|
||||
Matcher matcher = searchPattern.matcher(name);
|
||||
if (matcher.find()) {
|
||||
String newName = matcher.replaceAll(query.getReplacementText());
|
||||
int ordinal = component.getOrdinal();
|
||||
QuickFix item =
|
||||
new RenameFieldQuickFix(program, composite, ordinal, name, newName);
|
||||
accumulator.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String getFieldName(DataTypeComponent component) {
|
||||
String fieldName = component.getFieldName();
|
||||
return fieldName == null ? component.getDefaultFieldName() : fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
private static class DataTypeCommentsSearchType extends DataTypeSearchType {
|
||||
public DataTypeCommentsSearchType(SearchAndReplaceHandler handler) {
|
||||
super(handler, "Datatype Comments", "Search and replace comments on datatypes");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void search(Program program, DataType dataType, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator) {
|
||||
searchDescriptions(program, dataType, query, accumulator);
|
||||
|
||||
if (dataType instanceof Composite composite) {
|
||||
searchFieldComments(program, composite, query, accumulator);
|
||||
}
|
||||
else if (dataType instanceof Enum enumm) {
|
||||
searchEnumComments(program, enumm, query, accumulator);
|
||||
}
|
||||
}
|
||||
|
||||
private void searchEnumComments(Program program, Enum enumm, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator) {
|
||||
String[] names = enumm.getNames();
|
||||
Pattern searchPattern = query.getSearchPattern();
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
String valueName = names[i];
|
||||
String comment = enumm.getComment(valueName);
|
||||
Matcher matcher = searchPattern.matcher(comment);
|
||||
if (matcher.find()) {
|
||||
String newValueName = matcher.replaceAll(query.getReplacementText());
|
||||
QuickFix item =
|
||||
new UpdateEnumCommentQuickFix(program, enumm, valueName, newValueName);
|
||||
accumulator.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void searchFieldComments(Program program, Composite composite,
|
||||
SearchAndReplaceQuery query, Accumulator<QuickFix> accumulator) {
|
||||
|
||||
DataTypeComponent[] definedComponents = composite.getDefinedComponents();
|
||||
Pattern searchPattern = query.getSearchPattern();
|
||||
|
||||
for (int i = 0; i < definedComponents.length; i++) {
|
||||
DataTypeComponent component = definedComponents[i];
|
||||
String comment = component.getComment();
|
||||
if (comment == null) {
|
||||
continue;
|
||||
}
|
||||
Matcher matcher = searchPattern.matcher(comment);
|
||||
if (matcher.find()) {
|
||||
String newComment = matcher.replaceAll(query.getReplacementText());
|
||||
QuickFix item =
|
||||
new UpdateFieldCommentQuickFix(program, composite, component.getFieldName(),
|
||||
component.getOrdinal(), comment, newComment);
|
||||
accumulator.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void searchDescriptions(Program program, DataType dataType,
|
||||
SearchAndReplaceQuery query, Accumulator<QuickFix> accumulator) {
|
||||
|
||||
String description = getDescription(dataType);
|
||||
if (description == null || description.isBlank()) {
|
||||
return;
|
||||
}
|
||||
Pattern searchPattern = query.getSearchPattern();
|
||||
Matcher matcher = searchPattern.matcher(description);
|
||||
if (matcher.find()) {
|
||||
String newName = matcher.replaceAll(query.getReplacementText());
|
||||
UpdateDataTypeDescriptionQuickFix item =
|
||||
new UpdateDataTypeDescriptionQuickFix(program, dataType, newName);
|
||||
accumulator.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
private String getDescription(DataType dataType) {
|
||||
if (dataType instanceof Composite composite) {
|
||||
return composite.getDescription();
|
||||
}
|
||||
if (dataType instanceof Enum enumDataType) {
|
||||
return enumDataType.getDescription();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class EnumValueSearchType extends DataTypeSearchType {
|
||||
public EnumValueSearchType(SearchAndReplaceHandler handler) {
|
||||
super(handler, "Enum Values", "Search and replace enum value names");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void search(Program program, DataType dataType, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator) {
|
||||
|
||||
if (!(dataType instanceof Enum enumm)) {
|
||||
return;
|
||||
}
|
||||
String[] names = enumm.getNames();
|
||||
Pattern searchPattern = query.getSearchPattern();
|
||||
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
String valueName = names[i];
|
||||
Matcher matcher = searchPattern.matcher(valueName);
|
||||
if (matcher.find()) {
|
||||
String newValueName = matcher.replaceAll(query.getReplacementText());
|
||||
QuickFix item =
|
||||
new RenameEnumValueQuickFix(program, enumm, valueName, newValueName);
|
||||
accumulator.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace.handler;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.features.base.replace.*;
|
||||
import ghidra.features.base.replace.items.RenameCategoryQuickFix;
|
||||
import ghidra.program.model.data.Category;
|
||||
import ghidra.program.model.data.DataTypeManager;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utility.function.ExceptionalConsumer;
|
||||
|
||||
/**
|
||||
* {@link SearchAndReplaceHandler} for handling search and replace for datatype category names.
|
||||
*/
|
||||
public class DatatypeCategorySearchAndReplaceHandler extends SearchAndReplaceHandler {
|
||||
|
||||
public DatatypeCategorySearchAndReplaceHandler() {
|
||||
addType(new SearchType(this, "Datatype Categories",
|
||||
"Search and replace datatype category names"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findAll(Program program, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
int categoryCount = program.getDataTypeManager().getCategoryCount();
|
||||
monitor.initialize(categoryCount, "Searching Datatype categories...");
|
||||
|
||||
Pattern pattern = query.getSearchPattern();
|
||||
|
||||
DataTypeManager dtm = program.getDataTypeManager();
|
||||
Category rootCategory = dtm.getRootCategory();
|
||||
|
||||
visitRecursively(rootCategory, category -> {
|
||||
monitor.increment();
|
||||
Matcher matcher = pattern.matcher(category.getName());
|
||||
if (matcher.find()) {
|
||||
String newName = matcher.replaceAll(query.getReplacementText());
|
||||
RenameCategoryQuickFix item = new RenameCategoryQuickFix(program, category, newName);
|
||||
accumulator.add(item);
|
||||
if (accumulator.size() >= query.getSearchLimit()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void visitRecursively(Category category,
|
||||
ExceptionalConsumer<Category, CancelledException> callback) throws CancelledException {
|
||||
|
||||
callback.accept(category);
|
||||
Category[] categories = category.getCategories();
|
||||
for (Category childCategory : categories) {
|
||||
visitRecursively(childCategory, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace.handler;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.features.base.replace.*;
|
||||
import ghidra.features.base.replace.items.UpdateCommentQuickFix;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* {@link SearchAndReplaceHandler} for handling search and replace for listing comments on
|
||||
* instructions or data.
|
||||
*/
|
||||
public class ListingCommentsSearchAndReplaceHandler extends SearchAndReplaceHandler {
|
||||
|
||||
public ListingCommentsSearchAndReplaceHandler() {
|
||||
addType(new SearchType(this, "Comments", "Search and replace in listing comments"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findAll(Program program, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
Listing listing = program.getListing();
|
||||
long count = listing.getCommentAddressCount();
|
||||
monitor.initialize(count, "Searching Comments...");
|
||||
|
||||
Pattern pattern = query.getSearchPattern();
|
||||
String replaceMentText = query.getReplacementText();
|
||||
|
||||
for (Address address : listing.getCommentAddressIterator(program.getMemory(), true)) {
|
||||
monitor.checkCancelled();
|
||||
CodeUnitComments comments = listing.getAllComments(address);
|
||||
for (CommentType type : CommentType.values()) {
|
||||
String comment = comments.getComment(type);
|
||||
String newComment = checkMatch(pattern, comment, replaceMentText);
|
||||
if (newComment != null) {
|
||||
accumulator.add(
|
||||
new UpdateCommentQuickFix(program, address, type, comment, newComment));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String checkMatch(Pattern pattern, String comment, String replacementText) {
|
||||
if (comment == null) {
|
||||
return null;
|
||||
}
|
||||
Matcher matcher = pattern.matcher(comment);
|
||||
if (matcher.find()) {
|
||||
return matcher.replaceAll(replacementText);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace.handler;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.features.base.replace.*;
|
||||
import ghidra.features.base.replace.items.RenameMemoryBlockQuickFix;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.mem.MemoryBlock;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* {@link SearchAndReplaceHandler} for handling search and replace for memory block names.
|
||||
*/
|
||||
|
||||
public class MemoryBlockSearchAndReplaceHandler extends SearchAndReplaceHandler {
|
||||
|
||||
public MemoryBlockSearchAndReplaceHandler() {
|
||||
addType(new SearchType(this, "Memory Blocks", "Search and replace memory block names"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findAll(Program program, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
Memory memory = program.getMemory();
|
||||
MemoryBlock[] blocks = memory.getBlocks();
|
||||
monitor.initialize(blocks.length, "Searching MemoryBlocks...");
|
||||
|
||||
Pattern pattern = query.getSearchPattern();
|
||||
|
||||
for (MemoryBlock block : blocks) {
|
||||
monitor.increment();
|
||||
Matcher matcher = pattern.matcher(block.getName());
|
||||
if (matcher.find()) {
|
||||
String newName = matcher.replaceAll(query.getReplacementText());
|
||||
RenameMemoryBlockQuickFix item =
|
||||
new RenameMemoryBlockQuickFix(program, block, newName);
|
||||
accumulator.add(item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace.handler;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.features.base.replace.*;
|
||||
import ghidra.features.base.replace.items.RenameProgramTreeGroupQuickFix;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* {@link SearchAndReplaceHandler} for handling search and replace for program tree modules and
|
||||
* fragments.
|
||||
*/
|
||||
|
||||
public class ProgramTreeSearchAndReplaceHandler extends SearchAndReplaceHandler {
|
||||
|
||||
public ProgramTreeSearchAndReplaceHandler() {
|
||||
addType(new SearchType(this, "Program Trees",
|
||||
"Search and replace program tree module and fragment names"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findAll(Program program, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
Listing listing = program.getListing();
|
||||
String[] treeNames = listing.getTreeNames();
|
||||
monitor.initialize(treeNames.length, "Search Program Trees");
|
||||
for (String treeName : treeNames) {
|
||||
monitor.increment();
|
||||
findAll(program, treeName, query, accumulator, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
private void findAll(Program program, String treeName, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
Set<Group> set = gatherProgramTreeGroups(program, treeName, monitor);
|
||||
Pattern pattern = query.getSearchPattern();
|
||||
|
||||
/**
|
||||
* Check all the modules and fragments in the tree
|
||||
*/
|
||||
for (Group group : set) {
|
||||
String name = group.getName();
|
||||
Matcher matcher = pattern.matcher(name);
|
||||
if (matcher.find()) {
|
||||
String newName = matcher.replaceAll(query.getReplacementText());
|
||||
QuickFix item = new RenameProgramTreeGroupQuickFix(program, group, newName);
|
||||
accumulator.add(item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Set<Group> gatherProgramTreeGroups(Program program, String treeName,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
monitor.checkCancelled();
|
||||
Listing listing = program.getListing();
|
||||
ProgramModule rootModule = listing.getRootModule(treeName);
|
||||
|
||||
Set<Group> set = new HashSet<>();
|
||||
addProgramTreeGroupsRecursively(set, rootModule, monitor);
|
||||
|
||||
// The root module name is the name of the program. Don't allow to change it here.
|
||||
set.remove(rootModule);
|
||||
return set;
|
||||
}
|
||||
|
||||
private void addProgramTreeGroupsRecursively(Set<Group> set, Group group, TaskMonitor monitor) {
|
||||
set.add(group);
|
||||
if (group instanceof ProgramModule module) {
|
||||
Group[] children = module.getChildren();
|
||||
for (Group child : children) {
|
||||
addProgramTreeGroupsRecursively(set, child, monitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace.handler;
|
||||
|
||||
import static ghidra.program.model.symbol.SymbolType.*;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.features.base.replace.*;
|
||||
import ghidra.features.base.replace.items.RenameSymbolQuickFix;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.util.datastruct.Accumulator;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* {@link SearchAndReplaceHandler} for handling search and replace for symbols. Specifically, it
|
||||
* provides {@link SearchType}s for renaming labels, functions, namespaces, classes, local
|
||||
* variables, and parameters.
|
||||
*/
|
||||
public class SymbolsSearchAndReplaceHandler extends SearchAndReplaceHandler {
|
||||
|
||||
public SymbolsSearchAndReplaceHandler() {
|
||||
//@formatter:off
|
||||
addType(new SymbolSearchType(LABEL, "Labels", "Search and replace label names"));
|
||||
addType(new SymbolSearchType(FUNCTION, "Functions", "Search and replace function names"));
|
||||
addType(new SymbolSearchType(NAMESPACE, "Namespaces", "Search and replace generic namespace names"));
|
||||
addType(new SymbolSearchType(CLASS, "Classes", "Search and replace class names"));
|
||||
addType(new SymbolSearchType(LOCAL_VAR, "Local Variables", "Search and replace local variable names"));
|
||||
addType(new SymbolSearchType(PARAMETER, "Parameters", "Search and replace parameter names"));
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Override
|
||||
public void findAll(Program program, SearchAndReplaceQuery query,
|
||||
Accumulator<QuickFix> accumulator, TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
SymbolTable symbolTable = program.getSymbolTable();
|
||||
int symbolCount = symbolTable.getNumSymbols();
|
||||
monitor.initialize(symbolCount, "Searching Labels...");
|
||||
|
||||
Pattern pattern = query.getSearchPattern();
|
||||
|
||||
Set<SymbolType> selectedSymbolTypes = getSelectedSymbolTypes(query);
|
||||
|
||||
for (Symbol symbol : symbolTable.getDefinedSymbols()) {
|
||||
monitor.increment();
|
||||
if (symbol.isExternal()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SymbolType symbolType = symbol.getSymbolType();
|
||||
|
||||
if (selectedSymbolTypes.contains(symbolType)) {
|
||||
if (symbolType == SymbolType.FUNCTION) {
|
||||
Function function = (Function) symbol.getObject();
|
||||
// Thunks can't be renamed directly
|
||||
if (function.isThunk()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Matcher matcher = pattern.matcher(symbol.getName());
|
||||
if (matcher.find()) {
|
||||
String newName = matcher.replaceAll(query.getReplacementText());
|
||||
RenameSymbolQuickFix item = new RenameSymbolQuickFix(symbol, newName);
|
||||
accumulator.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<SymbolType> getSelectedSymbolTypes(SearchAndReplaceQuery query) {
|
||||
Set<SymbolType> symbolTypes = new HashSet<>();
|
||||
|
||||
Set<SearchType> selectedSearchTypes = query.getSelectedSearchTypes();
|
||||
for (SearchType searchType : selectedSearchTypes) {
|
||||
if (searchType instanceof SymbolSearchType symbolSearchType) {
|
||||
symbolTypes.add(symbolSearchType.getSymbolType());
|
||||
}
|
||||
}
|
||||
return symbolTypes;
|
||||
}
|
||||
|
||||
private class SymbolSearchType extends SearchType {
|
||||
private final SymbolType symbolType;
|
||||
|
||||
SymbolSearchType(SymbolType symbolType, String name, String description) {
|
||||
super(SymbolsSearchAndReplaceHandler.this, name, description);
|
||||
this.symbolType = symbolType;
|
||||
}
|
||||
|
||||
SymbolType getSymbolType() {
|
||||
return symbolType;
|
||||
}
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace.items;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.features.base.quickfix.QuickFix;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.Composite;
|
||||
import ghidra.program.model.data.DataTypeComponent;
|
||||
import ghidra.program.model.listing.Program;
|
||||
|
||||
/**
|
||||
* Base class for Composite field Quick Fixes. Primarily exists to host the logic for finding
|
||||
* components in a composite even as it is changing.
|
||||
*/
|
||||
public abstract class CompositeFieldQuickFix extends QuickFix {
|
||||
protected Composite composite;
|
||||
private int ordinal;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param program the program containing the composite.
|
||||
* @param composite the composite being changed
|
||||
* @param ordinal the ordinal of the field within the composite
|
||||
* @param original the original name of the field
|
||||
* @param newName the new name for the field
|
||||
*/
|
||||
public CompositeFieldQuickFix(Program program, Composite composite, int ordinal,
|
||||
String original, String newName) {
|
||||
super(program, original, newName);
|
||||
this.composite = composite;
|
||||
this.ordinal = ordinal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return composite.getPathName();
|
||||
}
|
||||
|
||||
protected DataTypeComponent findComponent(String name) {
|
||||
DataTypeComponent component = getComponentByOrdinal();
|
||||
if (component != null) {
|
||||
if (name.equals(component.getFieldName())) {
|
||||
return component;
|
||||
}
|
||||
}
|
||||
|
||||
// perhaps it moved (has a different ordinal now)?
|
||||
DataTypeComponent[] components = composite.getDefinedComponents();
|
||||
for (int i = 0; i < components.length; i++) {
|
||||
if (name.equals(components[i].getFieldName())) {
|
||||
ordinal = i;
|
||||
return components[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected DataTypeComponent getComponentByOrdinal() {
|
||||
if (composite.isDeleted()) {
|
||||
return null;
|
||||
}
|
||||
if (ordinal >= composite.getNumComponents()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return composite.getComponent(ordinal);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getCustomToolTipData() {
|
||||
return Map.of("DataType", composite.getPathName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
|
||||
DataTypeManagerService dtmService = services.getService(DataTypeManagerService.class);
|
||||
if (dtmService == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dtmService.setDataTypeSelected(composite);
|
||||
|
||||
if (!fromSelectionChange) {
|
||||
dtmService.edit(composite, getFieldName());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected abstract String getFieldName();
|
||||
|
||||
protected void editComposite(DataTypeManagerService dtmService) {
|
||||
dtmService.edit(composite);
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.features.base.replace.items;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import ghidra.app.services.DataTypeManagerService;
|
||||
import ghidra.features.base.quickfix.QuickFixStatus;
|
||||
import ghidra.features.base.replace.RenameQuickFix;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.data.Category;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.InvalidNameException;
|
||||
import ghidra.util.exception.DuplicateNameException;
|
||||
|
||||
/**
|
||||
* QuickFix for renaming datatype categories.
|
||||
*/
|
||||
public class RenameCategoryQuickFix extends RenameQuickFix {
|
||||
|
||||
private Category category;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param program the program containing the category to be renamed
|
||||
* @param category the category to be renamed
|
||||
* @param newName the new name for the category
|
||||
*/
|
||||
public RenameCategoryQuickFix(Program program, Category category, String newName) {
|
||||
super(program, category.getName(), newName);
|
||||
this.category = category;
|
||||
checkForDuplicates();
|
||||
}
|
||||
|
||||
private void checkForDuplicates() {
|
||||
Category parent = category.getParent();
|
||||
if (parent == null) {
|
||||
return;
|
||||
}
|
||||
if (parent.getCategory(replacement) != null) {
|
||||
setStatus(QuickFixStatus.WARNING,
|
||||
"The name \"" + replacement + "\" already exists in category \"" +
|
||||
parent.getCategoryPathName() + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void statusChanged(QuickFixStatus newStatus) {
|
||||
if (newStatus == QuickFixStatus.NONE) {
|
||||
checkForDuplicates();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItemType() {
|
||||
return "datatype category";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return category.getParent().getCategoryPathName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doGetCurrent() {
|
||||
return category.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void execute() {
|
||||
try {
|
||||
category.setName(replacement);
|
||||
}
|
||||
catch (DuplicateNameException | InvalidNameException e) {
|
||||
setStatus(QuickFixStatus.ERROR, "Rename Failed! " + e.getMessage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramLocation getProgramLocation() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean navigateSpecial(ServiceProvider services, boolean fromSelectionChange) {
|
||||
DataTypeManagerService dtmService = services.getService(DataTypeManagerService.class);
|
||||
if (dtmService == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dtmService.setCategorySelected(category);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getCustomToolTipData() {
|
||||
return Map.of("Parent Path", category.getParent().getCategoryPathName());
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user