mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-09-28 17:11:11 -09:00
Merge remote-tracking branch 'origin/patch'
This commit is contained in:
@@ -40,6 +40,28 @@ import util.CollectionUtils;
|
||||
* Repeatable comment for the code unit, any repeatable comments for the code units that this code
|
||||
* unit has references to, and possibly a comment indicating the data at a code unit that is
|
||||
* referenced by this code unit.
|
||||
*
|
||||
* This section describes the various EOL comment types that may appear. The user can
|
||||
* toggle which types are enabled. The comments are displayed in the order listed below.
|
||||
*
|
||||
* EOL Types:
|
||||
*
|
||||
* - EOL - user end of line comment
|
||||
*
|
||||
* - Repeatable - user repeatable source comment *at the code unit*
|
||||
* - Ref Repeatable- for every reference *from a code unit*, show the target:
|
||||
* - address repeatable,
|
||||
* - function repeatable,
|
||||
* - code unit repeatable
|
||||
*
|
||||
* - Auto - fabricated reference preview:
|
||||
* - function,
|
||||
* - indirect data pointer,
|
||||
* - direct data access preview
|
||||
* *depending on the options, this typically do not appear when
|
||||
* repeatable comments exist
|
||||
*
|
||||
* - Offcut - comments at addresses inside of a code unit
|
||||
*/
|
||||
public class EolComments {
|
||||
|
||||
@@ -67,6 +89,7 @@ public class EolComments {
|
||||
this.operandsShowReferences = operandsShowReferences;
|
||||
this.maxDisplayComments = maxDisplayComments;
|
||||
this.extraCommentsOption = extraCommentsOption;
|
||||
|
||||
loadComments();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@ package ghidra.app.util.viewer.field;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import docking.widgets.fieldpanel.field.*;
|
||||
import docking.widgets.fieldpanel.support.*;
|
||||
@@ -26,6 +26,7 @@ import generic.theme.GThemeDefaults.Colors.Palette;
|
||||
import ghidra.app.util.*;
|
||||
import ghidra.app.util.viewer.field.ListingColors.CommentColors;
|
||||
import ghidra.app.util.viewer.format.FieldFormatModel;
|
||||
import ghidra.app.util.viewer.listingpanel.ListingModel;
|
||||
import ghidra.app.util.viewer.options.OptionsGui;
|
||||
import ghidra.app.util.viewer.proxy.ProxyObj;
|
||||
import ghidra.framework.options.*;
|
||||
@@ -33,8 +34,10 @@ import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.util.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.StringUtilities;
|
||||
import ghidra.util.bean.field.AnnotatedTextFieldElement;
|
||||
import ghidra.util.exception.AssertException;
|
||||
import utility.function.Dummy;
|
||||
|
||||
/**
|
||||
* Generates End of line comment Fields.
|
||||
@@ -236,6 +239,7 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
if (!enabled || !(obj instanceof CodeUnit)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CodeUnit cu = (CodeUnit) obj;
|
||||
Program program = cu.getProgram();
|
||||
|
||||
@@ -244,14 +248,8 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
// comments if open. If this was allowed, then the comment would appear
|
||||
// on the outside data container and on the 1st internal member
|
||||
//
|
||||
if (cu instanceof Data) {
|
||||
Data data = (Data) cu;
|
||||
if (data.getNumComponents() > 0) {
|
||||
boolean isOpen = proxy.getListingLayoutModel().isOpen((Data) proxy.getObject());
|
||||
if (isOpen) {
|
||||
return null; // avoid double showing
|
||||
}
|
||||
}
|
||||
if (isOpenData(cu, proxy)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EolComments comments = new EolComments(cu, codeUnitFormatOptions.followReferencedPointers(),
|
||||
@@ -261,25 +259,59 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
List<FieldElement> elementList = new ArrayList<>();
|
||||
AttributedString prefix = createPrefix(CommentStyle.EOL);
|
||||
List<String> eols = comments.getEOLComments();
|
||||
List<FieldElement> eolElements = convertToFieldElements(program, eols, prefix, 0);
|
||||
List<FieldElement> eolElements = convertToFieldElements(program, eols, prefix, 0, true);
|
||||
elementList.addAll(eolElements);
|
||||
|
||||
/*
|
||||
This section describes the various EOL comment types that may appear. The user can
|
||||
toggle which types are enabled. The comments are displayed in the order listed below.
|
||||
|
||||
EOL Types:
|
||||
|
||||
- EOL - user end of line comment
|
||||
|
||||
- Repeatable - user repeatable source comment *at the code unit*
|
||||
- Ref Repeatable- for every reference *from a code unit*, show the target:
|
||||
- address repeatable,
|
||||
- function repeatable,
|
||||
- code unit repeatable
|
||||
|
||||
- Auto - fabricated reference preview:
|
||||
- function,
|
||||
- indirect data pointer,
|
||||
- direct data access preview
|
||||
*depending on the options, this typically do not appear when
|
||||
repeatable comments exist
|
||||
|
||||
- Offcut - comments at addresses inside of a code unit
|
||||
*/
|
||||
|
||||
if (comments.isShowingRepeatables()) {
|
||||
prefix = createPrefix(CommentStyle.REPEATABLE);
|
||||
int row = getNextRow(elementList);
|
||||
List<String> repeatables = comments.getRepeatableComments();
|
||||
List<FieldElement> elements = convertToFieldElements(program, repeatables, prefix, row);
|
||||
List<FieldElement> elements =
|
||||
convertToFieldElements(program, repeatables, prefix, row, true);
|
||||
elementList.addAll(elements);
|
||||
}
|
||||
|
||||
if (comments.isShowingRefRepeatables()) {
|
||||
prefix = createPrefix(CommentStyle.REF_REPEATABLE);
|
||||
|
||||
AttributedString refPrefix = createPrefix(CommentStyle.REF_REPEATABLE);
|
||||
List<RefRepeatComment> refRepeatables = comments.getReferencedRepeatableComments();
|
||||
for (RefRepeatComment comment : refRepeatables) {
|
||||
|
||||
int row = getNextRow(elementList);
|
||||
String[] lines = comment.getCommentLines();
|
||||
Address refAddress = comment.getAddress();
|
||||
List<String> linesList = Arrays.asList(lines);
|
||||
|
||||
Consumer<List<FieldElement>> decorator = elements -> {
|
||||
prependRefAddress(program, refPrefix, refAddress, elements);
|
||||
};
|
||||
|
||||
List<FieldElement> elements =
|
||||
convertToRefFieldElements(lines, program, prefix, comment.getAddress(), row);
|
||||
convertToFieldElements(program, linesList, decorator, prefix, row, true);
|
||||
elementList.addAll(elements);
|
||||
}
|
||||
}
|
||||
@@ -288,7 +320,11 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
prefix = createPrefix(CommentStyle.AUTO);
|
||||
int row = getNextRow(elementList);
|
||||
List<String> autos = comments.getAutomaticComment();
|
||||
List<FieldElement> elements = convertToFieldElements(program, autos, prefix, row);
|
||||
|
||||
// Note: we pass 'false' for allowing annotations so that the user will see the raw data
|
||||
// and not an interpreted annotation.
|
||||
List<FieldElement> elements =
|
||||
convertToFieldElements(program, autos, prefix, row, false);
|
||||
elementList.addAll(elements);
|
||||
}
|
||||
|
||||
@@ -296,7 +332,8 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
prefix = createPrefix(CommentStyle.OFFCUT);
|
||||
int row = getNextRow(elementList);
|
||||
List<String> offcuts = comments.getOffcutEolComments();
|
||||
List<FieldElement> elements = convertToFieldElements(program, offcuts, prefix, row);
|
||||
List<FieldElement> elements =
|
||||
convertToFieldElements(program, offcuts, prefix, row, true);
|
||||
elementList.addAll(elements);
|
||||
}
|
||||
|
||||
@@ -307,6 +344,18 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
maxDisplayLines, hlProvider);
|
||||
}
|
||||
|
||||
private boolean isOpenData(CodeUnit cu, ProxyObj<?> proxy) {
|
||||
if (cu instanceof Data data) {
|
||||
if (data.getNumComponents() > 0) {
|
||||
ListingModel listingModel = proxy.getListingLayoutModel();
|
||||
if (listingModel.isOpen(data)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private AttributedString createPrefix(CommentStyle commentStyle) {
|
||||
if (commentStyle == CommentStyle.EOL) {
|
||||
return new AttributedString(SEMICOLON_PREFIX, CommentColors.EOL, getMetrics(style),
|
||||
@@ -349,7 +398,15 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
}
|
||||
|
||||
private List<FieldElement> convertToFieldElements(Program program, List<String> comments,
|
||||
AttributedString prefix, int row) {
|
||||
AttributedString prefix, int row, boolean allowAnnotations) {
|
||||
|
||||
Consumer<List<FieldElement>> decorator = Dummy.consumer(); // no decorations by default
|
||||
return convertToFieldElements(program, comments, decorator, prefix, row, allowAnnotations);
|
||||
}
|
||||
|
||||
private List<FieldElement> convertToFieldElements(Program program, List<String> comments,
|
||||
Consumer<List<FieldElement>> decorator, AttributedString prefix, int row,
|
||||
boolean allowAnnotations) {
|
||||
|
||||
List<FieldElement> fieldElements = new ArrayList<>();
|
||||
if (comments.isEmpty()) {
|
||||
@@ -357,11 +414,15 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
}
|
||||
|
||||
for (int commentRow = 0; commentRow < comments.size(); commentRow++) {
|
||||
int offsetRow = row + commentRow;
|
||||
fieldElements.add(CommentUtils.parseTextForAnnotations(comments.get(commentRow),
|
||||
program, prefix, offsetRow));
|
||||
int encodedRow = row + commentRow;
|
||||
String commentText = comments.get(commentRow);
|
||||
FieldElement element =
|
||||
createCommentField(commentText, prefix, program, encodedRow, allowAnnotations);
|
||||
fieldElements.add(element);
|
||||
}
|
||||
|
||||
decorator.accept(fieldElements);
|
||||
|
||||
if (isWordWrap) {
|
||||
int lineWidth = showSemicolon ? width - prefix.getStringWidth() : width;
|
||||
fieldElements = FieldUtils.wrap(fieldElements, lineWidth);
|
||||
@@ -381,57 +442,43 @@ public class EolCommentFieldFactory extends FieldFactory {
|
||||
return fieldElements;
|
||||
}
|
||||
|
||||
private List<FieldElement> convertToRefFieldElements(String[] comments, Program program,
|
||||
AttributedString currentPrefixString, Address refAddress, int nextRow) {
|
||||
private FieldElement createCommentField(String commentText, AttributedString prototype,
|
||||
Program program, int row, boolean allowAnnotations) {
|
||||
|
||||
int numCommentLines = comments.length;
|
||||
List<FieldElement> fieldElements = new ArrayList<>();
|
||||
if (numCommentLines == 0) {
|
||||
return fieldElements;
|
||||
}
|
||||
for (int rowIndex = 0; rowIndex < numCommentLines; rowIndex++) {
|
||||
int encodedRow = nextRow + rowIndex;
|
||||
fieldElements.add(CommentUtils.parseTextForAnnotations(comments[rowIndex], program,
|
||||
currentPrefixString, encodedRow));
|
||||
}
|
||||
if (prependRefAddress) {
|
||||
FieldElement commentElement = fieldElements.get(0);
|
||||
// Address
|
||||
String refAddrComment = "{@address " + refAddress.toString() + "}";
|
||||
RowColLocation startRowCol = commentElement.getDataLocationForCharacterIndex(0);
|
||||
int encodedRow = startRowCol.row();
|
||||
int encodedCol = startRowCol.col();
|
||||
Annotation annotation = new Annotation(refAddrComment, program);
|
||||
FieldElement addressElement =
|
||||
new AnnotatedTextFieldElement(annotation, currentPrefixString, program, encodedRow,
|
||||
encodedCol);
|
||||
|
||||
// Space character
|
||||
AttributedString spaceStr = new AttributedString(" ", currentPrefixString.getColor(0),
|
||||
currentPrefixString.getFontMetrics(0), false, null);
|
||||
FieldElement spacerElement = new TextFieldElement(spaceStr, encodedRow, encodedCol);
|
||||
fieldElements.add(new CompositeFieldElement(
|
||||
new FieldElement[] { addressElement, spacerElement, commentElement }));
|
||||
if (allowAnnotations) {
|
||||
return CommentUtils.parseTextForAnnotations(commentText, program, prototype, row);
|
||||
}
|
||||
|
||||
if (isWordWrap) {
|
||||
int lineWidth = showSemicolon ? width - currentPrefixString.getStringWidth() : width;
|
||||
fieldElements = FieldUtils.wrap(fieldElements, lineWidth);
|
||||
String text = StringUtilities.convertTabsToSpaces(commentText);
|
||||
AttributedString as = new AttributedString(text, prototype.getColor(0),
|
||||
prototype.getFontMetrics(0), false, null);
|
||||
return new TextFieldElement(as, row, 0);
|
||||
}
|
||||
|
||||
private void prependRefAddress(Program program, AttributedString prefix, Address refAddress,
|
||||
List<FieldElement> fieldElements) {
|
||||
|
||||
if (!prependRefAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (showSemicolon) {
|
||||
for (int i = 0; i < fieldElements.size(); i++) {
|
||||
RowColLocation startRowCol =
|
||||
fieldElements.get(i).getDataLocationForCharacterIndex(0);
|
||||
int encodedRow = startRowCol.row();
|
||||
int encodedCol = startRowCol.col();
|
||||
FieldElement prefixFieldElement =
|
||||
new TextFieldElement(currentPrefixString, encodedRow, encodedCol);
|
||||
fieldElements.set(i, new CompositeFieldElement(
|
||||
new FieldElement[] { prefixFieldElement, fieldElements.get(i) }));
|
||||
}
|
||||
}
|
||||
return fieldElements;
|
||||
FieldElement commentElement = fieldElements.get(0);
|
||||
|
||||
// Address
|
||||
String refAddrComment = "{@address " + refAddress.toString() + "}";
|
||||
RowColLocation startRowCol = commentElement.getDataLocationForCharacterIndex(0);
|
||||
int encodedRow = startRowCol.row();
|
||||
int encodedCol = startRowCol.col();
|
||||
Annotation annotation = new Annotation(refAddrComment, program);
|
||||
FieldElement addressElement =
|
||||
new AnnotatedTextFieldElement(annotation, prefix, program, encodedRow, encodedCol);
|
||||
|
||||
// Space character
|
||||
AttributedString spaceStr = new AttributedString(" ", prefix.getColor(0),
|
||||
prefix.getFontMetrics(0), false, null);
|
||||
FieldElement spacerElement = new TextFieldElement(spaceStr, encodedRow, encodedCol);
|
||||
fieldElements.set(0, new CompositeFieldElement(
|
||||
new FieldElement[] { addressElement, spacerElement, commentElement }));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -879,6 +879,18 @@ public class ProgramBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a non-null-terminated ascii string at the given address
|
||||
* @param address the address
|
||||
* @param string the string
|
||||
* @return the new data
|
||||
* @throws Exception if there is an exception
|
||||
*/
|
||||
public Data createString(String address, String string) throws Exception {
|
||||
return createString(address, string, StandardCharsets.US_ASCII, false,
|
||||
StringDataType.dataType);
|
||||
}
|
||||
|
||||
public Data createString(String address, String string, Charset charset, boolean nullTerminate,
|
||||
DataType dataType) throws Exception {
|
||||
if (nullTerminate) {
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package ghidra.app.util.viewer.field;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.StringContains.*;
|
||||
import static org.hamcrest.core.StringStartsWith.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -29,10 +31,17 @@ import ghidra.program.database.ProgramDB;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.address.AddressFactory;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.RefType;
|
||||
import ghidra.program.model.symbol.SourceType;
|
||||
import ghidra.test.*;
|
||||
|
||||
public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationTest {
|
||||
|
||||
private static final String STRING_ADDRESS_WITH_ANNOTATION = "0x01002c98";
|
||||
private static final String ADDRESS_CALLING_STRING_WITH_ANNOTATION = "0X01002d37";
|
||||
private static final String AUTO_COMMENT_TEXT_WITH_ANNOTATION =
|
||||
"Annotation: {@address 12345678 foo}";
|
||||
|
||||
private TestEnv env;
|
||||
private CodeBrowserPlugin cb;
|
||||
private Options fieldOptions;
|
||||
@@ -75,21 +84,45 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
|
||||
public void testRepeatableComment_FunctionCall() throws Exception {
|
||||
|
||||
// check existing auto comment
|
||||
ListingTextField tf = getFieldText(addr("0x010022e6"));
|
||||
String from = "0x010022e6";
|
||||
ListingTextField tf = getFieldText(addr(from));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertThat(tf.getText(), startsWith("undefined ghidra(undefined4 param_1,"));
|
||||
|
||||
// set repeatable comment at destination
|
||||
Address destination = addr("0x01002cf5");
|
||||
// set repeatable comment at source
|
||||
String to = "0x01002cf5";
|
||||
String repeatableComment = "My repeatable comment";
|
||||
setRepeatableComment(destination, repeatableComment);
|
||||
setRepeatableComment(addr(to), repeatableComment);
|
||||
|
||||
// check that the auto comment now matches the updated comment
|
||||
tf = getFieldText(addr("0x010022e6"));
|
||||
// check that the repeatable comment now matches the updated comment
|
||||
tf = getFieldText(addr(from));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertEquals(tf.getText(), repeatableComment);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatableComment_FunctionCall_PrependRefAddress() throws Exception {
|
||||
|
||||
setBooleanOption(EolCommentFieldFactory.ENABLE_PREPEND_REF_ADDRESS_KEY, true);
|
||||
|
||||
// check existing auto comment
|
||||
String from = "0x010022e6";
|
||||
ListingTextField tf = getFieldText(addr(from));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertThat(tf.getText(), startsWith("undefined ghidra(undefined4 param_1,"));
|
||||
|
||||
// set repeatable comment at source
|
||||
String to = "0x01002cf5";
|
||||
String repeatableComment = "My repeatable comment";
|
||||
setRepeatableComment(addr(to), repeatableComment);
|
||||
|
||||
// check that the repeatable comment now matches the updated comment and has the ref address
|
||||
// prepended
|
||||
tf = getFieldText(addr(from));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertEquals("01002cf5 " + repeatableComment, tf.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRepeatableComment_DataAccess() throws Exception {
|
||||
|
||||
@@ -106,7 +139,21 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
|
||||
// check that the auto comment now matches the updated comment
|
||||
tf = getFieldText(addr("0x01002265"));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertEquals(tf.getText(), repeatableComment);
|
||||
assertEquals(repeatableComment, tf.getText());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutoCommentDoesNotRenderAnnotation() {
|
||||
|
||||
/*
|
||||
Creates a data reference to a string containing an annotation. Tests that the
|
||||
annotation is not rendered, but is shown in its raw form.
|
||||
*/
|
||||
|
||||
goTo(env.getTool(), program, STRING_ADDRESS_WITH_ANNOTATION);
|
||||
ListingTextField tf = getFieldText(addr(ADDRESS_CALLING_STRING_WITH_ANNOTATION));
|
||||
assertEquals(1, tf.getNumRows());
|
||||
assertThat(tf.getText(), containsString(AUTO_COMMENT_TEXT_WITH_ANNOTATION));
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
@@ -115,6 +162,13 @@ public class EolCommentFieldFactoryTest extends AbstractGhidraHeadedIntegrationT
|
||||
|
||||
private ProgramDB buildProgram() throws Exception {
|
||||
ClassicSampleX86ProgramBuilder builder = new ClassicSampleX86ProgramBuilder();
|
||||
|
||||
builder.createString(STRING_ADDRESS_WITH_ANNOTATION, AUTO_COMMENT_TEXT_WITH_ANNOTATION);
|
||||
|
||||
builder.createMemoryReference(ADDRESS_CALLING_STRING_WITH_ANNOTATION,
|
||||
STRING_ADDRESS_WITH_ANNOTATION,
|
||||
RefType.DATA, SourceType.ANALYSIS);
|
||||
|
||||
return builder.getProgram();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user