mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-22 04:05:39 +00:00
review fixes; waiting for master update
GP-4839 - Decompiler Text Finder - Added the ability to search decompiled text
This commit is contained in:
parent
9e589a451a
commit
90eba4f9e0
File diff suppressed because it is too large
Load Diff
@ -290,7 +290,7 @@
|
||||
only the entry point bookmarks, you would enter "entry" in the filter field. The results
|
||||
would show only those bookmarks with a Category or Description containing the word "entry".
|
||||
The text filter is not case sensitive, nor does it support <I><A href=
|
||||
"help/topics/Glossary/glossary.htm#RegularExpression">regular expressions</A></I>.</P>
|
||||
"help/topics/Glossary/glossary.htm#RegularExpression">Regular Expressions</A></I>.</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3><B>Reorder Columns</B></H3>
|
||||
|
@ -1111,7 +1111,10 @@ xmlns:w="urn:schemas-microsoft-com:office:word" xmlns="http://www.w3.org/TR/REC-
|
||||
<H2><A name="RegularExpression"></A>Regular Expression</H2>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>A character sequence used to match patterns in strings.</P>
|
||||
<P>A character sequence used to match patterns in strings. See
|
||||
<A href="help/topics/Search/Regular_Expressions.htm#Regex_Syntax">Regular Expression</A>
|
||||
for examples.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2><A name="RelocationTable"></A>Relocation Table</H2>
|
||||
|
@ -124,7 +124,7 @@ public class ProgramLocationActionContext extends ProgramActionContext
|
||||
return functions;
|
||||
}
|
||||
|
||||
private Function getFunctionForLocation() {
|
||||
protected Function getFunctionForLocation() {
|
||||
if (location instanceof FunctionLocation functionLocation) {
|
||||
Address functionAddress = functionLocation.getFunctionAddress();
|
||||
return program.getFunctionManager().getFunctionAt(functionAddress);
|
||||
|
@ -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.
|
||||
@ -25,6 +25,7 @@ import docking.action.DockingAction;
|
||||
import docking.action.MenuData;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.FunctionSupplierContext;
|
||||
import ghidra.app.context.ListingActionContext;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
@ -145,7 +146,15 @@ public class CallTreePlugin extends ProgramPlugin {
|
||||
|
||||
@Override
|
||||
public boolean isAddToPopup(ActionContext context) {
|
||||
return (context instanceof ListingActionContext);
|
||||
if (context instanceof ListingActionContext) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (context instanceof FunctionSupplierContext functionContext) {
|
||||
return functionContext.hasFunctions();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -92,6 +92,18 @@ public class LocationReferenceContext {
|
||||
return buffy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns text that is helpful for debugging, such as printing to a console.
|
||||
* @return the text
|
||||
*/
|
||||
public String getDebugText() {
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
for (Part part : parts) {
|
||||
buffy.append(part.getDebugText());
|
||||
}
|
||||
return buffy.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns HTML text for this context. Any matching items embedded in the returned string will
|
||||
* be bold.
|
||||
@ -142,7 +154,7 @@ public class LocationReferenceContext {
|
||||
|
||||
abstract String getHtmlText();
|
||||
|
||||
abstract String getText(String start, String end);
|
||||
abstract String getDebugText();
|
||||
|
||||
static String fixBreakingSpaces(String s) {
|
||||
String updated = s.replaceAll("\\s", " ");
|
||||
@ -165,8 +177,8 @@ public class LocationReferenceContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
String getText(String start, String end) {
|
||||
return text; // we don't decorate
|
||||
String getDebugText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -187,8 +199,8 @@ public class LocationReferenceContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
String getText(String start, String end) {
|
||||
return start + text + end;
|
||||
String getDebugText() {
|
||||
return " [[ " + text + " ]] ";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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.
|
||||
@ -58,6 +58,19 @@ public class LocationReferenceContextBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a newline character to the previously added text.
|
||||
* @return this builder
|
||||
*/
|
||||
public LocationReferenceContextBuilder newline() {
|
||||
if (parts.isEmpty()) {
|
||||
throw new IllegalStateException("Cannot add a newline without first appending text");
|
||||
}
|
||||
Part last = parts.get(parts.size() - 1);
|
||||
last.text += '\n';
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link LocationReferenceContext} using the text supplied via the {@code append}
|
||||
* methods.
|
||||
@ -67,6 +80,14 @@ public class LocationReferenceContextBuilder {
|
||||
return new LocationReferenceContext(parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if no text has been added to this builder.
|
||||
* @return true if no text has been added to this builder
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return parts.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
|
@ -150,8 +150,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
if (result == null) {
|
||||
searchDialog.setStatusText("Not found");
|
||||
}
|
||||
else if (result.programLocation()
|
||||
.equals(currentLocation)) {
|
||||
else if (result.programLocation().equals(currentLocation)) {
|
||||
searchNext(searchTask.getProgram(), searchNavigatable, textSearcher);
|
||||
}
|
||||
else {
|
||||
@ -376,8 +375,9 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
* Create the action for to pop up the search dialog.
|
||||
*/
|
||||
private void createActions() {
|
||||
String subGroup = getClass().getName();
|
||||
String subGroup = "d"; // Memory Search uses groups 'a', 'b', and 'c'
|
||||
|
||||
//@formatter:off
|
||||
new ActionBuilder("Search Text", getName())
|
||||
.menuPath("&Search", "Program &Text...")
|
||||
.menuGroup("search", subGroup)
|
||||
@ -406,6 +406,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
searchDialog.repeatSearch();
|
||||
})
|
||||
.buildAndInstall(tool);
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
protected void updateNavigatable(ActionContext context) {
|
||||
@ -467,8 +468,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
String textSelection = navigatable.getTextSelection();
|
||||
ProgramLocation location = navigatable.getLocation();
|
||||
Address address = location.getAddress();
|
||||
Listing listing = context.getProgram()
|
||||
.getListing();
|
||||
Listing listing = context.getProgram().getListing();
|
||||
CodeUnit codeUnit = listing.getCodeUnitAt(address);
|
||||
boolean isInstruction = false;
|
||||
if (textSelection != null) {
|
||||
@ -582,8 +582,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
|
||||
// must have completed too fast for the provider to be set; try something cute
|
||||
Component focusOwner =
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
||||
.getFocusOwner();
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
|
||||
return focusOwner; // assume this IS the provider
|
||||
}
|
||||
|
||||
@ -628,8 +627,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
@Override
|
||||
public Highlight[] createHighlights(String text, ListingField field, int cursorTextOffset) {
|
||||
|
||||
Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory()
|
||||
.getClass();
|
||||
Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory().getClass();
|
||||
|
||||
if (!doHighlight) {
|
||||
return NO_HIGHLIGHTS;
|
||||
@ -652,8 +650,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
return getAllHighlights(text, cursorTextOffset);
|
||||
}
|
||||
|
||||
Address address = searchResult.programLocation()
|
||||
.getAddress();
|
||||
Address address = searchResult.programLocation().getAddress();
|
||||
ProxyObj<?> proxy = field.getProxy();
|
||||
if (proxy.contains(address)) {
|
||||
return getSingleSearchHighlight(text, field, cursorTextOffset);
|
||||
@ -745,8 +742,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
||||
return true;
|
||||
}
|
||||
|
||||
Class<? extends FieldFactory> factoryClass = field.getFieldFactory()
|
||||
.getClass();
|
||||
Class<? extends FieldFactory> factoryClass = field.getFieldFactory().getClass();
|
||||
if (searchOptions.searchComments()) {
|
||||
if (factoryClass == PreCommentFieldFactory.class ||
|
||||
factoryClass == PlateFieldFactory.class ||
|
||||
|
@ -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.
|
||||
@ -384,6 +384,10 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
||||
return model;
|
||||
}
|
||||
|
||||
public GhidraTable getTable() {
|
||||
return threadedPanel.getTable();
|
||||
}
|
||||
|
||||
private void updateTitle() {
|
||||
setSubTitle(generateSubTitle());
|
||||
}
|
||||
|
@ -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.
|
||||
@ -25,14 +25,12 @@ import ghidra.program.model.symbol.NameTransformer;
|
||||
import ghidra.util.StringUtilities;
|
||||
|
||||
/**
|
||||
* This class is used to convert a C/C++ language
|
||||
* token group into readable C/C++ code.
|
||||
* This class is used to convert a C/C++ language token group into readable C/C++ code.
|
||||
*/
|
||||
public class PrettyPrinter {
|
||||
|
||||
/**
|
||||
* The indent string to use when printing.
|
||||
*/
|
||||
private final static NameTransformer IDENTITY = new IdentityNameTransformer();
|
||||
|
||||
public final static String INDENT_STRING = " ";
|
||||
|
||||
private Function function;
|
||||
@ -51,7 +49,7 @@ public class PrettyPrinter {
|
||||
public PrettyPrinter(Function function, ClangTokenGroup tokgroup, NameTransformer transformer) {
|
||||
this.function = function;
|
||||
this.tokgroup = tokgroup;
|
||||
this.transformer = (transformer != null) ? transformer : new IdentityNameTransformer();
|
||||
this.transformer = transformer != null ? transformer : IDENTITY;
|
||||
flattenLines();
|
||||
padEmptyLines();
|
||||
}
|
||||
@ -72,8 +70,7 @@ public class PrettyPrinter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the C language lines contained in the
|
||||
* C language token group.
|
||||
* Returns a list of the C language lines contained in the C language token group.
|
||||
* @return a list of the C language lines
|
||||
*/
|
||||
public List<ClangLine> getLines() {
|
||||
@ -81,37 +78,51 @@ public class PrettyPrinter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the C language token group
|
||||
* into a string of C code.
|
||||
* Prints the C language token group into a string of C code.
|
||||
* @return a string of readable C code
|
||||
*/
|
||||
public DecompiledFunction print() {
|
||||
StringBuilder buff = new StringBuilder();
|
||||
for (ClangLine line : lines) {
|
||||
buff.append(line.getIndentString());
|
||||
List<ClangToken> tokens = line.getAllTokens();
|
||||
|
||||
for (ClangToken token : tokens) {
|
||||
boolean isToken2Clean = token instanceof ClangFuncNameToken ||
|
||||
token instanceof ClangVariableToken || token instanceof ClangTypeToken ||
|
||||
token instanceof ClangFieldToken || token instanceof ClangLabelToken;
|
||||
|
||||
//do not clean constant variable tokens
|
||||
if (isToken2Clean && token.getSyntaxType() == ClangToken.CONST_COLOR) {
|
||||
isToken2Clean = false;
|
||||
}
|
||||
|
||||
String tokenText = token.getText();
|
||||
if (isToken2Clean) {
|
||||
tokenText = transformer.simplify(tokenText);
|
||||
}
|
||||
buff.append(tokenText);
|
||||
}
|
||||
getText(buff, line, transformer);
|
||||
buff.append(StringUtilities.LINE_SEPARATOR);
|
||||
}
|
||||
return new DecompiledFunction(findSignature(), buff.toString());
|
||||
}
|
||||
|
||||
private static void getText(StringBuilder buff, ClangLine line, NameTransformer transformer) {
|
||||
buff.append(line.getIndentString());
|
||||
List<ClangToken> tokens = line.getAllTokens();
|
||||
|
||||
for (ClangToken token : tokens) {
|
||||
boolean isToken2Clean = token instanceof ClangFuncNameToken ||
|
||||
token instanceof ClangVariableToken || token instanceof ClangTypeToken ||
|
||||
token instanceof ClangFieldToken || token instanceof ClangLabelToken;
|
||||
|
||||
//do not clean constant variable tokens
|
||||
if (isToken2Clean && token.getSyntaxType() == ClangToken.CONST_COLOR) {
|
||||
isToken2Clean = false;
|
||||
}
|
||||
|
||||
String tokenText = token.getText();
|
||||
if (isToken2Clean) {
|
||||
tokenText = transformer.simplify(tokenText);
|
||||
}
|
||||
buff.append(tokenText);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the text of the given line as seen in the UI.
|
||||
* @param line the line
|
||||
* @return the text
|
||||
*/
|
||||
public static String getText(ClangLine line) {
|
||||
StringBuilder buff = new StringBuilder();
|
||||
getText(buff, line, IDENTITY);
|
||||
return buff.toString();
|
||||
}
|
||||
|
||||
private String findSignature() {
|
||||
int nChildren = tokgroup.numChildren();
|
||||
for (int i = 0; i < nChildren; ++i) {
|
||||
|
@ -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.
|
||||
@ -23,6 +23,7 @@ import ghidra.app.context.NavigatableActionContext;
|
||||
import ghidra.app.context.RestrictedAddressSetContext;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||
import ghidra.app.decompiler.component.DecompilerUtils;
|
||||
import ghidra.framework.plugintool.PluginTool;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Function;
|
||||
@ -123,8 +124,8 @@ public class DecompilerActionContext extends NavigatableActionContext
|
||||
if (lineNumber != 0) {
|
||||
return lineNumber;
|
||||
}
|
||||
getTokenAtCursor();
|
||||
return tokenAtCursor == null ? 0 : tokenAtCursor.getLineParent().getLineNumber();
|
||||
ClangToken token = getTokenAtCursor();
|
||||
return token == null ? 0 : token.getLineParent().getLineNumber();
|
||||
}
|
||||
|
||||
public DecompilerPanel getDecompilerPanel() {
|
||||
@ -152,6 +153,22 @@ public class DecompilerActionContext extends NavigatableActionContext
|
||||
getComponentProvider().getController().setStatusMessage(msg);
|
||||
}
|
||||
|
||||
// allows this Decompiler action context to signal the location is on a function
|
||||
@Override
|
||||
protected Function getFunctionForLocation() {
|
||||
ClangToken token = getTokenAtCursor();
|
||||
if (token == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (token instanceof ClangFuncNameToken functionToken) {
|
||||
Function function = DecompilerUtils.getFunction(program, functionToken);
|
||||
return function;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The companion method of {@link #checkActionEnablement(Supplier)}.
|
||||
*
|
||||
|
@ -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.
|
||||
@ -1062,7 +1062,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
|
||||
//
|
||||
// Search
|
||||
//
|
||||
String searchGroup = "comment2 - Search Group";
|
||||
String searchGroup = "Comment2 - Search Group";
|
||||
subGroupPosition = 0; // reset for the next group
|
||||
|
||||
FindAction findAction = new FindAction();
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -15,6 +15,7 @@
|
||||
*/
|
||||
apply from: "$rootProject.projectDir/gradle/distributableGhidraModule.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/helpProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/jacocoProject.gradle"
|
||||
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
||||
apply plugin: 'eclipse'
|
||||
|
@ -1,3 +1,8 @@
|
||||
##VERSION: 2.0
|
||||
Module.manifest||GHIDRA||||END|
|
||||
data/ExtensionPoint.manifest||GHIDRA||||END|
|
||||
data/decompiler.dependent.theme.properties||GHIDRA||||END|
|
||||
src/main/help/help/TOC_Source.xml||GHIDRA||||END|
|
||||
src/main/help/help/topics/DecompilerTextFinderPlugin/Decompiler_Text_Finder.html||GHIDRA||||END|
|
||||
src/main/help/help/topics/DecompilerTextFinderPlugin/images/DecompilerTextFinderDialog.png||GHIDRA||||END|
|
||||
src/main/help/help/topics/DecompilerTextFinderPlugin/images/DecompilerTextFinderResultsTable.png||GHIDRA||||END|
|
||||
|
@ -0,0 +1,7 @@
|
||||
|
||||
[Defaults]
|
||||
|
||||
|
||||
icon.plugin.decompiler.text.finder.select.functions = icon.make.selection {FunctionScope.gif[size(12,12)][move(6,6)]}
|
||||
|
||||
[Dark Defaults]
|
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!--
|
||||
|
||||
This is an XML file intended to be parsed by the Ghidra help system. It is loosely based
|
||||
upon the JavaHelp table of contents document format. The Ghidra help system uses a
|
||||
TOC_Source.xml file to allow a module with help to define how its contents appear in the
|
||||
Ghidra help viewer's table of contents. The main document (in the Base module)
|
||||
defines a basic structure for the
|
||||
Ghidra table of contents system. Other TOC_Source.xml files may use this structure to insert
|
||||
their files directly into this structure (and optionally define a substructure).
|
||||
|
||||
|
||||
In this document, a tag can be either a <tocdef> or a <tocref>. The former is a definition
|
||||
of an XML item that may have a link and may contain other <tocdef> and <tocref> children.
|
||||
<tocdef> items may be referred to in other documents by using a <tocref> tag with the
|
||||
appropriate id attribute value. Using these two tags allows any module to define a place
|
||||
in the table of contents system (<tocdef>), which also provides a place for
|
||||
other TOC_Source.xml files to insert content (<tocref>).
|
||||
|
||||
During the help build time, all TOC_Source.xml files will be parsed and validated to ensure
|
||||
that all <tocref> tags point to valid <tocdef> tags. From these files will be generated
|
||||
<module name>_TOC.xml files, which are table of contents files written in the format
|
||||
desired by the JavaHelp system. Additionally, the genated files will be merged together
|
||||
as they are loaded by the JavaHelp system. In the end, when displaying help in the Ghidra
|
||||
help GUI, there will be on table of contents that has been created from the definitions in
|
||||
all of the modules' TOC_Source.xml files.
|
||||
|
||||
|
||||
Tags and Attributes
|
||||
|
||||
<tocdef>
|
||||
-id - the name of the definition (this must be unique across all TOC_Source.xml files)
|
||||
-text - the display text of the node, as seen in the help GUI
|
||||
-target** - the file to display when the node is clicked in the GUI
|
||||
-sortgroup - this is a string that defines where a given node should appear under a given
|
||||
parent. The string values will be sorted by the JavaHelp system using
|
||||
a javax.text.RulesBasedCollator. If this attribute is not specified, then
|
||||
the text of attribute will be used.
|
||||
|
||||
<tocref>
|
||||
-id - The id of the <tocdef> that this reference points to
|
||||
|
||||
**The URL for the target is relative and should start with 'help/topics'. This text is
|
||||
used by the Ghidra help system to provide a universal starting point for all links so that
|
||||
they can be resolved at runtime, across modules.
|
||||
|
||||
|
||||
-->
|
||||
<tocroot>
|
||||
<tocref id="Program Search">
|
||||
<tocdef id="Decompiled Text"
|
||||
text="Decompiled Text"
|
||||
target="help/topics/DecompilerTextFinderPlugin/Decompiler_Text_Finder.html" />
|
||||
|
||||
</tocref>
|
||||
</tocroot>
|
@ -0,0 +1,197 @@
|
||||
<!DOCTYPE doctype PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
|
||||
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<TITLE>Search Decompiled Text</TITLE>
|
||||
<META http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
||||
<LINK rel="stylesheet" type="text/css" href="help/shared/DefaultStyle.css">
|
||||
</HEAD>
|
||||
|
||||
<BODY lang="EN-US">
|
||||
|
||||
<H1><A name="Search_Decompiled_Text"></A>Search Decompiled Text</H1>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
Available from <B>Search <IMG SRC="help/shared/arrow.gif" alt="->" border="0" /> Search
|
||||
Decompiled Text</B>, this action allows you to search the decompiled output
|
||||
for each function in the current program.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The <B>Decompiled Function Search</B> dialog is shown when users execute the <B>Search
|
||||
Decompiled Text</B> action. If <I>String Search</I> is selected, then a case insensitive text
|
||||
search is performed using the specified text. If <I>Regular Expression</I> is selected, then
|
||||
a <A href="help/topics/Search/Regular_Expressions.htm#Regex_Syntax">Regular Expression</A>
|
||||
search is performed. Selecting <I>Search Selection</I> will only search function entry points
|
||||
that are in the current program selection.
|
||||
</P>
|
||||
|
||||
<BR><BR>
|
||||
<TABLE width="100%">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD align="center" width="100%">
|
||||
<IMG border="1" src="images/DecompilerTextFinderDialog.png"></TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
|
||||
<CAPTION align="bottom">
|
||||
<I>The Decompiled Function Search input dialog</I>
|
||||
</CAPTION>
|
||||
</TABLE>
|
||||
|
||||
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/tip.png" alt="Note" border="0">When restricting the search to
|
||||
the current program selection, only functions whose entry point is in the selection will
|
||||
be searched. The selection will not search functions that have a program selection in
|
||||
the body, but not at the entry point.
|
||||
</P>
|
||||
</BLOCKQUOTE><BR><BR>
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<P><IMG src="help/shared/tip.png" alt="Note" border="0">By default text searches apply
|
||||
only to one line at a time. If you would like a multi-line search, then you will need
|
||||
to use a Regular Expression search to do so. See the table below for an example.
|
||||
</P>
|
||||
</BLOCKQUOTE><BR><BR>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H2>Search Results</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The search results will be presented as they are found, as seen in the table below. For single
|
||||
line search matches, the matching part of the search will be highlighted in the <B>Context</B>
|
||||
column.
|
||||
</P>
|
||||
|
||||
<BR><BR>
|
||||
<TABLE width="100%">
|
||||
<TBODY>
|
||||
<TR>
|
||||
<TD align="center" width="100%">
|
||||
<IMG border="1" src="images/DecompilerTextFinderResultsTable.png" /></TD>
|
||||
</TR>
|
||||
</TBODY>
|
||||
|
||||
<CAPTION align="bottom">
|
||||
<I>The Decompiled Function Search results table</I>
|
||||
</CAPTION>
|
||||
</TABLE>
|
||||
|
||||
|
||||
<H3>Select Functions Action <IMG BORDER="1" SRC="icon.plugin.decompiler.text.finder.select.functions" />
|
||||
</H3><A NAME="Select_Functions"></A>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
|
||||
This action will create a program selection for each function entry point for each function
|
||||
selected in the table.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
<H3>Other Actions</H3>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
Help for the other table actions can be found
|
||||
<A HREF="help/topics/Search/Query_Results_Dialog.htm#Make_Selection">here</A>.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<H2>Example Searches</H2>
|
||||
<BLOCKQUOTE>
|
||||
<P>
|
||||
The table below show some example searches and lines that they will match. Note that the
|
||||
reported matches will show the entire line that matched, not just the matching portion of the
|
||||
line.
|
||||
</P>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
<BLOCKQUOTE>
|
||||
<CENTER>
|
||||
<TABLE border="1">
|
||||
<TR>
|
||||
<TH align="left" bgcolor="#c0c0c0" valign="top"><B>Search String</B></TH>
|
||||
<TH align="left" bgcolor="#c0c0c0" valign="top"><B>Description</B></TH>
|
||||
<TH align="left" bgcolor="#c0c0c0" valign="top"><B>Example Matching Lines</B></TH>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD valign="top">
|
||||
<CODE>
|
||||
= '\0'
|
||||
</CODE>
|
||||
</TD>
|
||||
<TD valign="top">
|
||||
A non-regular expression to find the null character assignment.
|
||||
</TD>
|
||||
<TD valign="top" nowrap>
|
||||
<CODE>
|
||||
ptr->data[1] = '\0';
|
||||
</CODE>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD valign="top">
|
||||
<CODE>
|
||||
set_string\(.*->.*\)
|
||||
</CODE>
|
||||
</TD>
|
||||
<TD valign="top">
|
||||
A regular expression to find <CODE>set_string(</CODE> followed by any number of characters,
|
||||
followed by <CODE>-></CODE>, followed by any number of characters and a closing parenthesis.
|
||||
</TD>
|
||||
<TD valign="top" nowrap>
|
||||
<CODE>
|
||||
set_string(mytable->entry + mytable->numcodes,ptr);
|
||||
</CODE>
|
||||
</TD>
|
||||
</TR>
|
||||
<TR>
|
||||
<TD valign="top">
|
||||
<CODE>
|
||||
(?s)ffff.*piVar2 =
|
||||
</CODE>
|
||||
</TD>
|
||||
<TD valign="top">
|
||||
A regular expression that searches across multiple lines by using <CODE>(?S)</CODE>. This
|
||||
will find <CODE>ffff</CODE> followed by any number of characters, followed by
|
||||
<CODE>piVar2 =</CODE>.
|
||||
</TD>
|
||||
<TD valign="top" nowrap>
|
||||
<CODE>
|
||||
if (__CTOR_LIST__ != (code *)0xffffffff) {<BR>
|
||||
piVar2 = (int *)&__CTOR_LIST__;,
|
||||
</CODE>
|
||||
</TD>
|
||||
</TR>
|
||||
</TABLE>
|
||||
</CENTER>
|
||||
</BLOCKQUOTE>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<P class="providedbyplugin">Provided by: <I>Decompiler Text Finder Plugin</I></P>
|
||||
|
||||
<P class="relatedtopic">Related Topics:</P>
|
||||
|
||||
<UL>
|
||||
<LI><A href="help/topics/DecompilePlugin/DecompilerIntro.html">Decompiler</A></LI>
|
||||
|
||||
<LI><A href="help/topics/Search/Search_Program_Text.htm">Search Program Text</A></LI>
|
||||
</UL><BR>
|
||||
<BR>
|
||||
</BODY>
|
||||
</HTML>
|
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@ -0,0 +1,389 @@
|
||||
/* ###
|
||||
* 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.search;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import generic.json.Json;
|
||||
import ghidra.app.decompiler.*;
|
||||
import ghidra.app.decompiler.component.DecompilerUtils;
|
||||
import ghidra.app.decompiler.parallel.*;
|
||||
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
|
||||
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContextBuilder;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.address.AddressSpace;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import utility.function.Dummy;
|
||||
|
||||
/**
|
||||
* Searches for the given pattern in all functions in the given program.
|
||||
*/
|
||||
public class DecompilerTextFinder {
|
||||
|
||||
/**
|
||||
* Finds text inside decompiled functions using the given pattern.
|
||||
* @param program the program
|
||||
* @param searchPattern the search pattern
|
||||
* @param consumer the consumer that will get matches
|
||||
* @param monitor the task monitor
|
||||
* @see #findText(Program, Pattern, Collection, Consumer, TaskMonitor)
|
||||
*/
|
||||
public void findText(Program program, Pattern searchPattern, Consumer<TextMatch> consumer,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
monitor = TaskMonitor.dummyIfNull(monitor);
|
||||
StringFinderCallback callback = new StringFinderCallback(program, searchPattern, consumer);
|
||||
|
||||
Listing listing = program.getListing();
|
||||
FunctionIterator functions = listing.getFunctions(true);
|
||||
|
||||
doFindText(program, functions, callback, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds text inside the given decompiled functions using the given pattern.
|
||||
* @param program the program
|
||||
* @param searchPattern the search pattern
|
||||
* @param functions the functions to search
|
||||
* @param consumer the consumer that will get matches
|
||||
* @param monitor the task monitor
|
||||
* @see #findText(Program, Pattern, Consumer, TaskMonitor)
|
||||
*/
|
||||
public void findText(Program program, Pattern searchPattern, Iterator<Function> functions,
|
||||
Consumer<TextMatch> consumer, TaskMonitor monitor) {
|
||||
|
||||
monitor = TaskMonitor.dummyIfNull(monitor);
|
||||
StringFinderCallback callback = new StringFinderCallback(program, searchPattern, consumer);
|
||||
doFindText(program, functions, callback, monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds text inside the given decompiled functions using the given pattern.
|
||||
* @param program the program
|
||||
* @param searchPattern the search pattern
|
||||
* @param functions the functions to search
|
||||
* @param consumer the consumer that will get matches
|
||||
* @param monitor the task monitor
|
||||
* @see #findText(Program, Pattern, Consumer, TaskMonitor)
|
||||
*/
|
||||
public void findText(Program program, Pattern searchPattern, Collection<Function> functions,
|
||||
Consumer<TextMatch> consumer, TaskMonitor monitor) {
|
||||
|
||||
monitor = TaskMonitor.dummyIfNull(monitor);
|
||||
StringFinderCallback callback = new StringFinderCallback(program, searchPattern, consumer);
|
||||
doFindText(functions, callback, monitor);
|
||||
}
|
||||
|
||||
private void doFindText(Collection<Function> functions, StringFinderCallback callback,
|
||||
TaskMonitor monitor) {
|
||||
|
||||
try {
|
||||
ParallelDecompiler.decompileFunctions(callback, functions, monitor);
|
||||
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt(); // reset the flag
|
||||
if (!monitor.isCancelled()) {
|
||||
Msg.debug(this, "Interrupted while decompiling functions");
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Encountered an exception decompiling functions", e);
|
||||
}
|
||||
finally {
|
||||
callback.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void doFindText(Program program, Iterator<Function> functions,
|
||||
StringFinderCallback callback, TaskMonitor monitor) {
|
||||
|
||||
Consumer<Void> dummy = Dummy.consumer();
|
||||
|
||||
try {
|
||||
ParallelDecompiler.decompileFunctions(callback, program, functions, dummy, monitor);
|
||||
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt(); // reset the flag
|
||||
if (!monitor.isCancelled()) {
|
||||
Msg.debug(this, "Interrupted while decompiling functions");
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Msg.error(this, "Encountered an exception decompiling functions", e);
|
||||
}
|
||||
finally {
|
||||
callback.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static class StringFinderCallback extends DecompilerCallback<Void> {
|
||||
|
||||
private Consumer<TextMatch> callback;
|
||||
private Pattern pattern;
|
||||
private String searchText;
|
||||
|
||||
StringFinderCallback(Program program, Pattern pattern, Consumer<TextMatch> callback) {
|
||||
super(program, new DecompilerConfigurer());
|
||||
this.pattern = pattern;
|
||||
this.callback = callback;
|
||||
this.searchText = pattern.pattern();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void process(DecompileResults results, TaskMonitor monitor) throws Exception {
|
||||
|
||||
Function function = results.getFunction();
|
||||
if (function.isThunk()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
ClangTokenGroup tokens = results.getCCodeMarkup();
|
||||
List<ClangLine> lines = DecompilerUtils.toLines(tokens);
|
||||
|
||||
// (?s) - enable dot all mode
|
||||
// (?-s) - disable dot all mode
|
||||
boolean multiLine = (pattern.flags() & Pattern.DOTALL) == Pattern.DOTALL;
|
||||
if (multiLine) {
|
||||
performMultiLineSearch(function, lines);
|
||||
}
|
||||
else {
|
||||
// line-by-line search
|
||||
for (ClangLine cLine : lines) {
|
||||
findMatch(function, cLine);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void performMultiLineSearch(Function function, List<ClangLine> lines) {
|
||||
|
||||
// Map characters to lines so we can later translate a character position into the line
|
||||
// that contains it. Also convert all lines into one big run of text to use with the
|
||||
// regex. Also, turn the c line into a String for later searching.
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
TreeMap<Integer, TextLine> linesRangeMap = new TreeMap<>();
|
||||
int pos = 0;
|
||||
for (ClangLine cLine : lines) {
|
||||
String text = PrettyPrinter.getText(cLine);
|
||||
buffy.append(text).append('\n');
|
||||
TextLine textLine = new TextLine(pos, cLine, text);
|
||||
linesRangeMap.put(pos, textLine);
|
||||
pos += text.length() + 1; // +1 for newline
|
||||
}
|
||||
|
||||
Matcher matcher = pattern.matcher(buffy);
|
||||
findMatches(function, linesRangeMap, matcher);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the given matcher to search for all matches, which may span multiple lines.
|
||||
*/
|
||||
private void findMatches(Function function, TreeMap<Integer, TextLine> linesRangeMap,
|
||||
Matcher matcher) {
|
||||
|
||||
while (matcher.find()) {
|
||||
emitTextMatch(function, linesRangeMap, matcher);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitTextMatch(Function function, TreeMap<Integer, TextLine> linesRangeMap,
|
||||
Matcher matcher) {
|
||||
|
||||
List<TextLine> lineMatches = new ArrayList<>();
|
||||
|
||||
int pos = 0;
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
for (pos = start; pos < end; pos++) {
|
||||
|
||||
// grab the line that contains the current character position
|
||||
TextLine line = linesRangeMap.floorEntry(pos).getValue();
|
||||
|
||||
// This will be positive if the current line contains the match start character.
|
||||
// In this case, let the line know it has the start. If we don't set the start,
|
||||
// then the line match will start at 0.
|
||||
int lineStartOffset = start - line.getOffset(); // relative offset
|
||||
if (lineStartOffset >= 0) {
|
||||
line.setMatchStart(lineStartOffset);
|
||||
}
|
||||
|
||||
// In this case, let the line know it has the end. If we don't set the end,
|
||||
// then the line match will end at the end of the line.
|
||||
if (end <= line.getEndOffset()) {
|
||||
int relativeEnd = end - line.getOffset();
|
||||
line.setMatchEnd(relativeEnd);
|
||||
}
|
||||
|
||||
lineMatches.add(line);
|
||||
pos = line.getEndOffset();
|
||||
}
|
||||
|
||||
// Use the first line for attributes of the match, like line number and address
|
||||
TextLine firstLine = lineMatches.get(0);
|
||||
int lineNumber = firstLine.getLineNumber();
|
||||
AddressSet addresses = getAddresses(function, firstLine.getCLine());
|
||||
LocationReferenceContext context = createMatchContext(lineMatches);
|
||||
TextMatch match =
|
||||
new TextMatch(function, addresses, lineNumber, searchText, context, true);
|
||||
callback.accept(match);
|
||||
}
|
||||
|
||||
private LocationReferenceContext createMatchContext(List<TextLine> matches) {
|
||||
|
||||
LocationReferenceContextBuilder builder = new LocationReferenceContextBuilder();
|
||||
for (TextLine line : matches) {
|
||||
if (!builder.isEmpty()) {
|
||||
builder.newline();
|
||||
}
|
||||
|
||||
String text = line.getText();
|
||||
int start = line.getMatchStart();
|
||||
int end = line.getMatchEnd();
|
||||
builder.append(text.substring(0, start));
|
||||
builder.appendMatch(text.substring(start, end));
|
||||
builder.append(text.substring(end, line.length()));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private void findMatch(Function function, ClangLine line) {
|
||||
|
||||
String textLine = PrettyPrinter.getText(line);
|
||||
Matcher matcher = pattern.matcher(textLine);
|
||||
if (!matcher.find()) {
|
||||
return;
|
||||
}
|
||||
|
||||
LocationReferenceContextBuilder builder = new LocationReferenceContextBuilder();
|
||||
|
||||
int start = matcher.start();
|
||||
int end = matcher.end();
|
||||
builder.append(textLine.substring(0, start));
|
||||
builder.appendMatch(textLine.substring(start, end));
|
||||
if (end < textLine.length()) {
|
||||
builder.append(textLine.substring(end));
|
||||
}
|
||||
|
||||
int lineNumber = line.getLineNumber();
|
||||
AddressSet addresses = getAddresses(function, line);
|
||||
LocationReferenceContext context = builder.build();
|
||||
TextMatch match =
|
||||
new TextMatch(function, addresses, lineNumber, searchText, context, false);
|
||||
callback.accept(match);
|
||||
}
|
||||
|
||||
private AddressSet getAddresses(Function function, ClangLine line) {
|
||||
Program program = function.getProgram();
|
||||
AddressSpace space = function.getEntryPoint().getAddressSpace();
|
||||
List<ClangToken> tokens = line.getAllTokens();
|
||||
return DecompilerUtils.findClosestAddressSet(program, space, tokens);
|
||||
}
|
||||
|
||||
/**
|
||||
* A text line represents a ClangLine, it's pretty text, its character position in the
|
||||
* overall body of text and the character positions of the portion of text that has matched
|
||||
* a search. A line may have a search match that is partial or the entire line may match
|
||||
* the search, such as in a multi-line match.
|
||||
*/
|
||||
private class TextLine {
|
||||
private ClangLine cLine;
|
||||
private int offset; // the character offset into the entire body of text
|
||||
private String text;
|
||||
|
||||
// relative offsets
|
||||
private int matchStart;
|
||||
private int matchEnd;
|
||||
|
||||
TextLine(int offset, ClangLine cLine, String text) {
|
||||
this.cLine = cLine;
|
||||
this.offset = offset;
|
||||
this.text = text;
|
||||
|
||||
matchStart = 0;
|
||||
matchEnd = text.length();
|
||||
}
|
||||
|
||||
ClangLine getCLine() {
|
||||
return cLine;
|
||||
}
|
||||
|
||||
int length() {
|
||||
return text.length();
|
||||
}
|
||||
|
||||
int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
int getEndOffset() {
|
||||
return offset + length();
|
||||
}
|
||||
|
||||
int getLineNumber() {
|
||||
return cLine.getLineNumber();
|
||||
}
|
||||
|
||||
String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
int getMatchStart() {
|
||||
return matchStart;
|
||||
}
|
||||
|
||||
int getMatchEnd() {
|
||||
return matchEnd;
|
||||
}
|
||||
|
||||
void setMatchStart(int matchStart) {
|
||||
this.matchStart = matchStart;
|
||||
}
|
||||
|
||||
void setMatchEnd(int matchEnd) {
|
||||
this.matchEnd = matchEnd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DecompilerConfigurer implements DecompileConfigurer {
|
||||
|
||||
@Override
|
||||
public void configure(DecompInterface decompiler) {
|
||||
decompiler.toggleCCode(true);
|
||||
decompiler.toggleSyntaxTree(true);
|
||||
decompiler.setSimplificationStyle("decompile");
|
||||
|
||||
DecompileOptions xmlOptions = new DecompileOptions();
|
||||
xmlOptions.setDefaultTimeout(60);
|
||||
decompiler.setOptions(xmlOptions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
/* ###
|
||||
* 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.search;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.PatternSyntaxException;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.ReusableDialogComponentProvider;
|
||||
import docking.widgets.checkbox.GCheckBox;
|
||||
import docking.widgets.combobox.GhidraComboBox;
|
||||
import docking.widgets.label.GLabel;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.MessageType;
|
||||
|
||||
/**
|
||||
* A dialog to gather input for performing a search over decompiled text.
|
||||
*/
|
||||
public class DecompilerTextFinderDialog extends ReusableDialogComponentProvider {
|
||||
|
||||
private GhidraComboBox<String> textCombo;
|
||||
|
||||
private JButton searchButton;
|
||||
private JCheckBox regexCb;
|
||||
private JCheckBox searchSelectionCb;
|
||||
|
||||
private String searchText;
|
||||
private boolean isCancelled;
|
||||
|
||||
public DecompilerTextFinderDialog() {
|
||||
super("Decompiled Function Search");
|
||||
|
||||
addWorkPanel(buildMainPanel());
|
||||
buildButtons();
|
||||
|
||||
setHelpLocation(new HelpLocation("DecompilerTextFinderPlugin", "Search_Decompiled_Text"));
|
||||
}
|
||||
|
||||
private void buildButtons() {
|
||||
searchButton = new JButton("Search");
|
||||
searchButton.addActionListener(ev -> doSearch());
|
||||
addButton(searchButton);
|
||||
setDefaultButton(searchButton);
|
||||
|
||||
addCancelButton();
|
||||
}
|
||||
|
||||
private JPanel buildMainPanel() {
|
||||
|
||||
regexCb = new GCheckBox("Regular Expression", false);
|
||||
regexCb.setName("Regular Expression Search");
|
||||
|
||||
regexCb.addItemListener(e -> clearStatusText());
|
||||
|
||||
searchSelectionCb = new JCheckBox("Search Selection");
|
||||
searchSelectionCb.setName("Search Selection");
|
||||
|
||||
textCombo = new GhidraComboBox<>();
|
||||
textCombo.setEditable(true);
|
||||
textCombo.addActionListener(e -> doSearch());
|
||||
|
||||
textCombo.setColumns(20);
|
||||
textCombo.addDocumentListener(new DocumentListener() {
|
||||
@Override
|
||||
public void changedUpdate(DocumentEvent e) {
|
||||
handleDocumentUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertUpdate(DocumentEvent e) {
|
||||
handleDocumentUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(DocumentEvent e) {
|
||||
handleDocumentUpdate();
|
||||
}
|
||||
|
||||
private void handleDocumentUpdate() {
|
||||
String text = textCombo.getText();
|
||||
searchButton.setEnabled(!StringUtils.isBlank(text));
|
||||
clearStatusText();
|
||||
}
|
||||
});
|
||||
|
||||
JLabel findLabel = new GLabel("Find: ");
|
||||
|
||||
// associate this label with a mnemonic key that activates the text field
|
||||
findLabel.setDisplayedMnemonic(KeyEvent.VK_N);
|
||||
textCombo.associateLabel(findLabel);
|
||||
|
||||
JPanel mainPanel = new JPanel(new BorderLayout());
|
||||
JPanel textPanel = new JPanel();
|
||||
textPanel.setLayout(new BoxLayout(textPanel, BoxLayout.LINE_AXIS));
|
||||
textPanel.add(findLabel);
|
||||
textPanel.add(textCombo);
|
||||
mainPanel.add(textPanel, BorderLayout.NORTH);
|
||||
mainPanel.add(buildOptionsPanel(), BorderLayout.SOUTH);
|
||||
mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
|
||||
|
||||
return mainPanel;
|
||||
}
|
||||
|
||||
private JPanel buildOptionsPanel() {
|
||||
JPanel optionsPanel = new JPanel();
|
||||
optionsPanel.setLayout(new BoxLayout(optionsPanel, BoxLayout.LINE_AXIS));
|
||||
optionsPanel.setBorder(BorderFactory.createTitledBorder("Options"));
|
||||
|
||||
optionsPanel.add(regexCb);
|
||||
optionsPanel.add(Box.createHorizontalGlue());
|
||||
optionsPanel.add(searchSelectionCb);
|
||||
|
||||
return optionsPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
textCombo.setText("");
|
||||
super.close();
|
||||
}
|
||||
|
||||
private void doSearch() {
|
||||
|
||||
searchText = null;
|
||||
clearStatusText();
|
||||
if (!searchButton.isEnabled()) {
|
||||
return; // don't search while disabled
|
||||
}
|
||||
|
||||
String text = textCombo.getText();
|
||||
if (!validateRegex(text)) {
|
||||
return; // leave the dialog open so the user can see the error text
|
||||
}
|
||||
|
||||
isCancelled = false;
|
||||
searchText = text;
|
||||
updateSearchHistory(searchText);
|
||||
close();
|
||||
}
|
||||
|
||||
private boolean validateRegex(String text) {
|
||||
|
||||
if (!isRegex()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
try {
|
||||
Pattern.compile(text);
|
||||
return true;
|
||||
}
|
||||
catch (PatternSyntaxException e) {
|
||||
setStatusText("Invalid regex: " + e.getMessage(), MessageType.ERROR);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dialogShown() {
|
||||
searchButton.setEnabled(false);
|
||||
clearStatusText();
|
||||
searchText = null;
|
||||
|
||||
// To track cancelled, assume that the dialog is always in a cancelled state unless the
|
||||
// user actually performed a search.
|
||||
isCancelled = true;
|
||||
}
|
||||
|
||||
public void setSearchText(String text) {
|
||||
textCombo.setText(text);
|
||||
}
|
||||
|
||||
public String getSearchText() {
|
||||
return searchText;
|
||||
}
|
||||
|
||||
public boolean isSearchSelection() {
|
||||
return searchSelectionCb.isSelected();
|
||||
}
|
||||
|
||||
public void setSearchSelectionEnabled(boolean b) {
|
||||
if (!b) {
|
||||
searchSelectionCb.setEnabled(false);
|
||||
searchSelectionCb.setSelected(false);
|
||||
}
|
||||
else {
|
||||
searchSelectionCb.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRegex() {
|
||||
return regexCb.isSelected();
|
||||
}
|
||||
|
||||
public boolean isCancelled() {
|
||||
return isCancelled;
|
||||
}
|
||||
|
||||
private void updateSearchHistory(String text) {
|
||||
|
||||
MutableComboBoxModel<String> model = (MutableComboBoxModel<String>) textCombo.getModel();
|
||||
model.insertElementAt(text, 0);
|
||||
|
||||
int size = model.getSize();
|
||||
for (int i = 1; i < size; i++) {
|
||||
String element = model.getElementAt(i);
|
||||
if (element.equals(text)) { // already in the list, remove it
|
||||
model.removeElementAt(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// do this last since removing items may change the selected item
|
||||
model.setSelectedItem(text);
|
||||
}
|
||||
}
|
@ -0,0 +1,167 @@
|
||||
/* ###
|
||||
* 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.search;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import docking.action.MenuData;
|
||||
import docking.action.builder.ActionBuilder;
|
||||
import generic.theme.GIcon;
|
||||
import ghidra.app.CorePluginPackage;
|
||||
import ghidra.app.context.NavigatableActionContext;
|
||||
import ghidra.app.context.NavigatableContextAction;
|
||||
import ghidra.app.events.ProgramSelectionPluginEvent;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.plugin.PluginCategoryNames;
|
||||
import ghidra.app.plugin.ProgramPlugin;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.app.util.SearchConstants;
|
||||
import ghidra.app.util.query.TableService;
|
||||
import ghidra.framework.options.ToolOptions;
|
||||
import ghidra.framework.plugintool.*;
|
||||
import ghidra.framework.plugintool.util.PluginStatus;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.util.ProgramSelection;
|
||||
import ghidra.util.table.GhidraTable;
|
||||
|
||||
//@formatter:off
|
||||
@PluginInfo(
|
||||
status = PluginStatus.RELEASED,
|
||||
packageName = CorePluginPackage.NAME,
|
||||
category = PluginCategoryNames.SEARCH,
|
||||
shortDescription = "Decompiler Text Finder Plugin",
|
||||
description = "This plugin adds an action to allow users to search decompiled text.",
|
||||
servicesRequired = { TableService.class }
|
||||
)
|
||||
//@formatter:on
|
||||
public class DecompilerTextFinderPlugin extends ProgramPlugin {
|
||||
|
||||
private static final Icon SELECT_FUNCTIONS_ICON =
|
||||
new GIcon("icon.plugin.decompiler.text.finder.select.functions");
|
||||
|
||||
private DecompilerTextFinderDialog searchDialog;
|
||||
|
||||
public DecompilerTextFinderPlugin(PluginTool tool) {
|
||||
super(tool);
|
||||
|
||||
createActions();
|
||||
}
|
||||
|
||||
private void createActions() {
|
||||
|
||||
NavigatableContextAction searchAction =
|
||||
new NavigatableContextAction("Search Decompiled Text", getName()) {
|
||||
@Override
|
||||
public void actionPerformed(NavigatableActionContext context) {
|
||||
search(context.getNavigatable(), context.getProgram());
|
||||
}
|
||||
};
|
||||
|
||||
// Memory Search uses groups 'a', 'b', and 'c'; Search Text uses group 'd'
|
||||
String subGroup = "e";
|
||||
searchAction.setMenuBarData(new MenuData(new String[] { "Search", "Decompiled Text..." },
|
||||
null, "search", -1, subGroup));
|
||||
|
||||
searchAction.addToWindowWhen(NavigatableActionContext.class);
|
||||
|
||||
tool.addAction(searchAction);
|
||||
}
|
||||
|
||||
private void search(Navigatable navigatable, Program program) {
|
||||
|
||||
if (searchDialog == null) {
|
||||
searchDialog = new DecompilerTextFinderDialog();
|
||||
}
|
||||
|
||||
// update the Search Selection checkbox as needed
|
||||
boolean enableSearchSelection = false;
|
||||
if (currentSelection != null) {
|
||||
FunctionManager functionManager = program.getFunctionManager();
|
||||
FunctionIterator it = functionManager.getFunctions(currentSelection, true);
|
||||
if (it.hasNext()) {
|
||||
enableSearchSelection = true;
|
||||
}
|
||||
}
|
||||
searchDialog.setSearchSelectionEnabled(enableSearchSelection);
|
||||
|
||||
tool.showDialog(searchDialog);
|
||||
if (searchDialog.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String searchText = searchDialog.getSearchText();
|
||||
boolean isRegex = searchDialog.isRegex();
|
||||
String title = "Decompiler Search Text - '" + searchText + "'";
|
||||
String tableTypeName = "Decompiler Search";
|
||||
DecompilerTextFinderTableModel model =
|
||||
new DecompilerTextFinderTableModel(tool, program, searchText, isRegex);
|
||||
if (searchDialog.isSearchSelection()) {
|
||||
model.setSelection(currentSelection);
|
||||
}
|
||||
|
||||
int searchLimit = getSearchLimit();
|
||||
model.setSearchLimit(searchLimit);
|
||||
|
||||
Color markerColor = SearchConstants.SEARCH_HIGHLIGHT_COLOR;
|
||||
Icon markerIcon = new GIcon("icon.base.search.marker");
|
||||
String windowSubMenu = "Search";
|
||||
TableService tableService = tool.getService(TableService.class);
|
||||
TableComponentProvider<TextMatch> provider = tableService.showTableWithMarkers(title,
|
||||
tableTypeName, model, markerColor, markerIcon, windowSubMenu, navigatable);
|
||||
|
||||
provider.installRemoveItemsAction();
|
||||
|
||||
GhidraTable table = provider.getTable();
|
||||
//@formatter:off
|
||||
new ActionBuilder("Select Functions", getName())
|
||||
.description("Make program selection of function starts from selected rows")
|
||||
.toolBarIcon(SELECT_FUNCTIONS_ICON)
|
||||
.popupMenuIcon(SELECT_FUNCTIONS_ICON)
|
||||
.popupMenuPath("Select Functions")
|
||||
.popupMenuGroup(null, "a") // before the others in the table, to match the toolbar
|
||||
.enabledWhen(c -> table.getSelectedRowCount() > 0)
|
||||
.onAction(c -> selectFunctions(table, model))
|
||||
.buildAndInstallLocal(provider)
|
||||
;
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
private int getSearchLimit() {
|
||||
ToolOptions options = tool.getOptions(SearchConstants.SEARCH_OPTION_NAME);
|
||||
return options.getInt(SearchConstants.SEARCH_LIMIT_NAME,
|
||||
SearchConstants.DEFAULT_SEARCH_LIMIT);
|
||||
}
|
||||
|
||||
private void selectFunctions(GhidraTable table, DecompilerTextFinderTableModel model) {
|
||||
|
||||
AddressSet addresses = new AddressSet();
|
||||
int[] rows = table.getSelectedRows();
|
||||
for (int row : rows) {
|
||||
TextMatch match = model.getRowObject(row);
|
||||
Function f = match.getFunction();
|
||||
addresses.add(f.getEntryPoint());
|
||||
}
|
||||
|
||||
ProgramSelection selection = new ProgramSelection(addresses);
|
||||
PluginEvent event =
|
||||
new ProgramSelectionPluginEvent(getName(), selection, table.getProgram());
|
||||
firePluginEvent(event);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,200 @@
|
||||
/* ###
|
||||
* 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.search;
|
||||
|
||||
import java.awt.Component;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import docking.widgets.table.*;
|
||||
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
|
||||
import ghidra.docking.settings.Settings;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSetView;
|
||||
import ghidra.program.model.listing.*;
|
||||
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.table.field.AbstractProgramBasedDynamicTableColumn;
|
||||
import ghidra.util.table.field.FunctionNameTableColumn;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class DecompilerTextFinderTableModel extends GhidraProgramTableModel<TextMatch> {
|
||||
|
||||
private String searchText;
|
||||
private boolean isRegex;
|
||||
private AddressSetView selection;
|
||||
private int searchLimit;
|
||||
|
||||
protected DecompilerTextFinderTableModel(ServiceProvider serviceProvider, Program program,
|
||||
String searchText, boolean isRegex) {
|
||||
super("Decompiler Search", serviceProvider, program, null, true);
|
||||
this.searchText = searchText;
|
||||
this.isRegex = isRegex;
|
||||
}
|
||||
|
||||
void setSelection(AddressSetView selection) {
|
||||
this.selection = selection;
|
||||
}
|
||||
|
||||
void setSearchLimit(int limit) {
|
||||
this.searchLimit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableColumnDescriptor<TextMatch> createTableColumnDescriptor() {
|
||||
|
||||
TableColumnDescriptor<TextMatch> descriptor = new TableColumnDescriptor<>();
|
||||
|
||||
descriptor.addVisibleColumn(
|
||||
DiscoverableTableUtils.adaptColumForModel(this, new FunctionNameTableColumn()), 1,
|
||||
true);
|
||||
descriptor.addVisibleColumn(new LineNumberTableColumn(), 2, true);
|
||||
descriptor.addVisibleColumn(new ContextTableColumn());
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doLoad(Accumulator<TextMatch> accumulator, TaskMonitor monitor)
|
||||
throws CancelledException {
|
||||
|
||||
// Add a consumer that will monitor the count and stop the process on too many results
|
||||
AtomicInteger counter = new AtomicInteger();
|
||||
Consumer<TextMatch> limitedConsumer = tm -> {
|
||||
int count = counter.incrementAndGet();
|
||||
if (count >= searchLimit) {
|
||||
monitor.cancel();
|
||||
}
|
||||
|
||||
accumulator.add(tm);
|
||||
};
|
||||
|
||||
Pattern pattern;
|
||||
if (isRegex) {
|
||||
// note: we expect this to be a valid regex
|
||||
pattern = Pattern.compile(searchText);
|
||||
}
|
||||
else {
|
||||
String quoted = Pattern.quote(searchText);
|
||||
pattern = Pattern.compile(quoted, Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
DecompilerTextFinder finder = new DecompilerTextFinder();
|
||||
if (selection != null) {
|
||||
FunctionManager functionManager = program.getFunctionManager();
|
||||
FunctionIterator functions = functionManager.getFunctions(selection, true);
|
||||
finder.findText(program, pattern, functions, limitedConsumer, monitor);
|
||||
}
|
||||
else {
|
||||
finder.findText(program, pattern, limitedConsumer, monitor);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address getAddress(int row) {
|
||||
TextMatch match = getRowObject(row);
|
||||
if (match != null) {
|
||||
return match.getAddress();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
//=================================================================================================
|
||||
//Inner Classes
|
||||
//=================================================================================================
|
||||
|
||||
private class LineNumberTableColumn
|
||||
extends AbstractProgramBasedDynamicTableColumn<TextMatch, Integer> {
|
||||
|
||||
@Override
|
||||
public Integer getValue(TextMatch rowObject, Settings settings, Program p,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getLineNumber();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Line Number";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnPreferredWidth() {
|
||||
return 75;
|
||||
}
|
||||
}
|
||||
|
||||
private class ContextTableColumn
|
||||
extends AbstractProgramBasedDynamicTableColumn<TextMatch, LocationReferenceContext> {
|
||||
|
||||
private ContextCellRenderer renderer = new ContextCellRenderer();
|
||||
|
||||
@Override
|
||||
public LocationReferenceContext getValue(TextMatch rowObject, Settings settings, Program p,
|
||||
ServiceProvider sp) throws IllegalArgumentException {
|
||||
return rowObject.getContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName() {
|
||||
return "Context";
|
||||
}
|
||||
|
||||
@Override
|
||||
public GColumnRenderer<LocationReferenceContext> getColumnRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
}
|
||||
|
||||
private class ContextCellRenderer
|
||||
extends AbstractGhidraColumnRenderer<LocationReferenceContext> {
|
||||
|
||||
{
|
||||
// the context uses html
|
||||
setHTMLRenderingEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
|
||||
|
||||
// initialize
|
||||
super.getTableCellRendererComponent(data);
|
||||
|
||||
TextMatch match = (TextMatch) data.getRowObject();
|
||||
LocationReferenceContext context = match.getContext();
|
||||
String text;
|
||||
if (match.isMultiLine()) {
|
||||
// multi-line matches create visual noise when showing colors, as of much of the
|
||||
// entire line matches
|
||||
text = context.getPlainText();
|
||||
}
|
||||
else {
|
||||
text = context.getBoldMatchingText();
|
||||
}
|
||||
setText(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFilterString(LocationReferenceContext context, Settings settings) {
|
||||
return context.getPlainText();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/* ###
|
||||
* 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.search;
|
||||
|
||||
import generic.json.Json;
|
||||
import ghidra.app.plugin.core.navigation.locationreferences.LocationReferenceContext;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressSet;
|
||||
import ghidra.program.model.listing.Function;
|
||||
|
||||
/**
|
||||
* A simple class that represents text found in Decompiler output.
|
||||
*/
|
||||
public class TextMatch {
|
||||
|
||||
private Function function;
|
||||
private AddressSet addresses;
|
||||
private LocationReferenceContext context;
|
||||
private int lineNumber;
|
||||
|
||||
private String searchText;
|
||||
private boolean isMultiLine;
|
||||
|
||||
TextMatch(Function function, AddressSet addresses, int lineNumber, String searchText,
|
||||
LocationReferenceContext context, boolean isMultiLine) {
|
||||
this.function = function;
|
||||
this.addresses = addresses;
|
||||
this.lineNumber = lineNumber;
|
||||
this.searchText = searchText;
|
||||
this.context = context;
|
||||
this.isMultiLine = isMultiLine;
|
||||
}
|
||||
|
||||
public Function getFunction() {
|
||||
return function;
|
||||
}
|
||||
|
||||
public LocationReferenceContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
public int getLineNumber() {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public Address getAddress() {
|
||||
if (addresses.isEmpty()) {
|
||||
return function.getEntryPoint();
|
||||
}
|
||||
|
||||
return addresses.getFirstRange().getMinAddress();
|
||||
}
|
||||
|
||||
public boolean isMultiLine() {
|
||||
return isMultiLine;
|
||||
}
|
||||
|
||||
public String getSearchText() {
|
||||
return searchText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this, "function", "context", "searchText");
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/* ###
|
||||
* 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.search;
|
||||
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.table.ProgramLocationTableRowMapper;
|
||||
|
||||
public class TextMatchToAddressTableRowMapper
|
||||
extends ProgramLocationTableRowMapper<TextMatch, Address> {
|
||||
|
||||
@Override
|
||||
public Address map(TextMatch rowObject, Program data, ServiceProvider serviceProvider) {
|
||||
return rowObject.getAddress();
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/* ###
|
||||
* 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.search;
|
||||
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.table.ProgramLocationTableRowMapper;
|
||||
|
||||
public class TextMatchToFunctionTableRowMapper
|
||||
extends ProgramLocationTableRowMapper<TextMatch, Function> {
|
||||
|
||||
@Override
|
||||
public Function map(TextMatch rowObject, Program data, ServiceProvider serviceProvider) {
|
||||
return rowObject.getFunction();
|
||||
}
|
||||
}
|
@ -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.
|
||||
@ -23,7 +23,8 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
import ghidra.program.database.*;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.database.data.ProgramDataTypeManager;
|
||||
import ghidra.program.model.data.*;
|
||||
|
||||
@ -81,14 +82,14 @@ public class FormatStringParserTest extends AbstractGenericTest {
|
||||
runFormatTest("%d%hi", expectedTypes2, false);
|
||||
|
||||
DataType[] expectedTypes3 =
|
||||
{ program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)),
|
||||
{ program.getDataTypeManager().getPointer(new PointerDataType(VoidDataType.dataType)),
|
||||
program.getDataTypeManager().getPointer(new CharDataType()) };
|
||||
runFormatTest("%p%*d%s", expectedTypes3, false);
|
||||
|
||||
DataType[] expectedTypes4 =
|
||||
{ program.getDataTypeManager().getPointer(new LongDoubleDataType()),
|
||||
program.getDataTypeManager().getPointer(new CharDataType()),
|
||||
program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)) };
|
||||
DataType[] expectedTypes4 = {
|
||||
program.getDataTypeManager().getPointer(new LongDoubleDataType()),
|
||||
program.getDataTypeManager().getPointer(new CharDataType()),
|
||||
program.getDataTypeManager().getPointer(new PointerDataType(VoidDataType.dataType)) };
|
||||
|
||||
runFormatTest("!:%12La%*d+=%2s%3p%*20d", expectedTypes4, false);
|
||||
|
||||
@ -106,8 +107,8 @@ public class FormatStringParserTest extends AbstractGenericTest {
|
||||
{ program.getDataTypeManager().getPointer(new CharDataType()), new LongDataType() };
|
||||
runFormatTest("#thisisatest%+-4.12s%#.1lin\nd2", expectedTypes2, true);
|
||||
|
||||
DataType[] expectedTypes3 = { new PointerDataType(DataType.VOID), new LongDoubleDataType(),
|
||||
new UnsignedCharDataType() };
|
||||
DataType[] expectedTypes3 = { new PointerDataType(VoidDataType.dataType),
|
||||
new LongDoubleDataType(), new UnsignedCharDataType() };
|
||||
runFormatTest("%01.3pp%%%#1.2Lg%%%%%hhXxn2", expectedTypes3, true);
|
||||
|
||||
DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(),
|
||||
|
@ -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.
|
||||
@ -501,12 +501,18 @@ public abstract class DockingAction implements DockingActionIf {
|
||||
|
||||
// menu path
|
||||
if (menuBarData != null) {
|
||||
buffer.append(" MENU PATH: ").append(
|
||||
menuBarData.getMenuPathAsString());
|
||||
buffer.append(" MENU PATH: ")
|
||||
.append(menuBarData.getMenuPathAsString());
|
||||
buffer.append('\n');
|
||||
buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup());
|
||||
buffer.append('\n');
|
||||
|
||||
String menuSubGroup = menuBarData.getMenuSubGroup();
|
||||
if (menuSubGroup != null) {
|
||||
buffer.append(" MENU SUB-GROUP: ").append(menuSubGroup);
|
||||
buffer.append('\n');
|
||||
}
|
||||
|
||||
String parentGroup = menuBarData.getParentMenuGroup();
|
||||
if (parentGroup != null) {
|
||||
buffer.append(" PARENT GROUP: ").append(parentGroup);
|
||||
@ -528,8 +534,8 @@ public abstract class DockingAction implements DockingActionIf {
|
||||
|
||||
// popup menu path
|
||||
if (popupMenuData != null) {
|
||||
buffer.append(" POPUP PATH: ").append(
|
||||
popupMenuData.getMenuPathAsString());
|
||||
buffer.append(" POPUP PATH: ")
|
||||
.append(popupMenuData.getMenuPathAsString());
|
||||
buffer.append('\n');
|
||||
buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup());
|
||||
buffer.append('\n');
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -15,8 +15,11 @@
|
||||
*/
|
||||
package generic.json;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.builder.*;
|
||||
|
||||
@ -135,16 +138,14 @@ public class Json extends ToStringStyle {
|
||||
* @return the string
|
||||
*/
|
||||
public static String toStringExclude(Object o, String... excludedFields) {
|
||||
ReflectionToStringBuilder builder = new ReflectionToStringBuilder(o,
|
||||
Json.WITH_NEWLINES);
|
||||
ReflectionToStringBuilder builder = new ReflectionToStringBuilder(o, Json.WITH_NEWLINES);
|
||||
builder.setExcludeFieldNames(excludedFields);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
// Future: update this class to use the order of the included fields to be the printed ordered
|
||||
private static class InclusiveReflectionToStringBuilder extends ReflectionToStringBuilder {
|
||||
|
||||
private String[] includedNames;
|
||||
private String[] includedNames = new String[0];
|
||||
|
||||
public InclusiveReflectionToStringBuilder(Object object) {
|
||||
super(object, WITH_NEWLINES);
|
||||
@ -156,27 +157,73 @@ public class Json extends ToStringStyle {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.includedNames != null &&
|
||||
Arrays.binarySearch(this.includedNames, field.getName()) >= 0) {
|
||||
return true;
|
||||
if (includedNames.length == 0) {
|
||||
return true; // this implies a programming error
|
||||
}
|
||||
|
||||
String fieldName = field.getName();
|
||||
for (String name : includedNames) {
|
||||
if (fieldName.equals(name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Overridden to control the order the field are listed. The parent class sorts by name; we
|
||||
// want output in the order specified by the user.
|
||||
@Override
|
||||
protected void appendFieldsIn(final Class<?> clazz) {
|
||||
if (clazz.isArray()) {
|
||||
super.appendFieldsIn(clazz);
|
||||
return;
|
||||
}
|
||||
|
||||
if (includedNames.length == 0) {
|
||||
super.appendFieldsIn(clazz);
|
||||
return;
|
||||
}
|
||||
|
||||
Field[] fields = clazz.getDeclaredFields();
|
||||
AccessibleObject.setAccessible(fields, true);
|
||||
Map<String, Field> fieldsByName =
|
||||
Arrays.stream(fields).collect(Collectors.toMap(f -> f.getName(), f -> f));
|
||||
for (String name : includedNames) {
|
||||
|
||||
Field field = fieldsByName.get(name);
|
||||
if (field == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (accept(field)) {
|
||||
try {
|
||||
// Field.get(Object) creates wrappers objects for primitive types.
|
||||
Object fieldValue = this.getValue(field);
|
||||
if (!isExcludeNullValues() || fieldValue != null) {
|
||||
this.append(name, fieldValue,
|
||||
!field.isAnnotationPresent(ToStringSummary.class));
|
||||
}
|
||||
}
|
||||
catch (IllegalAccessException ex) {
|
||||
throw new InternalError(
|
||||
"Unexpected IllegalAccessException: " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the names to be included
|
||||
* @param includeFieldNamesParam the names
|
||||
* @return this builder
|
||||
*/
|
||||
public ReflectionToStringBuilder setIncludeFieldNames(
|
||||
final String... includeFieldNamesParam) {
|
||||
public ReflectionToStringBuilder setIncludeFieldNames(String... includeFieldNamesParam) {
|
||||
if (includeFieldNamesParam == null) {
|
||||
this.includedNames = null;
|
||||
this.includedNames = new String[0];
|
||||
}
|
||||
else {
|
||||
this.includedNames = includeFieldNamesParam;
|
||||
Arrays.sort(this.includedNames);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
@ -4,9 +4,9 @@
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@ -16,6 +16,7 @@
|
||||
package ghidra.util.datastruct;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@ -31,7 +32,7 @@ import java.util.stream.StreamSupport;
|
||||
*
|
||||
* @param <T> the type
|
||||
*/
|
||||
public interface Accumulator<T> extends Iterable<T> {
|
||||
public interface Accumulator<T> extends Iterable<T>, Consumer<T> {
|
||||
|
||||
public void add(T t);
|
||||
|
||||
@ -50,4 +51,9 @@ public interface Accumulator<T> extends Iterable<T> {
|
||||
default Stream<T> stream() {
|
||||
return StreamSupport.stream(spliterator(), false);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void accept(T t) {
|
||||
add(t);
|
||||
}
|
||||
}
|
||||
|
@ -305,10 +305,11 @@ public class UserSearchUtils {
|
||||
*/
|
||||
private static String convertUserInputToRegex(String input, boolean allowGlobbing) {
|
||||
|
||||
// Note: Order is important! (due to how escape characters added and checked)
|
||||
String escaped = escapeEscapeCharacters(input);
|
||||
|
||||
String escaped = input;
|
||||
if (allowGlobbing) {
|
||||
|
||||
// Note: Order is important! (due to how escape characters added and checked)
|
||||
escaped = escapeEscapeCharacters(input);
|
||||
escaped = escapeNonGlobbingRegexCharacters(input);
|
||||
escaped = convertGlobbingCharactersToRegex(escaped);
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/* ###
|
||||
* 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 help.screenshot;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import docking.action.DockingActionIf;
|
||||
import ghidra.app.plugin.core.search.DecompilerTextFinderDialog;
|
||||
import ghidra.app.plugin.core.search.TextMatch;
|
||||
import ghidra.app.plugin.core.table.TableComponentProvider;
|
||||
import ghidra.util.table.GhidraProgramTableModel;
|
||||
|
||||
public class DecompilerTextFinderPluginScreenShots extends GhidraScreenShotGenerator {
|
||||
|
||||
@Test
|
||||
public void testDecompilerTextFinderDialog() {
|
||||
|
||||
DockingActionIf action = getAction(tool, "Search Decompiled Text");
|
||||
performAction(action, false);
|
||||
captureDialog();
|
||||
closeAllWindows();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecompilerTextFinderResultsTable() {
|
||||
|
||||
DockingActionIf action = getAction(tool, "Search Decompiled Text");
|
||||
performAction(action, false);
|
||||
DecompilerTextFinderDialog searchDialog =
|
||||
waitForDialogComponent(DecompilerTextFinderDialog.class);
|
||||
|
||||
String searchText = " = '\\0'";
|
||||
runSwing(() -> searchDialog.setSearchText(searchText));
|
||||
pressButtonByText(searchDialog, "Search", false);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
TableComponentProvider<TextMatch> tableProvider =
|
||||
waitForComponentProvider(TableComponentProvider.class);
|
||||
GhidraProgramTableModel<TextMatch> model = tableProvider.getModel();
|
||||
waitForTableModel(model);
|
||||
|
||||
// TOD capture entire window?
|
||||
captureProvider(tableProvider);
|
||||
close(searchDialog);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user