mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-22 06:42:56 +08:00
Merge remote-tracking branch
'origin/GP-6561_dragonmacher_PR-8993_hay-mon_local_hndl' (Closes #8993)
This commit is contained in:
@@ -104,11 +104,11 @@
|
||||
Notes:
|
||||
|
||||
<ul style="margin-left: 10px;">
|
||||
<li>
|
||||
If you provide a symbol name and not an address, then you will need to
|
||||
fully-qualify the symbol name if it is not in the global namespace (e.g.,
|
||||
FunctionFoo::param1 to link to a specific function parameter).
|
||||
</li>
|
||||
<li>
|
||||
If you provide a symbol name and not an address, then you will need to
|
||||
fully-qualify the symbol name if it is not in the global namespace (e.g.,
|
||||
FunctionFoo::param1 to link to a specific function parameter).
|
||||
</li>
|
||||
<li>
|
||||
If you specify a symbol name instead of an address, then the text of the comment
|
||||
will be changed to use the address of that symbol.
|
||||
@@ -285,6 +285,51 @@
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="5%">Variable<BR>
|
||||
</TD>
|
||||
|
||||
<TD valign="top" width="15%">Displays the text of the given variable or parameter
|
||||
for a given function as a hyperlink.<BR>
|
||||
<BR>
|
||||
<BR>
|
||||
Notes:
|
||||
|
||||
<ul style="margin-left: 10px;">
|
||||
<li>
|
||||
This annotation will modify itself to use the variable and function
|
||||
address to survive renaming of the variable and the function.
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</TD>
|
||||
|
||||
|
||||
<TD valign="top" width="12%">
|
||||
<OL style="margin-left: 15px;">
|
||||
<LI>variable/parameter name (@variable) or hash identifier</LI>
|
||||
<LI>[function name or address]<BR>
|
||||
</LI>
|
||||
</OL>
|
||||
</TD>
|
||||
|
||||
<TD valign="top" width="10%">
|
||||
<UL style="margin-left: 10px;">
|
||||
<LI>@variable</LI>
|
||||
<LI>@var</LI>
|
||||
</UL>
|
||||
</TD>
|
||||
|
||||
<TD valign="top" width="25%">
|
||||
<UL style="margin-left: 10px;">
|
||||
<LI>{@variable "i"}</LI>
|
||||
|
||||
<LI>{@var "argv" "main"}</LI>
|
||||
</LI>
|
||||
</UL>
|
||||
</TD>
|
||||
</TR>
|
||||
|
||||
<TR>
|
||||
<TD valign="top" width="5%"><I>Discovered Annotations</I><BR>
|
||||
</TD>
|
||||
|
||||
@@ -15,10 +15,13 @@
|
||||
*/
|
||||
package ghidra.app.cmd.comments;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.app.util.viewer.field.CommentUtils;
|
||||
import ghidra.framework.cmd.Command;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.util.exception.AssertException;
|
||||
|
||||
/**
|
||||
* Command for editing and removing comments at an address.
|
||||
@@ -31,7 +34,6 @@ public class SetCommentsCmd implements Command<Program> {
|
||||
private String eolComment;
|
||||
private String plateComment;
|
||||
private String repeatableComment;
|
||||
private String msg;
|
||||
|
||||
/**
|
||||
* Construct command for setting all the different types of comments at an
|
||||
@@ -54,65 +56,50 @@ public class SetCommentsCmd implements Command<Program> {
|
||||
this.repeatableComment = newRepeatableComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the edit action.
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Set Comments";
|
||||
}
|
||||
|
||||
/**
|
||||
* return true if the newValue and oldValue are different
|
||||
* @param newValue the value that we desire to set
|
||||
* @param oldValue the existing value
|
||||
* @return boolean
|
||||
*/
|
||||
private boolean commentChanged(String newValue, String oldValue) {
|
||||
if (newValue == null && oldValue == null) {
|
||||
return false;
|
||||
}
|
||||
if (newValue != null) {
|
||||
return !newValue.equals(oldValue);
|
||||
}
|
||||
return !oldValue.equals(newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean applyTo(Program program) {
|
||||
CodeUnit cu = getCodeUnit(program);
|
||||
if (cu == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cu != null) {
|
||||
if (commentChanged(cu.getComment(CommentType.PRE), preComment)) {
|
||||
String updatedPreComment = CommentUtils.fixupAnnotations(preComment, program);
|
||||
updatedPreComment = CommentUtils.sanitize(updatedPreComment);
|
||||
cu.setComment(CommentType.PRE, updatedPreComment);
|
||||
}
|
||||
if (commentChanged(cu.getComment(CommentType.POST), postComment)) {
|
||||
String updatedPostComment = CommentUtils.fixupAnnotations(postComment, program);
|
||||
updatedPostComment = CommentUtils.sanitize(updatedPostComment);
|
||||
cu.setComment(CommentType.POST, updatedPostComment);
|
||||
}
|
||||
if (commentChanged(cu.getComment(CommentType.EOL), eolComment)) {
|
||||
String updatedEOLComment = CommentUtils.fixupAnnotations(eolComment, program);
|
||||
updatedEOLComment = CommentUtils.sanitize(updatedEOLComment);
|
||||
cu.setComment(CommentType.EOL, updatedEOLComment);
|
||||
}
|
||||
if (commentChanged(cu.getComment(CommentType.PLATE), plateComment)) {
|
||||
String updatedPlateComment = CommentUtils.fixupAnnotations(plateComment, program);
|
||||
updatedPlateComment = CommentUtils.sanitize(updatedPlateComment);
|
||||
cu.setComment(CommentType.PLATE, updatedPlateComment);
|
||||
}
|
||||
if (commentChanged(cu.getComment(CommentType.REPEATABLE), repeatableComment)) {
|
||||
String updatedRepeatableComment =
|
||||
CommentUtils.fixupAnnotations(repeatableComment, program);
|
||||
updatedRepeatableComment = CommentUtils.sanitize(updatedRepeatableComment);
|
||||
cu.setComment(CommentType.REPEATABLE, updatedRepeatableComment);
|
||||
Address addr = cu.getAddress();
|
||||
for (CommentType type : CommentType.values()) {
|
||||
String newComment = getComment(type);
|
||||
String oldComment = cu.getComment(type);
|
||||
if (Objects.equals(oldComment, newComment)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String fixedComment = CommentUtils.fixupAnnotations(newComment, program, addr);
|
||||
fixedComment = CommentUtils.sanitize(fixedComment);
|
||||
cu.setComment(type, fixedComment);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private String getComment(CommentType type) {
|
||||
switch (type) {
|
||||
case EOL:
|
||||
return eolComment;
|
||||
case PLATE:
|
||||
return plateComment;
|
||||
case POST:
|
||||
return postComment;
|
||||
case PRE:
|
||||
return preComment;
|
||||
case REPEATABLE:
|
||||
return repeatableComment;
|
||||
default:
|
||||
throw new AssertException("Unhandled CommentType");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the code unit from the program location provider.
|
||||
*
|
||||
@@ -134,7 +121,6 @@ public class SetCommentsCmd implements Command<Program> {
|
||||
|
||||
@Override
|
||||
public String getStatusMsg() {
|
||||
return msg;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-25
@@ -15,24 +15,23 @@
|
||||
*/
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Objects;
|
||||
|
||||
import docking.widgets.fieldpanel.field.AttributedString;
|
||||
import docking.widgets.fieldpanel.field.FieldElement;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.util.ProgramLocation;
|
||||
import ghidra.util.bean.field.AnnotatedTextFieldElement;
|
||||
import ghidra.util.classfinder.ExtensionPoint;
|
||||
|
||||
/**
|
||||
* NOTE: ALL AnnotatedStringHandler CLASSES MUST END IN "StringHandler". If not,
|
||||
* the ClassSearcher will not find them.
|
||||
*
|
||||
* An interface that describes a string that has been annotated, which allows for adding
|
||||
* rendering and functionality to strings.
|
||||
* <p>
|
||||
* NOTE: ALL AnnotatedStringHandler class names MUST END IN "StringHandler". If not, the
|
||||
* ClassSearcher will not find them.
|
||||
*/
|
||||
public interface AnnotatedStringHandler extends ExtensionPoint {
|
||||
|
||||
@@ -44,7 +43,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
|
||||
* @param s string to escape
|
||||
* @return escaped string
|
||||
*/
|
||||
static String escapeAnnotationPart(String s) {
|
||||
public static String escapeAnnotationPart(String s) {
|
||||
s = Objects.requireNonNullElse(s, "");
|
||||
String origStr = s;
|
||||
if (s.indexOf('"') >= 0) {
|
||||
@@ -56,14 +55,6 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
|
||||
return s;
|
||||
}
|
||||
|
||||
AnnotatedMouseHandler DUMMY_MOUSE_HANDLER = new AnnotatedMouseHandler() {
|
||||
@Override
|
||||
public boolean handleMouseClick(ProgramLocation location, MouseEvent mouseEvent,
|
||||
ServiceProvider serviceProvider) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an {@link FieldElement} based upon the give array of Strings. The first String
|
||||
* in the list is expected to be the annotation tag used to create the annotation. At the
|
||||
@@ -80,7 +71,7 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
|
||||
* @throws AnnotationException if the given text data does not fit the expected format for
|
||||
* the given handler implementation.
|
||||
*/
|
||||
AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
|
||||
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
|
||||
Program program) throws AnnotationException;
|
||||
|
||||
/**
|
||||
@@ -89,42 +80,47 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
|
||||
*
|
||||
* @return the annotation string names that this AnnotatedStringHandler supports.
|
||||
*/
|
||||
String[] getSupportedAnnotations();
|
||||
public String[] getSupportedAnnotations();
|
||||
|
||||
/**
|
||||
* A method that is notified when an annotation is clicked. Returns true if this annotation
|
||||
* handles the click; return false if this annotation does not do anything with the click.
|
||||
*
|
||||
* @param annotationParts The constituent parts of the annotation
|
||||
* @param sourceNavigatable The location in the program that was clicked.
|
||||
* @param serviceProvider A service provider for needed services.
|
||||
* @param text the constituent parts of the annotation
|
||||
* @param sourceNavigatable the location in the program that was clicked.
|
||||
* @param serviceProvider the service provider for needed services.
|
||||
* @return true if this annotation handles the click; return false if this annotation does
|
||||
* not do anything with the click.
|
||||
*/
|
||||
boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable,
|
||||
public boolean handleMouseClick(String[] text, Navigatable sourceNavigatable,
|
||||
ServiceProvider serviceProvider);
|
||||
|
||||
/**
|
||||
* Returns the String that represents the GUI presence of this option
|
||||
* @return the String to display in GUI components.
|
||||
*/
|
||||
String getDisplayString();
|
||||
public String getDisplayString();
|
||||
|
||||
/**
|
||||
* Returns an example string of how the annotation is used
|
||||
* @return the example of how this is used.
|
||||
*/
|
||||
String getPrototypeString();
|
||||
public String getPrototypeString();
|
||||
|
||||
/**
|
||||
* Returns an example string of how the annotation is used
|
||||
* @param displayText The text that may be wrapped, cannot be null
|
||||
* @param displayText the text that may be wrapped, will not be null
|
||||
* @return the example of how this is used.
|
||||
*/
|
||||
default String getPrototypeString(String displayText) {
|
||||
public default String getPrototypeString(String displayText) {
|
||||
return getPrototypeString();
|
||||
}
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public default String[] modify(String[] text, Program program) {
|
||||
return modify(text, program, Address.NO_ADDRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array with modifications by the annotation; null otherwise. This method will be
|
||||
* called by the framework when comments are created, before they are applied. This allows the
|
||||
@@ -132,9 +128,10 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
|
||||
*
|
||||
* @param text the array of annotation parts to modify
|
||||
* @param program the program
|
||||
* @param addr address of the annotation in the program
|
||||
* @return the modified array; null otherwise
|
||||
*/
|
||||
default String[] modify(String[] text, Program program) {
|
||||
public default String[] modify(String[] text, Program program, Address addr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,9 +50,10 @@ public class CommentUtils {
|
||||
*
|
||||
* @param rawCommentText the text to be updated
|
||||
* @param program the program associated with the comment
|
||||
* @param addr address of comment in program
|
||||
* @return the updated string
|
||||
*/
|
||||
public static String fixupAnnotations(String rawCommentText, Program program) {
|
||||
public static String fixupAnnotations(String rawCommentText, Program program, Address addr) {
|
||||
|
||||
if (rawCommentText == null) {
|
||||
return null;
|
||||
@@ -66,7 +67,7 @@ public class CommentUtils {
|
||||
String[] annotationParts = annotation.getAnnotationParts();
|
||||
AnnotatedStringHandler handler = getAnnotationHandler(annotationParts);
|
||||
|
||||
String[] updatedParts = handler.modify(annotationParts, program);
|
||||
String[] updatedParts = handler.modify(annotationParts, program, addr);
|
||||
if (updatedParts == null) {
|
||||
return annotation; // nothing to change
|
||||
}
|
||||
@@ -163,8 +164,7 @@ public class CommentUtils {
|
||||
text = StringUtilities.convertTabsToSpaces(text);
|
||||
|
||||
int column = 0;
|
||||
List<CommentPart> parts =
|
||||
doParseTextIntoParts(text, fixerUpper, program);
|
||||
List<CommentPart> parts = doParseTextIntoParts(text, fixerUpper, program);
|
||||
List<FieldElement> fields = new ArrayList<>();
|
||||
for (CommentPart part : parts) {
|
||||
|
||||
|
||||
+9
-3
@@ -32,12 +32,13 @@ import ghidra.util.Msg;
|
||||
* An annotated string handler that handles annotations that begin with
|
||||
* {@link #SUPPORTED_ANNOTATIONS}. This class expects one string following the annotation
|
||||
* text that is the address or a symbol name. The display text will be that of the symbol that
|
||||
* is referred to by the address or symbol name.
|
||||
* is referred to by the address or symbol name. The symbol name may be the fully-qualified
|
||||
* namespace path.
|
||||
*/
|
||||
public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
|
||||
private static final String INVALID_SYMBOL_TEXT =
|
||||
"@symbol annotation must have a valid " + "symbol name or address";
|
||||
"@symbol annotation must have a valid symbol name or address";
|
||||
private static final String[] SUPPORTED_ANNOTATIONS = { "symbol", "sym" };
|
||||
|
||||
@Override
|
||||
@@ -126,7 +127,7 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] modify(String[] text, Program program) {
|
||||
public String[] modify(String[] text, Program program, Address addr) {
|
||||
if (text.length <= 1) {
|
||||
return null;
|
||||
}
|
||||
@@ -148,6 +149,11 @@ public class SymbolAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
}
|
||||
|
||||
Address symbolAddress = symbols.get(0).getAddress();
|
||||
if (symbolAddress.isVariableAddress()) {
|
||||
// we can't use variable addresses to locate symbols; there may be other types to ignore
|
||||
return null;
|
||||
}
|
||||
|
||||
text[1] = symbolAddress.toString();
|
||||
|
||||
return text;
|
||||
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import docking.widgets.fieldpanel.field.AttributedString;
|
||||
import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.app.nav.Navigatable;
|
||||
import ghidra.app.services.GoToService;
|
||||
import ghidra.app.util.NamespaceUtils;
|
||||
import ghidra.framework.plugintool.ServiceProvider;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.Symbol;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Provides support for local function variable annotations.
|
||||
* <p>
|
||||
* This allows users to reference function variables in comments without having to change the
|
||||
* comment text when the variable is renamed. Users can enter the annotation using the variable's
|
||||
* name:
|
||||
* <pre>
|
||||
* {@variable local_8}
|
||||
*
|
||||
* or
|
||||
*
|
||||
* {@variable coolVariable SomeFunction}
|
||||
* </pre>
|
||||
* The user annotation will be converted to use address information:
|
||||
* <pre>
|
||||
* {@variable Stack[0xa] FUN_1234eaea}
|
||||
* </pre>
|
||||
*/
|
||||
public class VariableAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
|
||||
private static final String[] SUPPORTED_ANNOTATIONS = { "variable", "var" };
|
||||
|
||||
@Override
|
||||
public String[] getSupportedAnnotations() {
|
||||
return SUPPORTED_ANNOTATIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return "Variable";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrototypeString() {
|
||||
return "{@variable variable_name}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrototypeString(String displayText) {
|
||||
return "{@variable " + displayText.trim() + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
|
||||
Program program) throws AnnotationException {
|
||||
|
||||
/*
|
||||
We expect to be handed annotation text that was updated via a previous call to modify().
|
||||
The annotation will be of the form {@variable variable_address function_address}
|
||||
*/
|
||||
if (text.length != 3) {
|
||||
throw new AnnotationException(
|
||||
"@variable annotation must have a variable address and a function name");
|
||||
}
|
||||
|
||||
if (program == null) { // this can happen during merge operations
|
||||
return createPlaceholderString(prototypeString, text);
|
||||
}
|
||||
|
||||
String entry = text[2];
|
||||
Function function = getFunctionAt(program, entry);
|
||||
if (function == null) {
|
||||
throw new AnnotationException("Could not find function \"" + entry + "\"");
|
||||
}
|
||||
|
||||
String address = text[1];
|
||||
Variable var = getVariable(function, address);
|
||||
if (var == null) {
|
||||
String functionName = function.getName();
|
||||
throw new AnnotationException(
|
||||
"Could not find variable at address %s in function %s".formatted(functionName,
|
||||
address));
|
||||
}
|
||||
|
||||
return new AttributedString(var.getName(), prototypeString.getColor(0),
|
||||
prototypeString.getFontMetrics(0), true, prototypeString.getColor(0));
|
||||
}
|
||||
|
||||
private AttributedString createPlaceholderString(AttributedString prototypeString,
|
||||
String[] text) {
|
||||
String joined = Arrays.stream(text).collect(Collectors.joining(" "));
|
||||
return new AttributedString(joined, Palette.LIGHT_GRAY,
|
||||
prototypeString.getFontMetrics(0));
|
||||
}
|
||||
|
||||
private Function getFunction(Program program, String value) {
|
||||
FunctionManager manager = program.getFunctionManager();
|
||||
for (Symbol s : NamespaceUtils.getSymbols(value, program)) {
|
||||
Address addr = s.getAddress();
|
||||
Function function = manager.getFunctionAt(addr);
|
||||
if (function != null) {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here, then see if the value is an address
|
||||
return getFunctionAt(program, value);
|
||||
}
|
||||
|
||||
private Function getFunctionAt(Program program, String address) {
|
||||
FunctionManager manager = program.getFunctionManager();
|
||||
AddressFactory af = program.getAddressFactory();
|
||||
Address addr = af.getAddress(address);
|
||||
if (addr != null) {
|
||||
Function function = manager.getFunctionAt(addr);
|
||||
if (function != null) {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the annotation to convert names to addresses.
|
||||
* @param text the array of annotation parts to modify
|
||||
* @param program the program
|
||||
* @param addr address of the annotation in the program
|
||||
* @return the modified array; null otherwise
|
||||
*/
|
||||
@Override
|
||||
public String[] modify(String[] text, Program program, Address addr) {
|
||||
if (program == null) { // this can happen during merge operations
|
||||
return null;
|
||||
}
|
||||
|
||||
Function function = null;
|
||||
switch (text.length) {
|
||||
case 3:
|
||||
function = getFunction(program, text[2]);
|
||||
break;
|
||||
case 2:
|
||||
function = program.getFunctionManager().getFunctionContaining(addr);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (function == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String value = text[1]; // value is a variable name or address
|
||||
VariableMatcher matcher = createVariableMatcher(program, value);
|
||||
Variable var = findVariable(function, matcher);
|
||||
if (var == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new String[] {
|
||||
"variable",
|
||||
var.getMinAddress().toString(),
|
||||
function.getEntryPoint().toString()
|
||||
};
|
||||
}
|
||||
|
||||
private VariableMatcher createVariableMatcher(Program p, String value) {
|
||||
|
||||
Address addr = toAddress(p, value);
|
||||
if (addr != null) {
|
||||
return new AddressMatcher(addr);
|
||||
}
|
||||
|
||||
return new NameMatcher(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMouseClick(String[] text, Navigatable sourceNavigatable,
|
||||
ServiceProvider serviceProvider) {
|
||||
|
||||
/*
|
||||
We expect to be handed annotation text that was updated via a previous call to modify().
|
||||
The annotation will be of the form {@variable variable_address function_address}
|
||||
*/
|
||||
if (text.length != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Program program = sourceNavigatable.getProgram();
|
||||
Function function = getFunction(program, text[2]);
|
||||
if (function == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String address = text[1];
|
||||
Variable var = getVariable(function, address);
|
||||
if (var == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Symbol symbol = var.getSymbol();
|
||||
if (symbol == null) {
|
||||
symbol = function.getSymbol();
|
||||
}
|
||||
|
||||
GoToService goToService = serviceProvider.getService(GoToService.class);
|
||||
if (goToService == null) {
|
||||
Msg.debug(this, "GoToService not installed");
|
||||
return false;
|
||||
}
|
||||
|
||||
return goToService.goTo(symbol.getProgramLocation());
|
||||
}
|
||||
|
||||
private Variable getVariable(Function function, String addressString) {
|
||||
Program p = function.getProgram();
|
||||
Address addr = toAddress(p, addressString);
|
||||
VariableMatcher matcher = new AddressMatcher(addr);
|
||||
return findVariable(function, matcher);
|
||||
}
|
||||
|
||||
private static Variable findVariable(Function function, VariableMatcher matcher) {
|
||||
for (Variable var : function.getAllVariables()) {
|
||||
if (matcher.matches(var)) {
|
||||
return var;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Address toAddress(Program p, String s) {
|
||||
AddressFactory af = p.getAddressFactory();
|
||||
return af.getAddress(s);
|
||||
}
|
||||
|
||||
private static interface VariableMatcher {
|
||||
public boolean matches(Variable v);
|
||||
}
|
||||
|
||||
private static class NameMatcher implements VariableMatcher {
|
||||
|
||||
private String name;
|
||||
|
||||
NameMatcher(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Variable v) {
|
||||
return v.getName().equals(name);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AddressMatcher implements VariableMatcher {
|
||||
|
||||
private Address addr;
|
||||
|
||||
AddressMatcher(Address addr) {
|
||||
this.addr = addr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(Variable v) {
|
||||
return v.getMinAddress().equals(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
-15
@@ -45,6 +45,10 @@ import ghidra.framework.store.FileSystem;
|
||||
import ghidra.program.database.ProgramBuilder;
|
||||
import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.data.UnsignedIntegerDataType;
|
||||
import ghidra.program.model.data.VoidDataType;
|
||||
import ghidra.program.model.listing.Function;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.program.model.mem.Memory;
|
||||
import ghidra.program.model.symbol.*;
|
||||
@@ -83,51 +87,55 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
builder.createLabel("1001018", "mySym{0}"); // symbol with braces
|
||||
builder.createLabel("1001022", "mySym\\{0\\}"); // symbol with braces escaped
|
||||
|
||||
Function myFunction =
|
||||
builder.createEmptyFunction("MyFunction", "1002000", 0x10, VoidDataType.dataType);
|
||||
builder.createLocalVariable(myFunction, "myVariable", UnsignedIntegerDataType.dataType, 10);
|
||||
|
||||
return builder.getProgram();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymbolAnnotationWithAddress() {
|
||||
String rawComment = "This is a symbol {@sym 01001014} annotation.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals(rawComment, fixed);
|
||||
|
||||
// with display string
|
||||
rawComment = "This is a symbol {@sym 01001014 bob} annotation.";
|
||||
fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals(rawComment, fixed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymbolAnnotationWithInvalidAddress() {
|
||||
String rawComment = "This is a symbol {@sym 999999} annotation.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals(rawComment, fixed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymbolAnnotationWithSymbol() {
|
||||
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("This is a symbol {@sym 01003d2c} annotation.", fixed);
|
||||
|
||||
// with display string
|
||||
rawComment = "This is a symbol {@sym LAB_01003d2c displayText} annotation.";
|
||||
fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("This is a symbol {@sym 01003d2c displayText} annotation.", fixed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymbolAnnotationWithInvalidSymbol() {
|
||||
String rawComment = "This is a symbol {@sym CocoPebbles} annotation.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("This is a symbol {@sym CocoPebbles} annotation.", fixed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoAnnotation() {
|
||||
String rawComment = "This is no symbol annotation.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals(rawComment, fixed);
|
||||
}
|
||||
|
||||
@@ -135,7 +143,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
public void testMixedAnnotationNoSymbolAnnotation() {
|
||||
String rawComment = "This is a symbol {@url www.noplace.com} annotation " +
|
||||
"with a {@program notepad} annotation.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals(rawComment, fixed);
|
||||
}
|
||||
|
||||
@@ -143,7 +151,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
public void testMixedAnnotationWithSymbolAnnotation() {
|
||||
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation " +
|
||||
"with a {@program notepad} annotation.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("This is a symbol {@sym 01003d2c} annotation " +
|
||||
"with a {@program notepad} annotation.", fixed);
|
||||
}
|
||||
@@ -151,21 +159,21 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
@Test
|
||||
public void testSymbolAnnotationAtBeginningOfComment() {
|
||||
String rawComment = "{@sym LAB_01003d2c} annotation at the beginning.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("{@sym 01003d2c} annotation at the beginning.", fixed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymbolAnnotation_BackToBack() {
|
||||
String rawComment = "Test {@sym LAB_01003d2c}{@sym LAB_01003d2c} end.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("Test {@sym 01003d2c}{@sym 01003d2c} end.", fixed);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSymbolAnnotationAtEndOfComment() {
|
||||
String rawComment = "Annotation at the end {@sym LAB_01003d2c}";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("Annotation at the end {@sym 01003d2c}", fixed);
|
||||
}
|
||||
|
||||
@@ -173,7 +181,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
public void testSymbolAnnotationAtBeginningAndEndOfComment() {
|
||||
String rawComment =
|
||||
"{@sym LAB_01003d2c} annotation at the beginning and end {@sym LAB_01003d5b}";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("{@sym 01003d2c} annotation at the " + "beginning and end {@sym 01003d5b}",
|
||||
fixed);
|
||||
}
|
||||
@@ -183,7 +191,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
String rawComment =
|
||||
"{@sym LAB_01003d2c} annotation at the beginning, middle {@sym LAB_01003d28} and " +
|
||||
"end {@sym LAB_01003d5b}";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("{@sym 01003d2c} annotation at the beginning, middle {@sym 01003d28} and " +
|
||||
"end {@sym 01003d5b}", fixed);
|
||||
}
|
||||
@@ -192,7 +200,7 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
public void testSymbolAnnotationWithValidAndInvalidSymbol() {
|
||||
String rawComment = "This is a symbol {@sym LAB_01003d2c} annotation " +
|
||||
"with a {@sym FruityPebbles} annotation.";
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program);
|
||||
String fixed = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("This is a symbol {@sym 01003d2c} annotation " +
|
||||
"with a {@sym FruityPebbles} annotation.", fixed);
|
||||
}
|
||||
@@ -733,6 +741,52 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
// Navigation performed by ProgramManager not tested due to use of spyServiceProvider
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariableAnnotation_Basic() {
|
||||
String rawComment = "{@variable Stack[0xa] MyFunction}";
|
||||
String display = CommentUtils.getDisplayString(rawComment, program);
|
||||
assertEquals("myVariable", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariableAnnotation_BasicModify() {
|
||||
String rawComment = "{@variable myVariable MyFunction}";
|
||||
String display = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("{@variable Stack[0xa] 01002000}", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariableAnnotation_BasicModify_NoFunction() {
|
||||
String rawComment = "{@variable myVariable}";
|
||||
String functionAddress = "01002000";
|
||||
Address entryPoint = addr(functionAddress);
|
||||
String display = CommentUtils.fixupAnnotations(rawComment, program, entryPoint);
|
||||
assertEquals("{@variable Stack[0xa] 01002000}", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalAnnotation_UserMarked() {
|
||||
String rawComment = "{@variable Stack[0xa] MyFunction}";
|
||||
String display = CommentUtils.getDisplayString(rawComment, program);
|
||||
assertEquals("myVariable", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalAnnotation_UserMarkedModify() {
|
||||
String rawComment = "{@variable Stack[0xa] MyFunction}";
|
||||
String display = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("{@variable Stack[0xa] 01002000}", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalAnnotation_UserMarkedModify_NoFunction() {
|
||||
String rawComment = "{@variable Stack[0xa]}";
|
||||
String functionAddress = "01002000";
|
||||
Address entryPoint = addr(functionAddress);
|
||||
String display = CommentUtils.fixupAnnotations(rawComment, program, entryPoint);
|
||||
assertEquals("{@variable Stack[0xa] 01002000}", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnknownAnnotation() {
|
||||
String rawComment = "This is a symbol {@syyyybol bob} annotation";
|
||||
@@ -917,6 +971,10 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
waitForSwing(); // let post-dialog processing happen
|
||||
}
|
||||
|
||||
private Address addr(String offset) {
|
||||
AddressFactory af = program.getAddressFactory();
|
||||
return af.getAddress(offset);
|
||||
}
|
||||
//==================================================================================================
|
||||
// Fake/Spy Classes
|
||||
//==================================================================================================
|
||||
|
||||
+3
-5
@@ -16,8 +16,7 @@
|
||||
package ghidra.program.database.function;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import java.util.Objects;
|
||||
|
||||
import ghidra.program.database.data.DataTypeUtilities;
|
||||
import ghidra.program.database.symbol.SymbolDB;
|
||||
@@ -353,14 +352,13 @@ public abstract class VariableDB implements Variable {
|
||||
}
|
||||
|
||||
Variable otherVar = (Variable) obj;
|
||||
|
||||
if (!isEquivalent(otherVar)) {
|
||||
return false;
|
||||
}
|
||||
if (!StringUtils.equals(getName(), otherVar.getName())) {
|
||||
if (!Objects.equals(getName(), otherVar.getName())) {
|
||||
return false;
|
||||
}
|
||||
return StringUtils.equals(getComment(), otherVar.getComment());
|
||||
return Objects.equals(getComment(), otherVar.getComment());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user