mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-02-16 15:40:14 +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
|
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".
|
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=
|
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>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H3><B>Reorder Columns</B></H3>
|
<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>
|
<H2><A name="RegularExpression"></A>Regular Expression</H2>
|
||||||
|
|
||||||
<BLOCKQUOTE>
|
<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>
|
</BLOCKQUOTE>
|
||||||
|
|
||||||
<H2><A name="RelocationTable"></A>Relocation Table</H2>
|
<H2><A name="RelocationTable"></A>Relocation Table</H2>
|
||||||
|
@ -124,7 +124,7 @@ public class ProgramLocationActionContext extends ProgramActionContext
|
|||||||
return functions;
|
return functions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Function getFunctionForLocation() {
|
protected Function getFunctionForLocation() {
|
||||||
if (location instanceof FunctionLocation functionLocation) {
|
if (location instanceof FunctionLocation functionLocation) {
|
||||||
Address functionAddress = functionLocation.getFunctionAddress();
|
Address functionAddress = functionLocation.getFunctionAddress();
|
||||||
return program.getFunctionManager().getFunctionAt(functionAddress);
|
return program.getFunctionManager().getFunctionAt(functionAddress);
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -25,6 +25,7 @@ import docking.action.DockingAction;
|
|||||||
import docking.action.MenuData;
|
import docking.action.MenuData;
|
||||||
import generic.theme.GIcon;
|
import generic.theme.GIcon;
|
||||||
import ghidra.app.CorePluginPackage;
|
import ghidra.app.CorePluginPackage;
|
||||||
|
import ghidra.app.context.FunctionSupplierContext;
|
||||||
import ghidra.app.context.ListingActionContext;
|
import ghidra.app.context.ListingActionContext;
|
||||||
import ghidra.app.plugin.PluginCategoryNames;
|
import ghidra.app.plugin.PluginCategoryNames;
|
||||||
import ghidra.app.plugin.ProgramPlugin;
|
import ghidra.app.plugin.ProgramPlugin;
|
||||||
@ -145,7 +146,15 @@ public class CallTreePlugin extends ProgramPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAddToPopup(ActionContext context) {
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -92,6 +92,18 @@ public class LocationReferenceContext {
|
|||||||
return buffy.toString();
|
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
|
* Returns HTML text for this context. Any matching items embedded in the returned string will
|
||||||
* be bold.
|
* be bold.
|
||||||
@ -142,7 +154,7 @@ public class LocationReferenceContext {
|
|||||||
|
|
||||||
abstract String getHtmlText();
|
abstract String getHtmlText();
|
||||||
|
|
||||||
abstract String getText(String start, String end);
|
abstract String getDebugText();
|
||||||
|
|
||||||
static String fixBreakingSpaces(String s) {
|
static String fixBreakingSpaces(String s) {
|
||||||
String updated = s.replaceAll("\\s", " ");
|
String updated = s.replaceAll("\\s", " ");
|
||||||
@ -165,8 +177,8 @@ public class LocationReferenceContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String getText(String start, String end) {
|
String getDebugText() {
|
||||||
return text; // we don't decorate
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -187,8 +199,8 @@ public class LocationReferenceContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
String getText(String start, String end) {
|
String getDebugText() {
|
||||||
return start + text + end;
|
return " [[ " + text + " ]] ";
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -58,6 +58,19 @@ public class LocationReferenceContextBuilder {
|
|||||||
return this;
|
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}
|
* Builds a {@link LocationReferenceContext} using the text supplied via the {@code append}
|
||||||
* methods.
|
* methods.
|
||||||
@ -67,6 +80,14 @@ public class LocationReferenceContextBuilder {
|
|||||||
return new LocationReferenceContext(parts);
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return Json.toString(this);
|
return Json.toString(this);
|
||||||
|
@ -150,8 +150,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
|||||||
if (result == null) {
|
if (result == null) {
|
||||||
searchDialog.setStatusText("Not found");
|
searchDialog.setStatusText("Not found");
|
||||||
}
|
}
|
||||||
else if (result.programLocation()
|
else if (result.programLocation().equals(currentLocation)) {
|
||||||
.equals(currentLocation)) {
|
|
||||||
searchNext(searchTask.getProgram(), searchNavigatable, textSearcher);
|
searchNext(searchTask.getProgram(), searchNavigatable, textSearcher);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -376,8 +375,9 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
|||||||
* Create the action for to pop up the search dialog.
|
* Create the action for to pop up the search dialog.
|
||||||
*/
|
*/
|
||||||
private void createActions() {
|
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())
|
new ActionBuilder("Search Text", getName())
|
||||||
.menuPath("&Search", "Program &Text...")
|
.menuPath("&Search", "Program &Text...")
|
||||||
.menuGroup("search", subGroup)
|
.menuGroup("search", subGroup)
|
||||||
@ -406,6 +406,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
|||||||
searchDialog.repeatSearch();
|
searchDialog.repeatSearch();
|
||||||
})
|
})
|
||||||
.buildAndInstall(tool);
|
.buildAndInstall(tool);
|
||||||
|
//@formatter:on
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateNavigatable(ActionContext context) {
|
protected void updateNavigatable(ActionContext context) {
|
||||||
@ -467,8 +468,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
|||||||
String textSelection = navigatable.getTextSelection();
|
String textSelection = navigatable.getTextSelection();
|
||||||
ProgramLocation location = navigatable.getLocation();
|
ProgramLocation location = navigatable.getLocation();
|
||||||
Address address = location.getAddress();
|
Address address = location.getAddress();
|
||||||
Listing listing = context.getProgram()
|
Listing listing = context.getProgram().getListing();
|
||||||
.getListing();
|
|
||||||
CodeUnit codeUnit = listing.getCodeUnitAt(address);
|
CodeUnit codeUnit = listing.getCodeUnitAt(address);
|
||||||
boolean isInstruction = false;
|
boolean isInstruction = false;
|
||||||
if (textSelection != null) {
|
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
|
// must have completed too fast for the provider to be set; try something cute
|
||||||
Component focusOwner =
|
Component focusOwner =
|
||||||
KeyboardFocusManager.getCurrentKeyboardFocusManager()
|
KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
|
||||||
.getFocusOwner();
|
|
||||||
return focusOwner; // assume this IS the provider
|
return focusOwner; // assume this IS the provider
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -628,8 +627,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
|||||||
@Override
|
@Override
|
||||||
public Highlight[] createHighlights(String text, ListingField field, int cursorTextOffset) {
|
public Highlight[] createHighlights(String text, ListingField field, int cursorTextOffset) {
|
||||||
|
|
||||||
Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory()
|
Class<? extends FieldFactory> fieldFactoryClass = field.getFieldFactory().getClass();
|
||||||
.getClass();
|
|
||||||
|
|
||||||
if (!doHighlight) {
|
if (!doHighlight) {
|
||||||
return NO_HIGHLIGHTS;
|
return NO_HIGHLIGHTS;
|
||||||
@ -652,8 +650,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
|||||||
return getAllHighlights(text, cursorTextOffset);
|
return getAllHighlights(text, cursorTextOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
Address address = searchResult.programLocation()
|
Address address = searchResult.programLocation().getAddress();
|
||||||
.getAddress();
|
|
||||||
ProxyObj<?> proxy = field.getProxy();
|
ProxyObj<?> proxy = field.getProxy();
|
||||||
if (proxy.contains(address)) {
|
if (proxy.contains(address)) {
|
||||||
return getSingleSearchHighlight(text, field, cursorTextOffset);
|
return getSingleSearchHighlight(text, field, cursorTextOffset);
|
||||||
@ -745,8 +742,7 @@ public class SearchTextPlugin extends ProgramPlugin implements OptionsChangeList
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<? extends FieldFactory> factoryClass = field.getFieldFactory()
|
Class<? extends FieldFactory> factoryClass = field.getFieldFactory().getClass();
|
||||||
.getClass();
|
|
||||||
if (searchOptions.searchComments()) {
|
if (searchOptions.searchComments()) {
|
||||||
if (factoryClass == PreCommentFieldFactory.class ||
|
if (factoryClass == PreCommentFieldFactory.class ||
|
||||||
factoryClass == PlateFieldFactory.class ||
|
factoryClass == PlateFieldFactory.class ||
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -384,6 +384,10 @@ public class TableComponentProvider<T> extends ComponentProviderAdapter
|
|||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public GhidraTable getTable() {
|
||||||
|
return threadedPanel.getTable();
|
||||||
|
}
|
||||||
|
|
||||||
private void updateTitle() {
|
private void updateTitle() {
|
||||||
setSubTitle(generateSubTitle());
|
setSubTitle(generateSubTitle());
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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;
|
import ghidra.util.StringUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class is used to convert a C/C++ language
|
* This class is used to convert a C/C++ language token group into readable C/C++ code.
|
||||||
* token group into readable C/C++ code.
|
|
||||||
*/
|
*/
|
||||||
public class PrettyPrinter {
|
public class PrettyPrinter {
|
||||||
|
|
||||||
/**
|
private final static NameTransformer IDENTITY = new IdentityNameTransformer();
|
||||||
* The indent string to use when printing.
|
|
||||||
*/
|
|
||||||
public final static String INDENT_STRING = " ";
|
public final static String INDENT_STRING = " ";
|
||||||
|
|
||||||
private Function function;
|
private Function function;
|
||||||
@ -51,7 +49,7 @@ public class PrettyPrinter {
|
|||||||
public PrettyPrinter(Function function, ClangTokenGroup tokgroup, NameTransformer transformer) {
|
public PrettyPrinter(Function function, ClangTokenGroup tokgroup, NameTransformer transformer) {
|
||||||
this.function = function;
|
this.function = function;
|
||||||
this.tokgroup = tokgroup;
|
this.tokgroup = tokgroup;
|
||||||
this.transformer = (transformer != null) ? transformer : new IdentityNameTransformer();
|
this.transformer = transformer != null ? transformer : IDENTITY;
|
||||||
flattenLines();
|
flattenLines();
|
||||||
padEmptyLines();
|
padEmptyLines();
|
||||||
}
|
}
|
||||||
@ -72,8 +70,7 @@ public class PrettyPrinter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of the C language lines contained in the
|
* Returns a list of the C language lines contained in the C language token group.
|
||||||
* C language token group.
|
|
||||||
* @return a list of the C language lines
|
* @return a list of the C language lines
|
||||||
*/
|
*/
|
||||||
public List<ClangLine> getLines() {
|
public List<ClangLine> getLines() {
|
||||||
@ -81,37 +78,51 @@ public class PrettyPrinter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prints the C language token group
|
* Prints the C language token group into a string of C code.
|
||||||
* into a string of C code.
|
|
||||||
* @return a string of readable C code
|
* @return a string of readable C code
|
||||||
*/
|
*/
|
||||||
public DecompiledFunction print() {
|
public DecompiledFunction print() {
|
||||||
StringBuilder buff = new StringBuilder();
|
StringBuilder buff = new StringBuilder();
|
||||||
for (ClangLine line : lines) {
|
for (ClangLine line : lines) {
|
||||||
buff.append(line.getIndentString());
|
getText(buff, line, transformer);
|
||||||
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);
|
|
||||||
}
|
|
||||||
buff.append(StringUtilities.LINE_SEPARATOR);
|
buff.append(StringUtilities.LINE_SEPARATOR);
|
||||||
}
|
}
|
||||||
return new DecompiledFunction(findSignature(), buff.toString());
|
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() {
|
private String findSignature() {
|
||||||
int nChildren = tokgroup.numChildren();
|
int nChildren = tokgroup.numChildren();
|
||||||
for (int i = 0; i < nChildren; ++i) {
|
for (int i = 0; i < nChildren; ++i) {
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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.context.RestrictedAddressSetContext;
|
||||||
import ghidra.app.decompiler.*;
|
import ghidra.app.decompiler.*;
|
||||||
import ghidra.app.decompiler.component.DecompilerPanel;
|
import ghidra.app.decompiler.component.DecompilerPanel;
|
||||||
|
import ghidra.app.decompiler.component.DecompilerUtils;
|
||||||
import ghidra.framework.plugintool.PluginTool;
|
import ghidra.framework.plugintool.PluginTool;
|
||||||
import ghidra.program.model.address.Address;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Function;
|
import ghidra.program.model.listing.Function;
|
||||||
@ -123,8 +124,8 @@ public class DecompilerActionContext extends NavigatableActionContext
|
|||||||
if (lineNumber != 0) {
|
if (lineNumber != 0) {
|
||||||
return lineNumber;
|
return lineNumber;
|
||||||
}
|
}
|
||||||
getTokenAtCursor();
|
ClangToken token = getTokenAtCursor();
|
||||||
return tokenAtCursor == null ? 0 : tokenAtCursor.getLineParent().getLineNumber();
|
return token == null ? 0 : token.getLineParent().getLineNumber();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DecompilerPanel getDecompilerPanel() {
|
public DecompilerPanel getDecompilerPanel() {
|
||||||
@ -152,6 +153,22 @@ public class DecompilerActionContext extends NavigatableActionContext
|
|||||||
getComponentProvider().getController().setStatusMessage(msg);
|
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)}.
|
* The companion method of {@link #checkActionEnablement(Supplier)}.
|
||||||
*
|
*
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -1062,7 +1062,7 @@ public class DecompilerProvider extends NavigatableComponentProviderAdapter
|
|||||||
//
|
//
|
||||||
// Search
|
// Search
|
||||||
//
|
//
|
||||||
String searchGroup = "comment2 - Search Group";
|
String searchGroup = "Comment2 - Search Group";
|
||||||
subGroupPosition = 0; // reset for the next group
|
subGroupPosition = 0; // reset for the next group
|
||||||
|
|
||||||
FindAction findAction = new FindAction();
|
FindAction findAction = new FindAction();
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* 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/distributableGhidraModule.gradle"
|
||||||
apply from: "$rootProject.projectDir/gradle/javaProject.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/jacocoProject.gradle"
|
||||||
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
apply from: "$rootProject.projectDir/gradle/javaTestProject.gradle"
|
||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
|
@ -1,3 +1,8 @@
|
|||||||
##VERSION: 2.0
|
##VERSION: 2.0
|
||||||
Module.manifest||GHIDRA||||END|
|
Module.manifest||GHIDRA||||END|
|
||||||
data/ExtensionPoint.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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -23,7 +23,8 @@ import org.junit.Before;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import generic.test.AbstractGenericTest;
|
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.database.data.ProgramDataTypeManager;
|
||||||
import ghidra.program.model.data.*;
|
import ghidra.program.model.data.*;
|
||||||
|
|
||||||
@ -81,14 +82,14 @@ public class FormatStringParserTest extends AbstractGenericTest {
|
|||||||
runFormatTest("%d%hi", expectedTypes2, false);
|
runFormatTest("%d%hi", expectedTypes2, false);
|
||||||
|
|
||||||
DataType[] expectedTypes3 =
|
DataType[] expectedTypes3 =
|
||||||
{ program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)),
|
{ program.getDataTypeManager().getPointer(new PointerDataType(VoidDataType.dataType)),
|
||||||
program.getDataTypeManager().getPointer(new CharDataType()) };
|
program.getDataTypeManager().getPointer(new CharDataType()) };
|
||||||
runFormatTest("%p%*d%s", expectedTypes3, false);
|
runFormatTest("%p%*d%s", expectedTypes3, false);
|
||||||
|
|
||||||
DataType[] expectedTypes4 =
|
DataType[] expectedTypes4 = {
|
||||||
{ program.getDataTypeManager().getPointer(new LongDoubleDataType()),
|
program.getDataTypeManager().getPointer(new LongDoubleDataType()),
|
||||||
program.getDataTypeManager().getPointer(new CharDataType()),
|
program.getDataTypeManager().getPointer(new CharDataType()),
|
||||||
program.getDataTypeManager().getPointer(new PointerDataType(DataType.VOID)) };
|
program.getDataTypeManager().getPointer(new PointerDataType(VoidDataType.dataType)) };
|
||||||
|
|
||||||
runFormatTest("!:%12La%*d+=%2s%3p%*20d", expectedTypes4, false);
|
runFormatTest("!:%12La%*d+=%2s%3p%*20d", expectedTypes4, false);
|
||||||
|
|
||||||
@ -106,8 +107,8 @@ public class FormatStringParserTest extends AbstractGenericTest {
|
|||||||
{ program.getDataTypeManager().getPointer(new CharDataType()), new LongDataType() };
|
{ program.getDataTypeManager().getPointer(new CharDataType()), new LongDataType() };
|
||||||
runFormatTest("#thisisatest%+-4.12s%#.1lin\nd2", expectedTypes2, true);
|
runFormatTest("#thisisatest%+-4.12s%#.1lin\nd2", expectedTypes2, true);
|
||||||
|
|
||||||
DataType[] expectedTypes3 = { new PointerDataType(DataType.VOID), new LongDoubleDataType(),
|
DataType[] expectedTypes3 = { new PointerDataType(VoidDataType.dataType),
|
||||||
new UnsignedCharDataType() };
|
new LongDoubleDataType(), new UnsignedCharDataType() };
|
||||||
runFormatTest("%01.3pp%%%#1.2Lg%%%%%hhXxn2", expectedTypes3, true);
|
runFormatTest("%01.3pp%%%#1.2Lg%%%%%hhXxn2", expectedTypes3, true);
|
||||||
|
|
||||||
DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(),
|
DataType[] expectedTypes4 = { new IntegerDataType(), new IntegerDataType(),
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -501,12 +501,18 @@ public abstract class DockingAction implements DockingActionIf {
|
|||||||
|
|
||||||
// menu path
|
// menu path
|
||||||
if (menuBarData != null) {
|
if (menuBarData != null) {
|
||||||
buffer.append(" MENU PATH: ").append(
|
buffer.append(" MENU PATH: ")
|
||||||
menuBarData.getMenuPathAsString());
|
.append(menuBarData.getMenuPathAsString());
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup());
|
buffer.append(" MENU GROUP: ").append(menuBarData.getMenuGroup());
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
|
|
||||||
|
String menuSubGroup = menuBarData.getMenuSubGroup();
|
||||||
|
if (menuSubGroup != null) {
|
||||||
|
buffer.append(" MENU SUB-GROUP: ").append(menuSubGroup);
|
||||||
|
buffer.append('\n');
|
||||||
|
}
|
||||||
|
|
||||||
String parentGroup = menuBarData.getParentMenuGroup();
|
String parentGroup = menuBarData.getParentMenuGroup();
|
||||||
if (parentGroup != null) {
|
if (parentGroup != null) {
|
||||||
buffer.append(" PARENT GROUP: ").append(parentGroup);
|
buffer.append(" PARENT GROUP: ").append(parentGroup);
|
||||||
@ -528,8 +534,8 @@ public abstract class DockingAction implements DockingActionIf {
|
|||||||
|
|
||||||
// popup menu path
|
// popup menu path
|
||||||
if (popupMenuData != null) {
|
if (popupMenuData != null) {
|
||||||
buffer.append(" POPUP PATH: ").append(
|
buffer.append(" POPUP PATH: ")
|
||||||
popupMenuData.getMenuPathAsString());
|
.append(popupMenuData.getMenuPathAsString());
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup());
|
buffer.append(" POPUP GROUP: ").append(popupMenuData.getMenuGroup());
|
||||||
buffer.append('\n');
|
buffer.append('\n');
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -15,8 +15,11 @@
|
|||||||
*/
|
*/
|
||||||
package generic.json;
|
package generic.json;
|
||||||
|
|
||||||
|
import java.lang.reflect.AccessibleObject;
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.apache.commons.lang3.builder.*;
|
import org.apache.commons.lang3.builder.*;
|
||||||
|
|
||||||
@ -135,16 +138,14 @@ public class Json extends ToStringStyle {
|
|||||||
* @return the string
|
* @return the string
|
||||||
*/
|
*/
|
||||||
public static String toStringExclude(Object o, String... excludedFields) {
|
public static String toStringExclude(Object o, String... excludedFields) {
|
||||||
ReflectionToStringBuilder builder = new ReflectionToStringBuilder(o,
|
ReflectionToStringBuilder builder = new ReflectionToStringBuilder(o, Json.WITH_NEWLINES);
|
||||||
Json.WITH_NEWLINES);
|
|
||||||
builder.setExcludeFieldNames(excludedFields);
|
builder.setExcludeFieldNames(excludedFields);
|
||||||
return builder.toString();
|
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 static class InclusiveReflectionToStringBuilder extends ReflectionToStringBuilder {
|
||||||
|
|
||||||
private String[] includedNames;
|
private String[] includedNames = new String[0];
|
||||||
|
|
||||||
public InclusiveReflectionToStringBuilder(Object object) {
|
public InclusiveReflectionToStringBuilder(Object object) {
|
||||||
super(object, WITH_NEWLINES);
|
super(object, WITH_NEWLINES);
|
||||||
@ -156,27 +157,73 @@ public class Json extends ToStringStyle {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.includedNames != null &&
|
if (includedNames.length == 0) {
|
||||||
Arrays.binarySearch(this.includedNames, field.getName()) >= 0) {
|
return true; // this implies a programming error
|
||||||
return true;
|
}
|
||||||
|
|
||||||
|
String fieldName = field.getName();
|
||||||
|
for (String name : includedNames) {
|
||||||
|
if (fieldName.equals(name)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
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
|
* Sets the names to be included
|
||||||
* @param includeFieldNamesParam the names
|
* @param includeFieldNamesParam the names
|
||||||
* @return this builder
|
* @return this builder
|
||||||
*/
|
*/
|
||||||
public ReflectionToStringBuilder setIncludeFieldNames(
|
public ReflectionToStringBuilder setIncludeFieldNames(String... includeFieldNamesParam) {
|
||||||
final String... includeFieldNamesParam) {
|
|
||||||
if (includeFieldNamesParam == null) {
|
if (includeFieldNamesParam == null) {
|
||||||
this.includedNames = null;
|
this.includedNames = new String[0];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.includedNames = includeFieldNamesParam;
|
this.includedNames = includeFieldNamesParam;
|
||||||
Arrays.sort(this.includedNames);
|
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@ -16,6 +16,7 @@
|
|||||||
package ghidra.util.datastruct;
|
package ghidra.util.datastruct;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import java.util.stream.StreamSupport;
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ import java.util.stream.StreamSupport;
|
|||||||
*
|
*
|
||||||
* @param <T> the type
|
* @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);
|
public void add(T t);
|
||||||
|
|
||||||
@ -50,4 +51,9 @@ public interface Accumulator<T> extends Iterable<T> {
|
|||||||
default Stream<T> stream() {
|
default Stream<T> stream() {
|
||||||
return StreamSupport.stream(spliterator(), false);
|
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) {
|
private static String convertUserInputToRegex(String input, boolean allowGlobbing) {
|
||||||
|
|
||||||
// Note: Order is important! (due to how escape characters added and checked)
|
String escaped = input;
|
||||||
String escaped = escapeEscapeCharacters(input);
|
|
||||||
|
|
||||||
if (allowGlobbing) {
|
if (allowGlobbing) {
|
||||||
|
|
||||||
|
// Note: Order is important! (due to how escape characters added and checked)
|
||||||
|
escaped = escapeEscapeCharacters(input);
|
||||||
escaped = escapeNonGlobbingRegexCharacters(input);
|
escaped = escapeNonGlobbingRegexCharacters(input);
|
||||||
escaped = convertGlobbingCharactersToRegex(escaped);
|
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