mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-06-01 06:25:26 +08:00
GP-6561 - Annotations - PR merge and fixup of new Variable annotation
This commit is contained in:
@@ -104,6 +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 specify a symbol name instead of an address, then the text of the comment
|
||||
will be changed to use the address of that symbol.
|
||||
@@ -292,7 +297,7 @@
|
||||
|
||||
<ul style="margin-left: 10px;">
|
||||
<li>
|
||||
This annotation will modify itself into the @var_hash format with a function
|
||||
This annotation will modify itself to use the variable and function
|
||||
address to survive renaming of the variable and the function.
|
||||
</li>
|
||||
</ul>
|
||||
@@ -302,7 +307,7 @@
|
||||
|
||||
<TD valign="top" width="12%">
|
||||
<OL style="margin-left: 15px;">
|
||||
<LI>variable/parameter name (@var) or hash identifier (@var_hash)</LI>
|
||||
<LI>variable/parameter name (@variable) or hash identifier</LI>
|
||||
<LI>[function name or address]<BR>
|
||||
</LI>
|
||||
</OL>
|
||||
@@ -311,10 +316,7 @@
|
||||
<TD valign="top" width="10%">
|
||||
<UL style="margin-left: 10px;">
|
||||
<LI>@variable</LI>
|
||||
|
||||
<LI>@var</LI>
|
||||
|
||||
<LI>@var_hash</LI>
|
||||
</UL>
|
||||
</TD>
|
||||
|
||||
@@ -323,10 +325,6 @@
|
||||
<LI>{@variable "i"}</LI>
|
||||
|
||||
<LI>{@var "argv" "main"}</LI>
|
||||
|
||||
<LI>{@var_hash 02a2a2a "main"}</LI>
|
||||
|
||||
<LI>{@var_hash 0101010 "1000002a"}<BR>
|
||||
</LI>
|
||||
</UL>
|
||||
</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,66 +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) {
|
||||
Address loc = cu.getAddress();
|
||||
if (commentChanged(cu.getComment(CommentType.PRE), preComment)) {
|
||||
String updatedPreComment = CommentUtils.fixupAnnotations(preComment, program, loc);
|
||||
updatedPreComment = CommentUtils.sanitize(updatedPreComment);
|
||||
cu.setComment(CommentType.PRE, updatedPreComment);
|
||||
}
|
||||
if (commentChanged(cu.getComment(CommentType.POST), postComment)) {
|
||||
String updatedPostComment = CommentUtils.fixupAnnotations(postComment, program, loc);
|
||||
updatedPostComment = CommentUtils.sanitize(updatedPostComment);
|
||||
cu.setComment(CommentType.POST, updatedPostComment);
|
||||
}
|
||||
if (commentChanged(cu.getComment(CommentType.EOL), eolComment)) {
|
||||
String updatedEOLComment = CommentUtils.fixupAnnotations(eolComment, program, loc);
|
||||
updatedEOLComment = CommentUtils.sanitize(updatedEOLComment);
|
||||
cu.setComment(CommentType.EOL, updatedEOLComment);
|
||||
}
|
||||
if (commentChanged(cu.getComment(CommentType.PLATE), plateComment)) {
|
||||
String updatedPlateComment = CommentUtils.fixupAnnotations(plateComment, program, loc);
|
||||
updatedPlateComment = CommentUtils.sanitize(updatedPlateComment);
|
||||
cu.setComment(CommentType.PLATE, updatedPlateComment);
|
||||
}
|
||||
if (commentChanged(cu.getComment(CommentType.REPEATABLE), repeatableComment)) {
|
||||
String updatedRepeatableComment =
|
||||
CommentUtils.fixupAnnotations(repeatableComment, program, loc);
|
||||
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.
|
||||
*
|
||||
@@ -135,7 +121,6 @@ public class SetCommentsCmd implements Command<Program> {
|
||||
|
||||
@Override
|
||||
public String getStatusMsg() {
|
||||
return msg;
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+18
-28
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Objects;
|
||||
|
||||
import docking.widgets.fieldpanel.field.AttributedString;
|
||||
@@ -24,16 +23,15 @@ 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 {
|
||||
|
||||
@@ -45,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) {
|
||||
@@ -57,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
|
||||
@@ -81,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;
|
||||
|
||||
/**
|
||||
@@ -90,44 +80,44 @@ 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)
|
||||
default String[] modify(String[] text, Program program) {
|
||||
public default String[] modify(String[] text, Program program) {
|
||||
return modify(text, program, Address.NO_ADDRESS);
|
||||
}
|
||||
|
||||
@@ -138,10 +128,10 @@ public interface AnnotatedStringHandler extends ExtensionPoint {
|
||||
*
|
||||
* @param text the array of annotation parts to modify
|
||||
* @param program the program
|
||||
* @param loc location of the annotation in the program
|
||||
* @param addr address of the annotation in the program
|
||||
* @return the modified array; null otherwise
|
||||
*/
|
||||
default String[] modify(String[] text, Program program, Address loc) {
|
||||
public default String[] modify(String[] text, Program program, Address addr) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ public class CommentUtils {
|
||||
*
|
||||
* @param rawCommentText the text to be updated
|
||||
* @param program the program associated with the comment
|
||||
* @param loc location of comment in program
|
||||
* @param addr address of comment in program
|
||||
* @return the updated string
|
||||
*/
|
||||
public static String fixupAnnotations(String rawCommentText, Program program, Address loc) {
|
||||
public static String fixupAnnotations(String rawCommentText, Program program, Address addr) {
|
||||
|
||||
if (rawCommentText == null) {
|
||||
return null;
|
||||
@@ -67,7 +67,7 @@ public class CommentUtils {
|
||||
String[] annotationParts = annotation.getAnnotationParts();
|
||||
AnnotatedStringHandler handler = getAnnotationHandler(annotationParts);
|
||||
|
||||
String[] updatedParts = handler.modify(annotationParts, program, loc);
|
||||
String[] updatedParts = handler.modify(annotationParts, program, addr);
|
||||
if (updatedParts == null) {
|
||||
return annotation; // nothing to change
|
||||
}
|
||||
@@ -164,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, Address loc) {
|
||||
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;
|
||||
|
||||
+212
-147
@@ -15,6 +15,9 @@
|
||||
*/
|
||||
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;
|
||||
@@ -22,122 +25,38 @@ 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;
|
||||
|
||||
/**
|
||||
* 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 name of a script.
|
||||
* 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 DEFAULT_ANO = "var";
|
||||
private static final String HASH_ANO = DEFAULT_ANO + "_hash";
|
||||
private static final String[] SUPPORTED_ANNOTATIONS = { "variable", DEFAULT_ANO, HASH_ANO };
|
||||
|
||||
private static final String INVALID_SYMBOL_TEXT =
|
||||
"@" + DEFAULT_ANO + " annotation must have form: <var_sym> [<func_sym>]";
|
||||
|
||||
|
||||
@Override
|
||||
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
|
||||
Program program) {
|
||||
if (program == null) { // this can happen during merge operations
|
||||
final StringBuilder buffer = new StringBuilder();
|
||||
for (String string : text) {
|
||||
buffer.append(string).append(" ");
|
||||
}
|
||||
|
||||
return new AttributedString(buffer.toString(), Palette.LIGHT_GRAY,
|
||||
prototypeString.getFontMetrics(0));
|
||||
}
|
||||
|
||||
if (text.length != 3) {
|
||||
if (text.length == 2) {
|
||||
throw new AnnotationException("Function symbol is not optional when rendering.");
|
||||
}
|
||||
throw new AnnotationException(INVALID_SYMBOL_TEXT);
|
||||
}
|
||||
|
||||
final Function func = getFunction(program, text[2]);
|
||||
if (func == null) {
|
||||
throw new AnnotationException("Could not find function matching \"" + text[2] + "\"");
|
||||
}
|
||||
final Variable var = getVariable(func, getFilterGenerator(text[0]).apply(text[1]));
|
||||
if (var == null) {
|
||||
throw new AnnotationException("Could not find variable in function \"" +
|
||||
func.getName() + "\" matching \"" + text[1] + "\".");
|
||||
}
|
||||
|
||||
return new AttributedString(var.getName(), prototypeString.getColor(0),
|
||||
prototypeString.getFontMetrics(0), true, prototypeString.getColor(0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] modify(String[] text, Program program, Address loc) {
|
||||
if (program == null) { // this can happen during merge operations
|
||||
return null;
|
||||
}
|
||||
|
||||
Function func = null;
|
||||
switch (text.length) {
|
||||
case 3:
|
||||
func = getFunction(program, text[2]);
|
||||
break;
|
||||
case 2:
|
||||
func = program.getFunctionManager().getFunctionContaining(loc);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
if (func == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Variable var = getVariable(func, getFilterGenerator(text[0]).apply(text[1]));
|
||||
if (var == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new String[] {
|
||||
HASH_ANO,
|
||||
Integer.toUnsignedString(var.hashCode(), 16),
|
||||
func.getEntryPoint().toString()
|
||||
};
|
||||
}
|
||||
private static final String[] SUPPORTED_ANNOTATIONS = { "variable", "var" };
|
||||
|
||||
@Override
|
||||
public String[] getSupportedAnnotations() {
|
||||
return SUPPORTED_ANNOTATIONS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleMouseClick(String[] annotationParts, Navigatable sourceNavigatable,
|
||||
ServiceProvider serviceProvider) {
|
||||
final Program program = sourceNavigatable.getProgram();
|
||||
|
||||
if (annotationParts.length != 3) {
|
||||
return false;
|
||||
}
|
||||
final Function func = getFunction(program, annotationParts[2]);
|
||||
if (func == null) {
|
||||
return false;
|
||||
}
|
||||
final Variable var = getVariable(func, getFilterGenerator(annotationParts[0]).apply(annotationParts[1]));
|
||||
if (var == null) {
|
||||
return false;
|
||||
}
|
||||
Symbol sym = var.getSymbol();
|
||||
if (sym == null) {
|
||||
sym = func.getSymbol();
|
||||
}
|
||||
|
||||
final GoToService goToService = serviceProvider.getService(GoToService.class);
|
||||
return goToService.goTo(sym.getProgramLocation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayString() {
|
||||
return "Variable";
|
||||
@@ -145,40 +64,71 @@ public class VariableAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
|
||||
@Override
|
||||
public String getPrototypeString() {
|
||||
return "{@" + DEFAULT_ANO + " var_sym [func_sym]}";
|
||||
return "{@variable variable_name}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrototypeString(String displayText) {
|
||||
return "{@" + DEFAULT_ANO + " " + displayText.trim() + "}";
|
||||
return "{@variable " + displayText.trim() + "}";
|
||||
}
|
||||
|
||||
private static Function getFunction(final Program program, final String name) {
|
||||
final FunctionManager func_manager = program.getFunctionManager();
|
||||
@Override
|
||||
public AttributedString createAnnotatedString(AttributedString prototypeString, String[] text,
|
||||
Program program) throws AnnotationException {
|
||||
|
||||
for (Symbol sym : NamespaceUtils.getSymbols(name, program)) {
|
||||
final Function func = func_manager.getFunctionAt(sym.getAddress());
|
||||
if (func != null) {
|
||||
return func;
|
||||
/*
|
||||
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 functionName = text[2];
|
||||
Function function = getFunction(program, functionName);
|
||||
if (function == null) {
|
||||
throw new AnnotationException("Could not find function \"" + functionName + "\"");
|
||||
}
|
||||
|
||||
String address = text[1];
|
||||
Variable var = getVariable(function, address);
|
||||
if (var == null) {
|
||||
throw new AnnotationException("Could not find variable in function \"" +
|
||||
functionName + "\" matching \"" + text[1] + "\".");
|
||||
}
|
||||
|
||||
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 static Function getFunction(Program program, String name) {
|
||||
FunctionManager manager = program.getFunctionManager();
|
||||
for (Symbol s : NamespaceUtils.getSymbols(name, 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
|
||||
final Address addr = program.getAddressFactory().getAddress(name);
|
||||
Address addr = program.getAddressFactory().getAddress(name);
|
||||
if (addr != null) {
|
||||
final Function func = func_manager.getFunctionAt(addr);
|
||||
if (func != null) {
|
||||
return func;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Variable getVariable(final Function func, JFunction<Variable, Boolean> name_filter) {
|
||||
for (Variable var : func.getAllVariables()) {
|
||||
if (name_filter.apply(var)) {
|
||||
return var;
|
||||
Function function = manager.getFunctionAt(addr);
|
||||
if (function != null) {
|
||||
return function;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,32 +136,147 @@ public class VariableAnnotatedStringHandler implements AnnotatedStringHandler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the correct filter generator for parsing the local variable.
|
||||
*
|
||||
* @param name the name of the annotation
|
||||
* @return the filter generator
|
||||
* 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
|
||||
*/
|
||||
private static JFunction<String,JFunction<Variable, Boolean>> getFilterGenerator(final String name) {
|
||||
switch (name) {
|
||||
case HASH_ANO:
|
||||
return VariableAnnotatedStringHandler::hashFilterGen;
|
||||
default:
|
||||
return VariableAnnotatedStringHandler::nameFilterGen;
|
||||
@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 JFunction<Variable, Boolean> hashFilterGen(String s) {
|
||||
try {
|
||||
final int hash = Integer.parseUnsignedInt(s, 16);
|
||||
return (x) -> x.hashCode() == hash;
|
||||
} catch (NumberFormatException e) {
|
||||
return (x) -> false;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private static JFunction<Variable, Boolean> nameFilterGen(String s) {
|
||||
return (x) -> x.getName().equals(s);
|
||||
}
|
||||
|
||||
private interface JFunction<T, R> extends java.util.function.Function<T, R> {}
|
||||
}
|
||||
|
||||
+28
-19
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.instanceOf;
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -45,6 +45,7 @@ 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;
|
||||
@@ -64,7 +65,6 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
private static final String OTHER_PROGRAM_NAME = "program2";
|
||||
|
||||
private Program program;
|
||||
private Function test_func;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -87,8 +87,9 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
builder.createLabel("1001018", "mySym{0}"); // symbol with braces
|
||||
builder.createLabel("1001022", "mySym\\{0\\}"); // symbol with braces escaped
|
||||
|
||||
test_func = builder.createEmptyFunction("test_func", "1002000", 0x10, VoidDataType.dataType);
|
||||
builder.createLocalVariable(test_func, "test_var", UnsignedIntegerDataType.dataType, 10);
|
||||
Function myFunction =
|
||||
builder.createEmptyFunction("MyFunction", "1002000", 0x10, VoidDataType.dataType);
|
||||
builder.createLocalVariable(myFunction, "myVariable", UnsignedIntegerDataType.dataType, 10);
|
||||
|
||||
return builder.getProgram();
|
||||
}
|
||||
@@ -742,44 +743,48 @@ public class AnnotationTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
@Test
|
||||
public void testVariableAnnotation_Basic() {
|
||||
String rawComment = "{@var test_var test_func}";
|
||||
String rawComment = "{@variable Stack[0xa] MyFunction}";
|
||||
String display = CommentUtils.getDisplayString(rawComment, program);
|
||||
assertEquals("test_var", display);
|
||||
assertEquals("myVariable", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVariableAnnotation_BasicModify() {
|
||||
String rawComment = "{@var test_var test_func}";
|
||||
String rawComment = "{@variable myVariable MyFunction}";
|
||||
String display = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("{@var_hash a016221 01002000}", display);
|
||||
assertEquals("{@variable Stack[0xa] 01002000}", display);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testVariableAnnotation_BasicModify_NoFunction() {
|
||||
String rawComment = "{@var test_var}";
|
||||
String display = CommentUtils.fixupAnnotations(rawComment, program, test_func.getEntryPoint());
|
||||
assertEquals("{@var_hash a016221 01002000}", display);
|
||||
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 = "{@var_hash a016221 test_func}";
|
||||
String rawComment = "{@variable Stack[0xa] MyFunction}";
|
||||
String display = CommentUtils.getDisplayString(rawComment, program);
|
||||
assertEquals("test_var", display);
|
||||
assertEquals("myVariable", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalAnnotation_UserMarkedModify() {
|
||||
String rawComment = "{@var_hash a016221 test_func}";
|
||||
String rawComment = "{@variable Stack[0xa] MyFunction}";
|
||||
String display = CommentUtils.fixupAnnotations(rawComment, program, Address.NO_ADDRESS);
|
||||
assertEquals("{@var_hash a016221 01002000}", display);
|
||||
assertEquals("{@variable Stack[0xa] 01002000}", display);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalAnnotation_UserMarkedModify_NoFunction() {
|
||||
String rawComment = "{@var_hash a016221}";
|
||||
String display = CommentUtils.fixupAnnotations(rawComment, program, test_func.getEntryPoint());
|
||||
assertEquals("{@var_hash a016221 01002000}", display);
|
||||
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
|
||||
@@ -966,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
|
||||
//==================================================================================================
|
||||
|
||||
Reference in New Issue
Block a user