diff --git a/Ghidra/Features/Base/data/base.listing.theme.properties b/Ghidra/Features/Base/data/base.listing.theme.properties
index ff03aea7e4..8d58d46fbb 100644
--- a/Ghidra/Features/Base/data/base.listing.theme.properties
+++ b/Ghidra/Features/Base/data/base.listing.theme.properties
@@ -37,6 +37,7 @@ color.fg.listing.comment.ref.repeatable = color.palette.cornflowerblue
color.fg.listing.comment.plate = color.palette.gray
color.fg.listing.comment.post = color.palette.blue
color.fg.listing.comment.pre = color.palette.indigo
+color.fg.listing.comment.offcut = color.fg.error
color.fg.listing.ref.bad = color.fg.error
color.fg.listing.ext.entrypoint = color.palette.magenta
color.fg.listing.ext.ref.resolved = color.palette.darkorange
diff --git a/Ghidra/Features/Base/src/main/help/help/topics/CommentsPlugin/Comments.htm b/Ghidra/Features/Base/src/main/help/help/topics/CommentsPlugin/Comments.htm
index 8c9dba9a83..d32d08c493 100644
--- a/Ghidra/Features/Base/src/main/help/help/topics/CommentsPlugin/Comments.htm
+++ b/Ghidra/Features/Base/src/main/help/help/topics/CommentsPlugin/Comments.htm
@@ -73,6 +73,15 @@
annotations to change the display
characteristics of data entered into the comment fields.
+
A comment can also be offcut. This means it is
+ defined on an address that is in the middle of an instruction or data item. In this case,
+ the offcut comment will be displayed in the appropriate field (EOL, PLATE, POST, PRE) on the
+ instruction or data that contains that address and will be colored red to indicate the comment
+ has been attached to an invalid address. Note that this comment can only be edited if the
+ containing instruction or data is first cleared so that the individual addresses are shown in
+ the listing.
+
+
Adding or Editing
Comments (Set Comment)
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/EolComments.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/EolComments.java
index 3fadd068ec..27f3c5f16c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/EolComments.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/EolComments.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -20,8 +20,7 @@ import java.util.*;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.fieldpanel.support.RowColLocation;
-import ghidra.app.util.viewer.field.EolEnablement;
-import ghidra.app.util.viewer.field.EolExtraCommentsOption;
+import ghidra.app.util.viewer.field.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
@@ -53,6 +52,7 @@ public class EolComments {
private List refRepeatables = new ArrayList<>();
private List autos = new ArrayList<>();
private List references = new ArrayList<>();
+ private List offcuts = new ArrayList<>();
// used to signal the operand is already displaying a pointer reference, so there is no need for
// this class to create a comment to do the same
@@ -75,6 +75,7 @@ public class EolComments {
loadRepeatables();
loadRefRepeatables();
loadAutos();
+ loadOffcutEols();
}
/**
@@ -101,10 +102,15 @@ public class EolComments {
private void loadEols() {
Collection comments =
- Arrays.asList(codeUnit.getCommentAsArray(CodeUnit.EOL_COMMENT));
+ Arrays.asList(codeUnit.getCommentAsArray(CommentType.EOL));
addStrings(comments, eols);
}
+ private void loadOffcutEols() {
+ Collection comments = CommentUtils.getOffcutComments(codeUnit, CommentType.EOL);
+ addStrings(comments, offcuts);
+ }
+
private void loadRepeatables() {
boolean hasOtherComments = !eols.isEmpty();
if (!extraCommentsOption.isShowingRepeatables(hasOtherComments)) {
@@ -195,6 +201,10 @@ public class EolComments {
return !autos.isEmpty();
}
+ public boolean isShowingOffcutComments() {
+ return !offcuts.isEmpty();
+ }
+
private Collection getReferencePreviews() {
loadReferences();
@@ -642,6 +652,10 @@ public class EolComments {
return Collections.unmodifiableList(autos);
}
+ public List getOffcutEolComments() {
+ return Collections.unmodifiableList(offcuts);
+ }
+
@Override
public String toString() {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java
index 5404a63e5f..e23c86658a 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/CommentUtils.java
@@ -28,8 +28,8 @@ import docking.widgets.fieldpanel.field.*;
import generic.theme.GThemeDefaults.Colors;
import generic.theme.Gui;
import ghidra.app.util.NamespaceUtils;
-import ghidra.program.model.address.Address;
-import ghidra.program.model.listing.Program;
+import ghidra.program.model.address.*;
+import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolTable;
import ghidra.util.StringUtilities;
@@ -440,4 +440,49 @@ public class CommentUtils {
return Collections.unmodifiableSet(getAnnotatedStringHandlerMap().keySet());
}
+ /**
+ * Returns a list of offcut comments for the given code unit. All the offcut comments from
+ * possibly multiple addresses will be combined into a single list of comment lines.
+ * @param cu the code unit to get offcut comments for
+ * @param type the type of comment to retrieve (EOL, PRE, PLATE, POST)
+ * @return a list of all offcut comments for the given code unit.
+ */
+ public static List getOffcutComments(CodeUnit cu, CommentType type) {
+ // internal data items handle EOL comments, so ignore EOL comments on items that
+ // have sub-components
+ if (type == CommentType.EOL && cu instanceof Data data) {
+ if (data.getNumComponents() > 0) {
+ return Collections.emptyList();
+ }
+ }
+
+ Address start = cu.getMinAddress().next();
+ Address end = cu.getMaxAddress();
+ if (start == null || start.compareTo(end) > 0) {
+ return Collections.emptyList();
+ }
+
+ Listing listing = cu.getProgram().getListing();
+ AddressSet addrSet = new AddressSet(start, cu.getMaxAddress());
+ AddressIterator it = listing.getCommentAddressIterator(type, addrSet, true);
+
+ if (!it.hasNext()) {
+ return Collections.emptyList();
+ }
+
+ List offcutComments = new ArrayList<>();
+
+ while (it.hasNext()) {
+ Address next = it.next();
+ String comment = listing.getComment(type, next);
+ if (comment != null) {
+ String[] lines = StringUtilities.toLines(comment);
+ for (String line : lines) {
+ offcutComments.add(line);
+ }
+ }
+ }
+ return offcutComments;
+ }
+
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java
index 3925dc3bf9..fa6f4ae3de 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/EolCommentFieldFactory.java
@@ -64,6 +64,7 @@ public class EolCommentFieldFactory extends FieldFactory {
private int repeatableCommentStyle;
private int automaticCommentStyle;
private int refRepeatableCommentStyle;
+ private int offcutCommentStyle;
private EolExtraCommentsOption extraCommentsOption = new EolExtraCommentsOption();
@@ -292,6 +293,14 @@ public class EolCommentFieldFactory extends FieldFactory {
elementList.addAll(elements);
}
+ if (comments.isShowingOffcutComments()) {
+ prefix = createPrefix(CommentStyle.OFFCUT);
+ int row = getNextRow(elementList);
+ List offcuts = comments.getOffcutEolComments();
+ List elements = convertToFieldElements(program, offcuts, prefix, row);
+ elementList.addAll(elements);
+ }
+
if (elementList.isEmpty()) {
return null;
}
@@ -316,11 +325,16 @@ public class EolCommentFieldFactory extends FieldFactory {
return new AttributedString(SEMICOLON_PREFIX, CommentColors.AUTO,
getMetrics(automaticCommentStyle), false, null);
}
+ if (commentStyle == CommentStyle.OFFCUT) {
+ return new AttributedString(SEMICOLON_PREFIX, CommentColors.OFFCUT,
+ getMetrics(style), false, null);
+ }
+
throw new AssertException("Unexected comment style: " + commentStyle);
}
private enum CommentStyle {
- EOL, REPEATABLE, REF_REPEATABLE, AUTO;
+ EOL, REPEATABLE, REF_REPEATABLE, AUTO, OFFCUT;
}
private int getNextRow(List elementList) {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ListingColors.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ListingColors.java
index 0941c7ac64..b557e55280 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ListingColors.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/ListingColors.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -69,6 +69,7 @@ public class ListingColors {
public static final GColor PRE= new GColor("color.fg.listing.comment.pre");
public static final GColor REPEATABLE = new GColor("color.fg.listing.comment.repeatable");
public static final GColor REF_REPEATABLE = new GColor("color.fg.listing.comment.ref.repeatable");
+ public static final GColor OFFCUT = new GColor("color.fg.listing.comment.offcut");
}
public static class LabelColors {
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java
index b19be7c7b4..91462a129c 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PlateFieldFactory.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -40,6 +40,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.HelpLocation;
+import util.CollectionUtils;
/**
* Class for showing plate comments
@@ -142,12 +143,14 @@ public class PlateFieldFactory extends FieldFactory {
CodeUnit cu = (CodeUnit) proxy.getObject();
boolean isClipped = false;
List elements = new ArrayList<>();
- String commentText = getCommentText(cu);
+ List offcutComments = CommentUtils.getOffcutComments(cu, CommentType.PLATE);
+ String commentText = getCommentText(cu, offcutComments);
+
if (StringUtils.isBlank(commentText)) {
getDefaultFieldElements(cu, elements);
}
else {
- isClipped = getFormattedFieldElements(cu, elements);
+ isClipped = getFormattedFieldElements(cu, elements, offcutComments);
}
if (elements.isEmpty()) {
@@ -170,14 +173,15 @@ public class PlateFieldFactory extends FieldFactory {
return listingField;
}
- private boolean getFormattedFieldElements(CodeUnit cu, List elements) {
+ private boolean getFormattedFieldElements(CodeUnit cu, List elements,
+ List offcutComments) {
int numberBlankLines = getNumberBlankLines(cu, true);
addBlankLines(elements, numberBlankLines, cu);
String[] comments = cu.getCommentAsArray(CodeUnit.PLATE_COMMENT);
- return generateFormattedPlateComment(elements, comments, cu.getProgram());
+ return generateFormattedPlateComment(elements, comments, offcutComments, cu.getProgram());
}
private void getDefaultFieldElements(CodeUnit cu, List elements) {
@@ -205,7 +209,7 @@ public class PlateFieldFactory extends FieldFactory {
return false;
}
- private String getCommentText(CodeUnit cu) {
+ private String getCommentText(CodeUnit cu, List offcutComments) {
String[] comments = cu.getCommentAsArray(CodeUnit.PLATE_COMMENT);
if (comments == null) {
return null;
@@ -218,6 +222,12 @@ public class PlateFieldFactory extends FieldFactory {
}
buffy.append(comment);
}
+ for (String offcut : offcutComments) {
+ if (buffy.length() != 0) {
+ buffy.append('\n');
+ }
+ buffy.append(offcut);
+ }
return buffy.toString();
}
@@ -226,8 +236,8 @@ public class PlateFieldFactory extends FieldFactory {
* data is clipped because it is too long to display.
*/
private boolean generateFormattedPlateComment(List elements, String[] comments,
- Program p) {
- if (comments == null || comments.length == 0) {
+ List offcutComments, Program p) {
+ if (offcutComments.isEmpty() && CollectionUtils.isBlank(comments)) {
return false;
}
@@ -245,6 +255,11 @@ public class PlateFieldFactory extends FieldFactory {
for (String c : comments) {
commentsList.add(CommentUtils.parseTextForAnnotations(c, p, prototype, row++));
}
+ for (String offcut : offcutComments) {
+ AttributedString as = new AttributedString(offcut, CommentColors.OFFCUT,
+ getMetrics(style), false, null);
+ commentsList.add(new TextFieldElement(as, commentsList.size(), 0));
+ }
if (isWordWrap) {
int spaceWidth = getMetrics().charWidth(' ');
@@ -554,7 +569,8 @@ public class PlateFieldFactory extends FieldFactory {
*/
CodeUnit cu = (CodeUnit) obj;
- String commentText = getCommentText(cu);
+ List offcutComments = CommentUtils.getOffcutComments(cu, CommentType.PLATE);
+ String commentText = getCommentText(cu, offcutComments);
boolean hasComment = true;
if (StringUtils.isBlank(commentText)) {
String defaultComment = getDefaultComment(cu);
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java
index f90ffda7bc..a198097ceb 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PostCommentFieldFactory.java
@@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -137,21 +137,22 @@ public class PostCommentFieldFactory extends FieldFactory {
}
String[] autoComment = getAutoPostComment(cu);
+ List offcutComments = CommentUtils.getOffcutComments(cu, CommentType.POST);
String[] comments = cu.getCommentAsArray(CodeUnit.POST_COMMENT);
if (comments != null && comments.length > 0 && (cu instanceof Data)) {
- return getTextField(comments, autoComment, proxy, x, false);
+ return getTextField(comments, autoComment, offcutComments, proxy, x, false);
}
if (cu instanceof Instruction) {
Instruction instr = (Instruction) cu;
if (instr.getDelaySlotDepth() > 0) {
if (comments != null && comments.length > 0) {
- return getTextField(comments, null, proxy, x, false);
+ return getTextField(comments, null, offcutComments, proxy, x, false);
}
return null;
}
// check field options
- return getTextFieldForOptions(instr, comments, autoComment, proxy, x);
+ return getTextFieldForOptions(instr, comments, autoComment, offcutComments, proxy, x);
}
return null;
}
@@ -386,7 +387,7 @@ public class PostCommentFieldFactory extends FieldFactory {
}
private ListingTextField getTextFieldForOptions(Instruction instr, String[] comments,
- String[] autoComment, ProxyObj> proxy, int xStart) {
+ String[] autoComment, List offcutComments, ProxyObj> proxy, int xStart) {
Listing listing = instr.getProgram().getListing();
Address addr = instr.getMinAddress();
FlowType flowType = instr.getFlowType();
@@ -405,14 +406,14 @@ public class PostCommentFieldFactory extends FieldFactory {
String[] str = new String[] {
FUN_EXIT_FLAG_LEADER + function.getName() + FUN_EXIT_FLAG_TAIL };
- return getTextField(str, autoComment, proxy, xStart, true);
+ return getTextField(str, autoComment, offcutComments, proxy, xStart, true);
}
}
}
// Add Jump/Terminator
if (flagJMPsRETs && !instr.hasFallthrough()) {
String[] str = new String[] { DEFAULT_FLAG_COMMENT };
- return getTextField(str, autoComment, proxy, xStart, true);
+ return getTextField(str, autoComment, offcutComments, proxy, xStart, true);
}
}
@@ -472,11 +473,12 @@ public class PostCommentFieldFactory extends FieldFactory {
}
}
}
- return getTextField(comments, autoComment, proxy, xStart, false);
+ return getTextField(comments, autoComment, offcutComments, proxy, xStart, false);
}
private ListingTextField getTextField(String[] comments, String[] autoComment,
- ProxyObj> proxy, int xStart, boolean useLinesAfterBlock) {
+ List offcutComments, ProxyObj> proxy, int xStart,
+ boolean useLinesAfterBlock) {
if (comments == null) {
comments = EMPTY_STRING_ARRAY;
@@ -489,7 +491,8 @@ public class PostCommentFieldFactory extends FieldFactory {
((comments.length == 0 && !useLinesAfterBlock) || alwaysShowAutomatic)
? autoComment.length
: 0;
- if (!useLinesAfterBlock && comments.length == 0 && nLinesAutoComment == 0) {
+ if (!useLinesAfterBlock && comments.length == 0 && nLinesAutoComment == 0 &&
+ offcutComments.isEmpty()) {
return null;
}
@@ -507,6 +510,11 @@ public class PostCommentFieldFactory extends FieldFactory {
fields.add(CommentUtils.parseTextForAnnotations(comment, program, prototypeString,
fields.size()));
}
+ for (String offcutComment : offcutComments) {
+ AttributedString as = new AttributedString(offcutComment, CommentColors.OFFCUT,
+ getMetrics(style), false, null);
+ fields.add(new TextFieldElement(as, fields.size(), 0));
+ }
if (isWordWrap) {
fields = FieldUtils.wrap(fields, width);
}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PreCommentFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PreCommentFieldFactory.java
index 3f3dca3358..840a3988e3 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PreCommentFieldFactory.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/PreCommentFieldFactory.java
@@ -112,8 +112,9 @@ public class PreCommentFieldFactory extends FieldFactory {
String[] autoComment = getAutoPreComments(cu);
String[] comments = getDefinedPreComments(cu);
+ List offcutComments = CommentUtils.getOffcutComments(cu, CommentType.PRE);
- return getTextField(comments, autoComment, proxy, x);
+ return getTextField(comments, autoComment, offcutComments, proxy, x);
}
private String[] getDefinedPreComments(CodeUnit cu) {
@@ -353,7 +354,7 @@ public class PreCommentFieldFactory extends FieldFactory {
}
private ListingTextField getTextField(String[] comments, String[] autoComment,
- ProxyObj> proxy, int xStart) {
+ List offcutComments, ProxyObj> proxy, int xStart) {
if (comments == null) {
comments = EMPTY_STRING_ARRAY;
@@ -364,7 +365,8 @@ public class PreCommentFieldFactory extends FieldFactory {
int nLinesAutoComment =
(comments.length == 0 || alwaysShowAutomatic) ? autoComment.length : 0;
- if (comments.length == 0 && nLinesAutoComment == 0) {
+
+ if (comments.length == 0 && nLinesAutoComment == 0 && offcutComments.isEmpty()) {
return null;
}
@@ -382,6 +384,12 @@ public class PreCommentFieldFactory extends FieldFactory {
fields.add(CommentUtils.parseTextForAnnotations(comment, program, prototypeString,
fields.size()));
}
+ for (String offcutComment : offcutComments) {
+ AttributedString as = new AttributedString(offcutComment, CommentColors.OFFCUT,
+ getMetrics(style), false, null);
+ fields.add(new TextFieldElement(as, fields.size(), 0));
+ }
+
if (isWordWrap) {
fields = FieldUtils.wrap(fields, width);
}