diff --git a/Ghidra/Features/Base/data/base.listing.theme.properties b/Ghidra/Features/Base/data/base.listing.theme.properties index 8d58d46fbb..f7f7fbcfa8 100644 --- a/Ghidra/Features/Base/data/base.listing.theme.properties +++ b/Ghidra/Features/Base/data/base.listing.theme.properties @@ -40,7 +40,7 @@ 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 +color.fg.listing.ext.ref.resolved = color.palette.teal color.fg.listing.ext.ref.unresolved = color.fg.listing.ref.bad color.fg.listing.fieldname = color.fg diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/SymbolInspector.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/SymbolInspector.java index 7b6ef46bb5..d184457125 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/SymbolInspector.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/SymbolInspector.java @@ -382,10 +382,10 @@ public class SymbolInspector implements OptionsChangeListener { else if (isVariableSymbol(s)) { if (s.getSymbolType() == SymbolType.PARAMETER) { Function function = (Function) s.getParentNamespace(); - return function.hasCustomVariableStorage() ? OptionsGui.PARAMETER_CUSTOM - : OptionsGui.PARAMETER_DYNAMIC; + return function.hasCustomVariableStorage() ? OptionsGui.FUN_PARAM_CUSTOM + : OptionsGui.FUN_PARAM_DYNAMIC; } - return OptionsGui.VARIABLE; + return OptionsGui.FUN_VARIABLE; } else if (isPrimarySymbol(s)) { return OptionsGui.LABELS_PRIMARY; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AbstractVariableFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AbstractVariableFieldFactory.java index 5a34b674e0..3b24f4e6d8 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AbstractVariableFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/AbstractVariableFieldFactory.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. @@ -81,9 +81,9 @@ public abstract class AbstractVariableFieldFactory extends FieldFactory { parameterFieldOptions = new ParameterFieldOptions[2]; parameterFieldOptions[CUSTOM_PARAM_INDEX] = - new ParameterFieldOptions(OptionsGui.PARAMETER_CUSTOM); + new ParameterFieldOptions(OptionsGui.FUN_PARAM_CUSTOM); parameterFieldOptions[DYNAMIC_PARAM_INDEX] = - new ParameterFieldOptions(OptionsGui.PARAMETER_DYNAMIC); + new ParameterFieldOptions(OptionsGui.FUN_PARAM_DYNAMIC); for (int i = 0; i < 2; i++) { parameterFieldOptions[i].style = @@ -122,11 +122,16 @@ public abstract class AbstractVariableFieldFactory extends FieldFactory { } protected Color getColor(Variable var) { - if (var instanceof Parameter) { + if (var instanceof Parameter param) { + + if (param.isAutoParameter()) { + return FunctionColors.PARAM_AUTO; + } + return var.getFunction().hasCustomVariableStorage() ? FunctionColors.PARAM_CUSTOM : FunctionColors.PARAM_DYNAMIC; } - return FunctionColors.PARAM; + return FunctionColors.VARIABLE; } protected FontMetrics getMetrics(Variable var) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java index edde1b3ab4..09a5c63635 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/OperandFieldHelper.java @@ -16,6 +16,7 @@ package ghidra.app.util.viewer.field; import java.awt.Color; +import java.awt.FontMetrics; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; @@ -23,17 +24,10 @@ import java.util.List; import javax.swing.Icon; import javax.swing.event.ChangeListener; -import docking.widgets.fieldpanel.field.AbstractTextFieldElement; -import docking.widgets.fieldpanel.field.AttributedString; -import docking.widgets.fieldpanel.field.CompositeFieldElement; -import docking.widgets.fieldpanel.field.FieldElement; -import docking.widgets.fieldpanel.support.FieldLocation; -import docking.widgets.fieldpanel.support.FieldUtils; -import docking.widgets.fieldpanel.support.RowColLocation; +import docking.widgets.fieldpanel.field.*; +import docking.widgets.fieldpanel.support.*; import ghidra.GhidraOptions; -import ghidra.app.util.ColorAndStyle; -import ghidra.app.util.ListingHighlightProvider; -import ghidra.app.util.SymbolInspector; +import ghidra.app.util.*; import ghidra.app.util.viewer.field.ListingColors.FunctionColors; import ghidra.app.util.viewer.format.FieldFormatModel; import ghidra.app.util.viewer.options.OptionsGui; @@ -41,30 +35,14 @@ import ghidra.app.util.viewer.proxy.ProxyObj; import ghidra.framework.options.Options; import ghidra.framework.options.ToolOptions; import ghidra.program.model.address.Address; -import ghidra.program.model.data.DataImage; -import ghidra.program.model.data.DataType; +import ghidra.program.model.data.*; import ghidra.program.model.data.Enum; -import ghidra.program.model.data.Playable; -import ghidra.program.model.data.Union; import ghidra.program.model.lang.Register; -import ghidra.program.model.listing.CodeUnit; -import ghidra.program.model.listing.Data; -import ghidra.program.model.listing.Instruction; -import ghidra.program.model.listing.LabelString; -import ghidra.program.model.listing.OperandRepresentationList; -import ghidra.program.model.listing.Program; -import ghidra.program.model.listing.Variable; -import ghidra.program.model.listing.VariableOffset; +import ghidra.program.model.listing.*; +import ghidra.program.model.listing.LabelString.LabelType; import ghidra.program.model.scalar.Scalar; -import ghidra.program.model.symbol.Equate; -import ghidra.program.model.symbol.EquateTable; -import ghidra.program.model.symbol.Reference; -import ghidra.program.model.symbol.ReferenceManager; -import ghidra.program.model.symbol.Symbol; -import ghidra.program.model.symbol.SymbolTable; -import ghidra.program.util.EquateOperandFieldLocation; -import ghidra.program.util.OperandFieldLocation; -import ghidra.program.util.ProgramLocation; +import ghidra.program.model.symbol.*; +import ghidra.program.util.*; import ghidra.util.HelpLocation; /** @@ -260,81 +238,93 @@ abstract class OperandFieldHelper extends FieldFactory { int subOpIndex = element.getOperandSubIndex(); RowColLocation translatedLocation = btf.screenToDataLocation(row, col); + int dataCol = translatedLocation.col(); if (obj instanceof Instruction) { - Instruction inst = (Instruction) obj; - OperandRepresentationList operandRepresentationList = - codeUnitFormat.getOperandRepresentationList(inst, opIndex); - String repStr = ""; - Address refAddr = null; - VariableOffset variableOffset = null; - Program program = inst.getProgram(); - if (operandRepresentationList == null) { - return new OperandFieldLocation(program, inst.getMinAddress(), variableOffset, - refAddr, repStr, opIndex, subOpIndex, translatedLocation.col()); - } - - repStr = operandRepresentationList.toString(); - if (subOpIndex >= 0 && operandRepresentationList.size() > subOpIndex) { - Object rep = operandRepresentationList.get(subOpIndex); - if (rep instanceof Address) { - refAddr = (Address) rep; - } - else { - int extendedRefIndex = repStr.indexOf("=>"); - if (extendedRefIndex < 0 || translatedLocation.col() <= extendedRefIndex) { - // only get variable offset if extended reference was not clicked on - variableOffset = getVariableOffset(rep); - } - refAddr = inst.getAddress(opIndex); - if (refAddr == null) { - // Check for inferred variable reference - refAddr = getVariableStorageAddress(inst, operandRepresentationList, - element.getText()); - } - - if (rep instanceof Equate) { - Equate equate = (Equate) rep; - return new EquateOperandFieldLocation(program, inst.getMinAddress(), - refAddr, repStr, equate, opIndex, subOpIndex, translatedLocation.col()); - } - } - } - return new OperandFieldLocation(program, inst.getMinAddress(), variableOffset, refAddr, - repStr, opIndex, subOpIndex, translatedLocation.col()); + return createInstructionLocation(obj, element, opIndex, subOpIndex, dataCol); } else if (obj instanceof Data) { - Data data = (Data) obj; - Address refAddr = null; - Program program = data.getProgram(); - ReferenceManager referenceManager = program.getReferenceManager(); - Address minAddress = data.getMinAddress(); - Reference primaryReference = referenceManager.getPrimaryReferenceFrom(minAddress, 0); - Object value = data.getValue(); - if (primaryReference != null) { - refAddr = primaryReference.getToAddress(); - } - else { - if (value instanceof Address) { - refAddr = (Address) value; - } - } - - if (value instanceof Scalar) { - Scalar scalar = (Scalar) value; - EquateTable equateTable = program.getEquateTable(); - Equate equate = equateTable.getEquate(minAddress, opIndex, scalar.getValue()); - if (equate != null) { - return new EquateOperandFieldLocation(program, minAddress, refAddr, - equate.getDisplayName(), equate, opIndex, subOpIndex, - translatedLocation.col()); - } - } - return new OperandFieldLocation(program, minAddress, data.getComponentPath(), refAddr, - codeUnitFormat.getDataValueRepresentationString(data), 0, col); + return createDataLocation(obj, opIndex, subOpIndex, dataCol, col); } return null; } + private ProgramLocation createDataLocation(Object obj, int opIndex, int subOpIndex, + int dataCol, int screenCol) { + + Data data = (Data) obj; + Address refAddr = null; + Program program = data.getProgram(); + ReferenceManager referenceManager = program.getReferenceManager(); + Address minAddress = data.getMinAddress(); + Reference primaryReference = referenceManager.getPrimaryReferenceFrom(minAddress, 0); + Object value = data.getValue(); + if (primaryReference != null) { + refAddr = primaryReference.getToAddress(); + } + else { + if (value instanceof Address) { + refAddr = (Address) value; + } + } + + if (value instanceof Scalar) { + Scalar scalar = (Scalar) value; + EquateTable equateTable = program.getEquateTable(); + Equate equate = equateTable.getEquate(minAddress, opIndex, scalar.getValue()); + if (equate != null) { + return new EquateOperandFieldLocation(program, minAddress, refAddr, + equate.getDisplayName(), equate, opIndex, subOpIndex, dataCol); + } + } + return new OperandFieldLocation(program, minAddress, data.getComponentPath(), refAddr, + codeUnitFormat.getDataValueRepresentationString(data), 0, screenCol); + } + + private ProgramLocation createInstructionLocation(Object obj, OperandFieldElement element, + int opIndex, int subOpIndex, int dataCol) { + + Instruction inst = (Instruction) obj; + OperandRepresentationList opRepList = + codeUnitFormat.getOperandRepresentationList(inst, opIndex); + Address minAddress = inst.getMinAddress(); + Address refAddr = null; + VariableOffset variableOffset = null; + Program program = inst.getProgram(); + if (opRepList == null) { + return new OperandFieldLocation(program, minAddress, variableOffset, + refAddr, "", opIndex, subOpIndex, dataCol); + } + + String repStr = opRepList.toString(); + if (subOpIndex >= 0 && opRepList.size() > subOpIndex) { + Object rep = opRepList.get(subOpIndex); + if (rep instanceof Address) { + refAddr = (Address) rep; + } + else { + int extendedRefIndex = repStr.indexOf("=>"); + if (extendedRefIndex < 0 || dataCol <= extendedRefIndex) { + // only get variable offset if extended reference was not clicked on + variableOffset = getVariableOffset(rep); + } + refAddr = inst.getAddress(opIndex); + if (refAddr == null) { + // Check for inferred variable reference + refAddr = getVariableStorageAddress(inst, opRepList, element.getText()); + } + + if (rep instanceof Equate) { + Equate equate = (Equate) rep; + return new EquateOperandFieldLocation(program, minAddress, + refAddr, repStr, equate, opIndex, subOpIndex, dataCol); + } + } + } + + return new OperandFieldLocation(program, minAddress, variableOffset, refAddr, + repStr, opIndex, subOpIndex, dataCol); + } + private VariableOffset getVariableOffset(Object representation) { if (representation instanceof VariableOffset) { return (VariableOffset) representation; @@ -463,33 +453,23 @@ abstract class OperandFieldHelper extends FieldFactory { return null; } - List elements = new ArrayList<>(); - int characterOffset = createSeparatorFieldElement(inst, 0, 0, 0, 0, elements); + OpFieldResults results = new OpFieldResults(proxy); + OperandFieldElement separator = createLeadingSeparatorElement(inst); + if (separator != null) { + results.add(separator); + } for (int opIndex = 0; opIndex < numOperands; opIndex++) { - OperandRepresentationList operandRepresentationList = - codeUnitFormat.getOperandRepresentationList(inst, opIndex); - characterOffset = addElementsForOperand(inst, elements, opIndex, - operandRepresentationList, characterOffset); - characterOffset = 0; + OpInfo opInfo = new OpInfo(inst, opIndex); + addOperandElements(opInfo, results); + results.resetCharacterOffset(); } - // There may be operands with no representation objects, so we don't want to create a - // composite field element. - if (elements.isEmpty()) { + // If this is an operand with no representation objects, don't create a visual element + if (results.isEmpty()) { return null; } - if (wrapOnSemicolon) { - List lines = breakIntoLines(elements); - if (lines.size() == 1) { - return ListingTextField.createSingleLineTextField(this, proxy, lines.get(0), - startX + varWidth, width, hlProvider); - } - return ListingTextField.createMultilineTextField(this, proxy, lines, startX, width, - hlProvider); - } - return ListingTextField.createSingleLineTextField(this, proxy, - new CompositeFieldElement(elements), startX + varWidth, width, hlProvider); + return results.createListingTextField(varWidth); } private List breakIntoLines(List elements) { @@ -518,80 +498,208 @@ abstract class OperandFieldHelper extends FieldFactory { return fieldElements; } - private int addElementsForOperand(Instruction inst, List elements, - int opIndex, OperandRepresentationList opRepList, int characterOffset) { + private void addOperandElements(OpInfo opInfo, OpFieldResults results) { + int subOpIndex = 0; - if (opRepList == null || opRepList.hasError()) { - AttributedString as = - new AttributedString(opRepList != null ? opRepList.toString() : "", - badRefAttributes.colorAttribute, getMetrics(badRefAttributes.styleAttribute), - false, ListingColors.UNDERLINE); - elements.add(new OperandFieldElement(as, opIndex, subOpIndex, characterOffset)); - characterOffset += as.length(); + if (opInfo.isInvalid()) { + addOperandErrorElement(opInfo, results); + return; } - else { - boolean underline = isUnderlined(inst, opIndex, opRepList.isPrimaryReferenceHidden()); - for (; subOpIndex < opRepList.size(); subOpIndex++) { - characterOffset = addElement(inst, elements, opRepList.get(subOpIndex), underline, - opIndex, subOpIndex, characterOffset); - } + + for (; subOpIndex < opInfo.getRepCount(); subOpIndex++) { + Object opElement = opInfo.getRepElement(subOpIndex); + addOperandElement(opInfo, results, opElement, subOpIndex); } + // add in any separator after this operand - return createSeparatorFieldElement(inst, opIndex + 1, opIndex, subOpIndex - 1, - characterOffset, elements); + int separatorIndex = opInfo.opIndex + 1; + OperandFieldElement separator = + createSeparatorElement(opInfo, results, subOpIndex - 1, separatorIndex); + if (separator != null) { + results.add(separator); + } } - private int addElements(Instruction inst, List elements, List objList, - int opIndex, int subOpIndex, boolean underline, int characterOffset) { - for (Object element : objList) { - characterOffset = addElement(inst, elements, element, underline, opIndex, subOpIndex, - characterOffset); + private void addOperandErrorElement(OpInfo opInfo, OpFieldResults results) { + + FontMetrics metrics = getMetrics(badRefAttributes.styleAttribute); + String text = opInfo.getRepText(); + AttributedString as = + new AttributedString(text, badRefAttributes.colorAttribute, metrics, + false, ListingColors.UNDERLINE); + results.add(new OperandFieldElement(as, opInfo.opIndex, 0, results.characterOffset)); + + // add in any separator after this operand + int separatorIndex = opInfo.opIndex + 1; + OperandFieldElement separator = createSeparatorElement(opInfo, results, -1, separatorIndex); + if (separator != null) { + results.add(separator); } - return characterOffset; } - private int addElement(Instruction inst, List elements, Object opElem, - boolean underline, int opIndex, int subOpIndex, int characterOffset) { + private void addOperandSubElements(OpInfo opInfo, OpFieldResults results, + List elements, int subOpIndex) { - if (opElem instanceof VariableOffset) { - List objList = ((VariableOffset) opElem).getObjects(); - return addElements(inst, elements, objList, opIndex, subOpIndex, underline, - characterOffset); + // Special case: '[->Lib::Foo]' + // Most references will paint the label color, typically blue. For external function calls, + // the color will change depending on whether the external function is linked. Handle this + // case specially so that the arrow text will be the same color as the library name. + if (isIndirectReference(elements)) { + addIndirectReference(opInfo, results, elements, subOpIndex); + return; } - if (opElem instanceof List) { - return addElements(inst, elements, (List) opElem, opIndex, subOpIndex, underline, - characterOffset); + for (Object element : elements) { + addOperandElement(opInfo, results, element, subOpIndex); } - - ColorStyleAttributes attributes = getOpAttributes(opElem, inst, opIndex); - - AttributedString as = new AttributedString(opElem.toString(), attributes.colorAttribute, - getMetrics(attributes.styleAttribute), underline, ListingColors.UNDERLINE); - - elements.add(new OperandFieldElement(as, opIndex, subOpIndex, characterOffset)); - if (wrapOnSemicolon && opElem instanceof Character c && c == ';') { - elements.add(LINE_BREAK); - } - return characterOffset + as.length(); } - private int createSeparatorFieldElement(Instruction instruction, int separatorIndex, - int opIndex, int subOpIndex, int characterOffset, List elements) { - String separator = instruction.getSeparator(separatorIndex); + private boolean isIndirectReference(List elements) { + if (elements.size() != 2) { + return false; + } + + Object object = elements.get(0); + String text = object.toString(); + if (!text.equals(CodeUnitFormat.EXTENDED_INDIRECT_REFERENCE_DELIMITER)) { + return false; + } + + Object element = elements.get(1); + return element instanceof LabelString; + } + + private void addIndirectReference(OpInfo opInfo, OpFieldResults results, + List elements, int subOpIndex) { + + Object opElement = elements.get(1); + ColorStyleAttributes attributes = getOpAttributes(opInfo, opElement); + + boolean underline = opInfo.isUnderline(); + Object pointer = elements.get(0).toString(); + String text = pointer + opElement.toString(); + FontMetrics metrics = getMetrics(attributes.styleAttribute); + AttributedString as = new AttributedString(text, attributes.colorAttribute, + metrics, underline, ListingColors.UNDERLINE); + results.add( + new OperandFieldElement(as, opInfo.opIndex, subOpIndex, results.characterOffset)); + } + + private void addOperandElement(OpInfo opInfo, OpFieldResults results, Object opElement, + int subOpIndex) { + + if (opElement instanceof VariableOffset variableOffset) { + addVariableElement(opInfo, results, variableOffset, subOpIndex); + return; + } + + if (opElement instanceof List list) { + addOperandSubElements(opInfo, results, list, subOpIndex); + return; + } + + ColorStyleAttributes attributes = getOpAttributes(opInfo, opElement); + addOperandElementWithAttributes(opInfo, results, opElement, subOpIndex, attributes); + } + + private void addOperandElementWithAttributes(OpInfo opInfo, OpFieldResults results, + Object opElement, int subOpIndex, ColorStyleAttributes attributes) { + + boolean underline = opInfo.isUnderline(); + FontMetrics metrics = getMetrics(attributes.styleAttribute); + AttributedString as = new AttributedString(opElement.toString(), attributes.colorAttribute, + metrics, underline, ListingColors.UNDERLINE); + results.add( + new OperandFieldElement(as, opInfo.opIndex, subOpIndex, results.characterOffset)); + if (wrapOnSemicolon && opElement instanceof Character c && c == ';') { + results.addLineBreak(); + } + } + + private void addVariableElement(OpInfo opInfo, OpFieldResults results, + VariableOffset variableOffset, int subOpIndex) { + + Color color = FunctionColors.VARIABLE; + Variable variable = variableOffset.getVariable(); + if (variable instanceof Parameter param) { + if (param.isAutoParameter()) { + + Object opElement = opInfo.getRepElement(subOpIndex); + addAutoParameterElement(opInfo, results, param, opElement, subOpIndex); + return; + } + + Function f = variable.getFunction(); + color = f.hasCustomVariableStorage() ? FunctionColors.PARAM_CUSTOM + : FunctionColors.PARAM_DYNAMIC; + } + + List objects = variableOffset.getObjects(); + addVariableOffsetElements(opInfo, results, objects, subOpIndex, color); + } + + private void addVariableOffsetElements(OpInfo opInfo, OpFieldResults results, + List elements, int subOpIndex, Color color) { + + ColorStyleAttributes attributes = variableRefAttributes.with(color); + + boolean underline = opInfo.isUnderline(); + for (Object element : elements) { + + FontMetrics metrics = getMetrics(attributes.styleAttribute); + AttributedString as = + new AttributedString(element.toString(), attributes.colorAttribute, + metrics, underline, ListingColors.UNDERLINE); + results.add( + new OperandFieldElement(as, opInfo.opIndex, subOpIndex, results.characterOffset)); + } + } + + private void addAutoParameterElement(OpInfo opInfo, OpFieldResults results, Parameter parameter, + Object opElement, int subOpIndex) { + + Color color = FunctionColors.PARAM_AUTO; + int variableStyle = variableRefAttributes.styleAttribute; + + boolean underline = opInfo.isUnderline(); + FontMetrics metrics = getMetrics(variableStyle); + AttributedString as = new AttributedString(opElement.toString(), color, metrics, underline, + ListingColors.UNDERLINE); + results.add( + new OperandFieldElement(as, opInfo.opIndex, subOpIndex, results.characterOffset)); + } + + private OperandFieldElement createSeparatorElement(OpInfo opInfo, OpFieldResults results, + int subOpIndex, int separatorIndex) { + + String separator = opInfo.getSeparator(separatorIndex); if (separator == null) { - return characterOffset; + return null; } + if (spaceAfterSeparator) { separator += " "; } AttributedString as = new AttributedString(separator, separatorAttributes.colorAttribute, getMetrics(separatorAttributes.styleAttribute)); - OperandFieldElement fieldElement = - new OperandFieldElement(as, opIndex, subOpIndex, characterOffset); - elements.add(fieldElement); - return characterOffset + fieldElement.length(); + return new OperandFieldElement(as, opInfo.opIndex, subOpIndex, results.characterOffset); + } + + private OperandFieldElement createLeadingSeparatorElement(Instruction instruction) { + + String separator = instruction.getSeparator(0); + if (separator == null) { + return null; + } + + if (spaceAfterSeparator) { + separator += " "; + } + + AttributedString as = new AttributedString(separator, separatorAttributes.colorAttribute, + getMetrics(separatorAttributes.styleAttribute)); + return new OperandFieldElement(as, 0, 0, 0); } private boolean isUnderlined(CodeUnit codeUnit, int opIndex, boolean primaryReferenceHidden) { @@ -636,10 +744,10 @@ abstract class OperandFieldHelper extends FieldFactory { return attributes; } - private ColorStyleAttributes getOpAttributes(Object opObject, Instruction inst, int opIndex) { + private ColorStyleAttributes getOpAttributes(OpInfo opInfo, Object opObject) { if (opObject instanceof String) { - return getOpAttributes(inst, opIndex, inst.getProgram()); + return getOpAttributes(opInfo.inst, opInfo.opIndex, opInfo.getProgram()); } if (opObject instanceof Register) { return registerAttributes; @@ -660,17 +768,42 @@ abstract class OperandFieldHelper extends FieldFactory { } return badRefAttributes; } - if (opObject instanceof LabelString) { - LabelString label = (LabelString) opObject; - LabelString.LabelType labelType = label.getLabelType(); - if (labelType == LabelString.LabelType.VARIABLE) { - return variableRefAttributes; - } - return getOpAttributes(inst, opIndex, inst.getProgram()); + if (opObject instanceof LabelString labelString) { + return getLabelStringAttributes(labelString, opInfo.inst, opInfo.opIndex); } return separatorAttributes; } + private ColorStyleAttributes getLabelStringAttributes(LabelString opObject, Instruction inst, + int opIndex) { + + LabelString label = opObject; + LabelType labelType = label.getLabelType(); + if (labelType == LabelType.VARIABLE) { + return variableRefAttributes; + } + if (labelType == LabelType.EXTERNAL) { + + Symbol symbol = label.getSymbol(); + ColorStyleAttributes attributes = getExternalSymbolAttributes(symbol); + if (attributes != null) { + return attributes; + } + } + return getOpAttributes(inst, opIndex, inst.getProgram()); + } + + private ColorStyleAttributes getExternalSymbolAttributes(Symbol symbol) { + ColorAndStyle c = inspector.getColorAndStyle(symbol); + if (c != null) { + ColorStyleAttributes newAttributes = new ColorStyleAttributes(); + newAttributes.colorAttribute = c.getColor(); + newAttributes.styleAttribute = c.getStyle(); + return newAttributes; + } + return null; + } + private ColorStyleAttributes getRefAttributes(CodeUnit cu, int opIndex, Program p) { ReferenceManager refMgr = p.getReferenceManager(); @@ -739,10 +872,7 @@ abstract class OperandFieldHelper extends FieldFactory { return addressAttributes; } - /** - * Called when the fonts are first initialized or when one of the options - * changes. It looks up all the color settings and resets the its values. - */ + // Called when the fonts are first initialized or when one of the options private void setOptions(Options options) { separatorAttributes.colorAttribute = ListingColors.SEPARATOR; @@ -757,7 +887,7 @@ abstract class OperandFieldHelper extends FieldFactory { scalarAttributes.styleAttribute = options.getInt(OptionsGui.CONSTANT.getStyleOptionName(), -1); variableRefAttributes.styleAttribute = - options.getInt(OptionsGui.VARIABLE.getStyleOptionName(), -1); + options.getInt(OptionsGui.FUN_VARIABLE.getStyleOptionName(), -1); addressAttributes.styleAttribute = options.getInt(OptionsGui.ADDRESS.getStyleOptionName(), -1); badRefAttributes.styleAttribute = @@ -771,6 +901,13 @@ abstract class OperandFieldHelper extends FieldFactory { private class ColorStyleAttributes { private Color colorAttribute; private int styleAttribute; + + public ColorStyleAttributes with(Color color) { + ColorStyleAttributes attrs = new ColorStyleAttributes(); + attrs.styleAttribute = styleAttribute; + attrs.colorAttribute = color; + return attrs; + } } static class OperandFieldElement extends AbstractTextFieldElement { @@ -791,9 +928,6 @@ abstract class OperandFieldHelper extends FieldFactory { return row; } - /** - * @see docking.widgets.fieldpanel.field.FieldElement#substring(int, int) - */ @Override public FieldElement substring(int start, int end) { AttributedString as = attributedString.substring(start, end); @@ -803,13 +937,106 @@ abstract class OperandFieldHelper extends FieldFactory { return new OperandFieldElement(as, row, operandSubIndex, column + start); } - /** - * @see docking.widgets.fieldpanel.field.FieldElement#replaceAll(char[], char) - */ @Override public FieldElement replaceAll(char[] targets, char replacement) { return new OperandFieldElement(attributedString.replaceAll(targets, replacement), row, operandSubIndex, column); } } + + /** + * Simple object to pass between methods for accumulating results and tracking character offset. + */ + private class OpFieldResults { + + private int characterOffset; + private List elements = new ArrayList<>(); + private ProxyObj proxy; + + OpFieldResults(ProxyObj proxy) { + this.proxy = proxy; + } + + void add(OperandFieldElement element) { + elements.add(element); + characterOffset += element.length(); + } + + void addLineBreak() { + elements.add(LINE_BREAK); + } + + boolean isEmpty() { + return elements.isEmpty(); + } + + void resetCharacterOffset() { + characterOffset = 0; + } + + ListingTextField createListingTextField(int varWidth) { + + FieldFactory factory = OperandFieldHelper.this; + if (wrapOnSemicolon) { + List lines = breakIntoLines(elements); + if (lines.size() == 1) { + return ListingTextField.createSingleLineTextField(factory, proxy, + lines.get(0), startX + varWidth, width, hlProvider); + } + return ListingTextField.createMultilineTextField(factory, proxy, lines, startX, + width, + hlProvider); + } + + return ListingTextField.createSingleLineTextField(factory, proxy, + new CompositeFieldElement(elements), startX + varWidth, width, hlProvider); + + } + } + + /** + * Simple object to contain data and methods related to the instruction being processed and its + * operand representations. + */ + private class OpInfo { + + private Instruction inst; + private int opIndex; + + private OperandRepresentationList opRepList; + + OpInfo(Instruction inst, int opIndex) { + this.inst = inst; + this.opIndex = opIndex; + this.opRepList = codeUnitFormat.getOperandRepresentationList(inst, opIndex); + } + + boolean isInvalid() { + return opRepList == null || opRepList.hasError(); + } + + boolean isUnderline() { + return isUnderlined(inst, opIndex, opRepList.isPrimaryReferenceHidden()); + } + + int getRepCount() { + return opRepList.size(); + } + + Object getRepElement(int subOpIndex) { + return opRepList.get(subOpIndex); + } + + String getRepText() { + return opRepList != null ? opRepList.toString() : ""; + } + + String getSeparator(int separatorIndex) { + return inst.getSeparator(separatorIndex); + } + + Program getProgram() { + return inst.getProgram(); + } + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableNameFieldFactory.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableNameFieldFactory.java index 83325c27ab..ae0a481264 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableNameFieldFactory.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/VariableNameFieldFactory.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. @@ -45,7 +45,7 @@ public class VariableNameFieldFactory extends AbstractVariableFieldFactory { /** * Constructor * @param model the model that the field belongs to. - * @param hsProvider the HightLightStringProvider. + * @param hlProvider the HightLightStringProvider. * @param displayOptions the Options for display properties. * @param fieldOptions the Options for field specific properties. */ @@ -72,9 +72,6 @@ public class VariableNameFieldFactory extends AbstractVariableFieldFactory { width, hlProvider); } - /** - * @see ghidra.app.util.viewer.field.FieldFactory#getProgramLocation(int, int, ghidra.app.util.viewer.field.ListingField) - */ @Override public ProgramLocation getProgramLocation(int row, int col, ListingField bf) { ProxyObj proxy = bf.getProxy(); @@ -88,9 +85,6 @@ public class VariableNameFieldFactory extends AbstractVariableFieldFactory { return null; } - /** - * @see ghidra.app.util.viewer.field.FieldFactory#getFieldLocation(ghidra.app.util.viewer.field.ListingField, BigInteger, int, ghidra.program.util.ProgramLocation) - */ @Override public FieldLocation getFieldLocation(ListingField bf, BigInteger index, int fieldNum, ProgramLocation loc) { @@ -111,9 +105,6 @@ public class VariableNameFieldFactory extends AbstractVariableFieldFactory { return null; } - /** - * @see ghidra.app.util.viewer.field.FieldFactory#acceptsType(int, java.lang.Class) - */ @Override public boolean acceptsType(int category, Class proxyObjectClass) { if (!Variable.class.isAssignableFrom(proxyObjectClass)) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/options/OptionsGui.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/options/OptionsGui.java index b009403607..62a1474ac1 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/options/OptionsGui.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/options/OptionsGui.java @@ -40,6 +40,7 @@ import generic.theme.GThemeDefaults.Colors.Palette; import ghidra.GhidraOptions; import ghidra.app.util.viewer.field.ListingColors; import ghidra.app.util.viewer.field.ListingColors.*; +import ghidra.util.ColorUtils; /** * Class for displaying and manipulating field colors and fonts. @@ -65,6 +66,9 @@ public class OptionsGui extends JPanel { public static final ScreenElement FUN_CALL_FIXUP = new ScreenElement("Function Call-Fixup", FunctionColors.CALL_FIXUP); public static final ScreenElement FUN_NAME = new ScreenElement("Function Name", FunctionColors.NAME); public static final ScreenElement FUN_PARAMS = new ScreenElement("Function Parameters", FunctionColors.PARAM); + public static final ScreenElement FUN_PARAM_CUSTOM = new ScreenElement("Function Parameter, Custom Storage", FunctionColors.PARAM_CUSTOM); + public static final ScreenElement FUN_PARAM_DYNAMIC = new ScreenElement("Function Parameter, Dynamic Storage", FunctionColors.PARAM_DYNAMIC); + public static final ScreenElement FUN_VARIABLE = new ScreenElement("Function Variable", FunctionColors.VARIABLE); public static final ScreenElement FUN_TAG = new ScreenElement("Function Tag", FunctionColors.TAG); public static final ScreenElement FUN_AUTO_PARAMS = new ScreenElement("Function Auto-Parameters", FunctionColors.PARAM_AUTO); public static final ScreenElement FUN_RET_TYPE = new ScreenElement("Function Return Type", FunctionColors.RETURN_TYPE); @@ -73,7 +77,7 @@ public class OptionsGui extends JPanel { public static final ScreenElement LABELS_LOCAL = new ScreenElement("Labels, Local", LabelColors.LOCAL); public static final ScreenElement MNEMONIC = new ScreenElement("Mnemonic", MnemonicColors.NORMAL); public static final ScreenElement MNEMONIC_OVERRIDE = new ScreenElement("Mnemonic, Override", MnemonicColors.OVERRIDE); - public static final ScreenElement MNEMONIC_UNIMPL = new ScreenElement("Unimplemented Mnemonic", MnemonicColors.UNIMPLEMENTED); + public static final ScreenElement MNEMONIC_UNIMPL = new ScreenElement("Mnemonic, Unimplemented ", MnemonicColors.UNIMPLEMENTED); public static final ScreenElement FLOW_ARROW_ACTIVE = new ScreenElement("Flow Arrow, Active", FlowArrowColors.ACTIVE); public static final ScreenElement FLOW_ARROW_NON_ACTIVE = new ScreenElement("Flow Arrow, Not Active", FlowArrowColors.INACTIVE); public static final ScreenElement FLOW_ARROW_SELECTED = new ScreenElement("Flow Arrow, Selected", FlowArrowColors.SELECTED); @@ -83,9 +87,7 @@ public class OptionsGui extends JPanel { public static final ScreenElement COMMENT_POST = new ScreenElement("Comment, Post", "Post-Comment", CommentColors.POST); public static final ScreenElement COMMENT_PRE = new ScreenElement("Comment, Pre", "Pre-Comment", CommentColors.PRE); public static final ScreenElement SEPARATOR = new ScreenElement("Separator", ListingColors.SEPARATOR); - public static final ScreenElement VARIABLE = new ScreenElement("Variable", FunctionColors.VARIABLE); - public static final ScreenElement PARAMETER_CUSTOM = new ScreenElement("Parameter, Custom Storage", FunctionColors.PARAM_CUSTOM); - public static final ScreenElement PARAMETER_DYNAMIC = new ScreenElement("Parameter, Dynamic Storage", FunctionColors.PARAM_DYNAMIC); + public static final ScreenElement SEPARATOR_LINE = new ScreenElement("Separator Line", Colors.BACKGROUND); public static final ScreenElement XREF = new ScreenElement("XRef", XrefColors.DEFAULT); public static final ScreenElement XREF_OFFCUT = new ScreenElement("XRef, Offcut", XrefColors.OFFCUT); public static final ScreenElement XREF_READ = new ScreenElement("XRef Read", XrefColors.READ); @@ -98,19 +100,22 @@ public class OptionsGui extends JPanel { public static final ScreenElement PCODE_RAW_VARNODE = new ScreenElement("P-code Raw Varnode", PcodeColors.VARNODE); public static final ScreenElement PCODE_USEROP = new ScreenElement("P-code Userop", PcodeColors.USEROP); + static ScreenElement[] elements = { + ADDRESS, + BACKGROUND, BAD_REF_ADDR, BYTES, + COMMENT_AUTO, COMMENT_EOL, COMMENT_PLATE, COMMENT_POST, COMMENT_PRE, COMMENT_REPEATABLE, + COMMENT_REF_REPEAT, CONSTANT, + ENTRY_POINT, EXT_REF_RESOLVED, EXT_REF_UNRESOLVED, + FIELD_NAME, FLOW_ARROW_ACTIVE, FLOW_ARROW_NON_ACTIVE, FLOW_ARROW_SELECTED, + FUN_NAME, FUN_PARAMS, FUN_AUTO_PARAMS, FUN_PARAM_DYNAMIC, FUN_PARAM_CUSTOM, FUN_VARIABLE, + FUN_RET_TYPE, FUN_CALL_FIXUP, FUN_TAG, + LABELS_LOCAL, LABELS_NON_PRIMARY, LABELS_PRIMARY, LABELS_UNREFD, + MNEMONIC, MNEMONIC_OVERRIDE, MNEMONIC_UNIMPL, + PCODE_LINE_LABEL, PCODE_ADDR_SPACE, PCODE_RAW_VARNODE, PCODE_USEROP, + REGISTERS, SEPARATOR, UNDERLINE, + XREF, XREF_OFFCUT, XREF_READ, XREF_WRITE, XREF_OTHER }; //@formatter:on - static ScreenElement[] elements = - { ADDRESS, BACKGROUND, BAD_REF_ADDR, BYTES, COMMENT_AUTO, COMMENT_EOL, COMMENT_PLATE, - COMMENT_POST, COMMENT_PRE, COMMENT_REPEATABLE, COMMENT_REF_REPEAT, CONSTANT, - ENTRY_POINT, EXT_REF_RESOLVED, EXT_REF_UNRESOLVED, FIELD_NAME, FLOW_ARROW_ACTIVE, - FLOW_ARROW_NON_ACTIVE, FLOW_ARROW_SELECTED, FUN_CALL_FIXUP, FUN_NAME, FUN_PARAMS, - FUN_AUTO_PARAMS, FUN_RET_TYPE, FUN_TAG, LABELS_LOCAL, LABELS_NON_PRIMARY, - LABELS_PRIMARY, LABELS_UNREFD, MNEMONIC, MNEMONIC_OVERRIDE, PARAMETER_CUSTOM, - PARAMETER_DYNAMIC, PCODE_LINE_LABEL, PCODE_ADDR_SPACE, PCODE_RAW_VARNODE, PCODE_USEROP, - REGISTERS, SEPARATOR, UNDERLINE, MNEMONIC_UNIMPL, VARIABLE, XREF, XREF_OFFCUT, - XREF_READ, XREF_WRITE, XREF_OTHER }; - private Map metricsMap = new HashMap<>(); private JList namesList; @@ -167,28 +172,31 @@ public class OptionsGui extends JPanel { } else { setSelectedIndex(index); + setSelectedFieldElement(index); } }); setSelectedIndex(0); colorChooser.getSelectionModel().addChangeListener(e -> { Color c = colorChooser.getColor(); + Color oldColor = elements[selectedIndex].getColor(); elements[selectedIndex].setColor(c); colorPanel.setBackground(c); genLayouts(); - fieldPanel.setBackgroundColor(BACKGROUND.getColor()); - enableApply(); + + if (!ColorUtils.hasSameRgb(oldColor, c)) { + enableApply(); + } }); + ActionListener styleListener = e -> { updateStyle(); genLayouts(); - fieldPanel.setBackgroundColor(BACKGROUND.getColor()); enableApply(); }; ActionListener familyListener = e -> { updateFonts(); genLayouts(); - fieldPanel.setBackgroundColor(BACKGROUND.getColor()); enableApply(); }; @@ -217,7 +225,7 @@ public class OptionsGui extends JPanel { } /** - * callback for when the selected display field changes. + * Callback for when the selected display field changes. * * @param index the index in the JList of the selected field. */ @@ -243,6 +251,26 @@ public class OptionsGui extends JPanel { } } + private void setSelectedFieldElement(int index) { + ListModel model = namesList.getModel(); + ScreenElement screenElement = model.getElementAt(index); + + SimpleLayoutModel layoutModel = (SimpleLayoutModel) fieldPanel.getLayoutModel(); + FieldSelection selection = layoutModel.getFieldSelection(screenElement); + if (selection == null) { + fieldPanel.clearSelection(); + return; + } + + FieldRange range = selection.getFieldRange(0); + FieldLocation loc = range.getStart(); + + fieldPanel.setCursorPosition(loc.getIndex(), loc.getFieldNum(), loc.getRow(), loc.getCol()); + fieldPanel.setSelection(selection); + fieldPanel.scrollTo(loc); + fieldPanel.center(loc); + } + public void setBaseFont(Font font) { baseFont = font; baseMetrics = getFontMetrics(font); @@ -515,6 +543,8 @@ public class OptionsGui extends JPanel { lb.add(".........", SEPARATOR); list.add(lb.getLayout()); + list.add(blankLine()); + lb = new LayoutBuilder(1); lb.add(" ", null); lb.add("sprintf", LABELS_NON_PRIMARY); @@ -557,6 +587,8 @@ public class OptionsGui extends JPanel { lb.add(".........", SEPARATOR); list.add(lb.getLayout()); + list.add(blankLine()); + lb = new LayoutBuilder(2); lb.add(" ", null); lb.add("LAB2000", LABELS_PRIMARY); @@ -610,6 +642,9 @@ public class OptionsGui extends JPanel { lb.add("33", CONSTANT); list.add(lb.getLayout()); + list.add(blankLine()); + list.add(blankLine()); + lb = new LayoutBuilder(1); lb.add(" ", null); lb.add("// This is a function comment", COMMENT_EOL); @@ -639,30 +674,30 @@ public class OptionsGui extends JPanel { lb = new LayoutBuilder(3); lb.add(" ", null); - lb.add("12 ", PARAMETER_DYNAMIC); - lb.add("DWord ", PARAMETER_DYNAMIC); - lb.add("param_12 ", PARAMETER_DYNAMIC); + lb.add("12 ", FUN_PARAM_DYNAMIC); + lb.add("DWord ", FUN_PARAM_DYNAMIC); + lb.add("param_12 ", FUN_PARAM_DYNAMIC); list.add(lb.getLayout()); lb = new LayoutBuilder(3); lb.add(" ", null); - lb.add("8 ", PARAMETER_CUSTOM); - lb.add("DWord ", PARAMETER_CUSTOM); - lb.add("param_8 ", PARAMETER_CUSTOM); + lb.add("8 ", FUN_PARAM_CUSTOM); + lb.add("DWord ", FUN_PARAM_CUSTOM); + lb.add("param_8 ", FUN_PARAM_CUSTOM); list.add(lb.getLayout()); lb = new LayoutBuilder(3); lb.add(" ", null); - lb.add("4 ", PARAMETER_CUSTOM); - lb.add("Word ", PARAMETER_CUSTOM); - lb.add("param_4 ", PARAMETER_CUSTOM); + lb.add("4 ", FUN_PARAM_CUSTOM); + lb.add("Word ", FUN_PARAM_CUSTOM); + lb.add("param_4 ", FUN_PARAM_CUSTOM); list.add(lb.getLayout()); lb = new LayoutBuilder(3); lb.add(" ", null); - lb.add("-4 ", VARIABLE); - lb.add("Float ", VARIABLE); - lb.add("local_4 ", VARIABLE); + lb.add("-4 ", FUN_VARIABLE); + lb.add("Float ", FUN_VARIABLE); + lb.add("local_4 ", FUN_VARIABLE); list.add(lb.getLayout()); lb = new LayoutBuilder(2); @@ -689,8 +724,14 @@ public class OptionsGui extends JPanel { } } + private Layout blankLine() { + LayoutBuilder lb = new LayoutBuilder(1); + lb.add(" ", SEPARATOR_LINE); + return lb.getLayout(); + } + /** - * updates the style of the field at the selected index. + * Updates the style of the field at the selected index. */ private void updateStyle() { if (customCheckbox.isSelected()) { @@ -839,12 +880,75 @@ public class OptionsGui extends JPanel { return BigInteger.valueOf(layouts.length); } + FieldSelection getFieldSelection(ScreenElement element) { + + List ranges = getAllRanges(element); + if (ranges.isEmpty()) { + return null; + } + + FieldSelection selection = new FieldSelection(); + for (FieldRange r : ranges) { + selection.addRange(r); + } + + return selection; + } + + // finds all places this element is used; consecutive fields will be combined + List getAllRanges(ScreenElement element) { + + List ranges = new ArrayList<>(); + for (int index = 0; index < layouts.length; index++) { + SingleRowLayout rowLayout = (SingleRowLayout) layouts[index]; + getRangesForRow(rowLayout, element, index, ranges); + } + return ranges; + } + + private void getRangesForRow(SingleRowLayout layout, ScreenElement element, int index, + List ranges) { + + FieldRange lastRange = null; + int n = layout.getNumFields(); + for (int i = 0; i < n; i++) { + ScreenElementTextField field = (ScreenElementTextField) layout.getField(i); + ScreenElement fieldElement = field.getScreenElement(); + String text = field.getText(); + if (element != fieldElement) { + lastRange = null; + continue; + } + + int start = 0; + if (lastRange == null) { + start = getNonWhitespaceStart(text); + } + + lastRange = new FieldRange( + new FieldLocation(index, i, 0, start), + new FieldLocation(index, i, 0, start + text.trim().length())); + ranges.add(lastRange); + } + } + } + + private int getNonWhitespaceStart(String text) { + int start = 0; + for (int j = 0; j < text.length(); j++) { + char c = text.charAt(j); + if (!Character.isWhitespace(c)) { + break; + } + start++; + } + return start; } /** * Class to create the layouts for the preview panel. */ - class LayoutBuilder { + private class LayoutBuilder { private ClippingTextField[] fields; int startPos; int fieldNum; diff --git a/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java b/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java index 4cfc5bb8dc..f87acc065c 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/program/model/listing/CodeUnitFormat.java @@ -1251,8 +1251,8 @@ public class CodeUnitFormat { } result = addBlockName(program, toAddress, result, refBlock, withBlockName); - LabelType labelType = (toSymbol != null && toSymbol.isExternal()) ? LabelString.EXTERNAL - : LabelString.CODE_LABEL; + LabelType labelType = (toSymbol != null && toSymbol.isExternal()) ? LabelType.EXTERNAL + : LabelType.CODE_LABEL; LabelString label = new LabelString(result, labelType); // Apply extended pointer markup if needed @@ -1291,8 +1291,8 @@ public class CodeUnitFormat { Symbol symbol = program.getSymbolTable().getSymbol(referencesFrom[0]); if (symbol != null && !symbol.isDynamic()) { String result = getSymbolLabelString(program, symbol, ref.getFromAddress()); - return new LabelString(result, - symbol.isExternal() ? LabelString.EXTERNAL : LabelString.CODE_LABEL); + return new LabelString(result, symbol, + symbol.isExternal() ? LabelType.EXTERNAL : LabelType.CODE_LABEL); } return null; } diff --git a/Ghidra/Features/Decompiler/data/decompiler.theme.properties b/Ghidra/Features/Decompiler/data/decompiler.theme.properties index 7d5421c024..a88ebb7d94 100644 --- a/Ghidra/Features/Decompiler/data/decompiler.theme.properties +++ b/Ghidra/Features/Decompiler/data/decompiler.theme.properties @@ -12,6 +12,7 @@ color.fg.decompiler.type = color.palette.blue color.fg.decompiler.parameter = color.palette.purple color.fg.decompiler.global = color.palette.darkcyan color.fg.decompiler.special = color.palette.crimson +color.fg.decompiler.external.function = color.palette.fuchsia color.bg.decompiler.current.variable = color.palette.highlight.transparent.yellow diff --git a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java index 42df81b467..83d5093de7 100644 --- a/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java +++ b/Ghidra/Features/Decompiler/src/main/java/ghidra/app/decompiler/component/ClangLayoutController.java @@ -26,6 +26,7 @@ import docking.widgets.fieldpanel.field.*; import docking.widgets.fieldpanel.listener.IndexMapper; import docking.widgets.fieldpanel.listener.LayoutModelListener; import docking.widgets.fieldpanel.support.*; +import generic.theme.GColor; import ghidra.app.decompiler.*; import ghidra.app.util.SymbolInspector; import ghidra.app.util.viewer.field.CommentUtils; @@ -41,6 +42,9 @@ import ghidra.util.UndefinedFunction; */ public class ClangLayoutController implements LayoutModel, LayoutModelListener { + private static GColor COLOR_EXTERNAL_FUNCTION = + new GColor("color.fg.decompiler.external.function"); + private int maxWidth; private int indentWidth; private SymbolInspector symbolInspector; @@ -202,22 +206,44 @@ public class ClangLayoutController implements LayoutModel, LayoutModelListener { private Color getTokenColor(ClangToken token) { - Color tokenColor = syntaxColor[token.getSyntaxType()]; if (token instanceof ClangFuncNameToken clangFunctionToken) { Program program = decompilerPanel.getProgram(); Function function = DecompilerUtils.getFunction(program, clangFunctionToken); if (isValidFunction(function)) { - Symbol symbol = function.getSymbol(); - tokenColor = symbolInspector.getColor(symbol); + return getFunctionColor(function); } } + Color tokenColor = syntaxColor[token.getSyntaxType()]; if (tokenColor != null) { return tokenColor; } return syntaxColor[ClangToken.ERROR_COLOR]; } + private Color getFunctionColor(Function function) { + Symbol symbol = function.getSymbol(); + + // For now we have decided that any external function, linked or not, will be one color, as + // this makes it easy for the user to identify external function calls. Other functions will + // be colored according to the SymbolInspector. If we use the SymbolInspector for all + // colors, then some of the color values will be very close to some of the colors used by + // the Decompiler. For example, non-linked external functions default to red and linked + // external functions default to green. + if (function.isExternal()) { + return COLOR_EXTERNAL_FUNCTION; + } + + if (function.isThunk()) { + Function thunkedFunction = function.getThunkedFunction(true); + if (thunkedFunction.isExternal()) { + return COLOR_EXTERNAL_FUNCTION; + } + } + + return symbolInspector.getColor(symbol); + } + private boolean isValidFunction(Function f) { return f != null && !(f instanceof UndefinedFunction); } diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldLocation.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldLocation.java index 547f0ea651..00fd0a1b75 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldLocation.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/fieldpanel/support/FieldLocation.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. @@ -86,7 +86,7 @@ public class FieldLocation implements Comparable { * @param index the index of the layout containing the location * @param fieldNum the index of the field in the layout containing the location * @param row the text row in the field containing the location. - * @param col the character position the row containing the location. + * @param col the character position in the row containing the location. */ public FieldLocation(int index, int fieldNum, int row, int col) { this(BigInteger.valueOf(index), fieldNum, row, col); diff --git a/Ghidra/Framework/Gui/src/main/java/ghidra/util/ColorUtils.java b/Ghidra/Framework/Gui/src/main/java/ghidra/util/ColorUtils.java index 0e9fb551a7..2d9ddbfbea 100644 --- a/Ghidra/Framework/Gui/src/main/java/ghidra/util/ColorUtils.java +++ b/Ghidra/Framework/Gui/src/main/java/ghidra/util/ColorUtils.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. @@ -18,6 +18,8 @@ package ghidra.util; import java.awt.Color; import java.util.Comparator; +import generic.theme.GColor; + public class ColorUtils { private static final int MAX_COLOR_VALUE = 255; @@ -348,6 +350,26 @@ public class ColorUtils { return new Color(red, green, blue, alpha); } + /** + * Returns true if both colors are not null and have the same RGB value. This is useful to + * compare colors that may have different classes, such as {@link Color} and {@link GColor}. + * + * @param c1 the first color + * @param c2 the second color + * @return true if the colors have the same RGB value + */ + public static boolean hasSameRgb(Color c1, Color c2) { + int rgb1 = 0; + int rgb2 = 0; + if (c1 != null) { + rgb1 = c1.getRGB(); + } + if (c2 != null) { + rgb2 = c2.getRGB(); + } + return rgb1 == rgb2; + } + /** * Blender of colors */ diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/LabelString.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/LabelString.java index 1250d06f27..6292c32ce5 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/LabelString.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/LabelString.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. @@ -15,27 +15,38 @@ */ package ghidra.program.model.listing; -public class LabelString { - - public enum LabelType { CODE_LABEL, VARIABLE, EXTERNAL } +import ghidra.program.model.symbol.Symbol; + +public class LabelString { + + public enum LabelType { + CODE_LABEL, VARIABLE, EXTERNAL + } - public static final LabelType CODE_LABEL = LabelType.CODE_LABEL; - public static final LabelType VARIABLE = LabelType.VARIABLE; - public static final LabelType EXTERNAL = LabelType.EXTERNAL; - private final String label; private final LabelType type; + private Symbol symbol; public LabelString(String label, LabelType type) { this.label = label; this.type = type; } + public LabelString(String label, Symbol symbol, LabelType type) { + this.label = label; + this.symbol = symbol; + this.type = type; + } + + public Symbol getSymbol() { + return symbol; + } + @Override public String toString() { return label; } - + public LabelType getLabelType() { return type; } diff --git a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/VariableOffset.java b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/VariableOffset.java index 83c0865d18..04cd0cdc33 100644 --- a/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/VariableOffset.java +++ b/Ghidra/Framework/SoftwareModeling/src/main/java/ghidra/program/model/listing/VariableOffset.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. @@ -15,12 +15,12 @@ */ package ghidra.program.model.listing; -import java.util.ArrayList; -import java.util.List; +import java.util.*; import ghidra.program.model.address.Address; import ghidra.program.model.data.*; import ghidra.program.model.lang.Register; +import ghidra.program.model.listing.LabelString.LabelType; import ghidra.program.model.scalar.Scalar; import ghidra.program.model.symbol.*; import ghidra.util.SystemUtilities; @@ -43,7 +43,7 @@ public class VariableOffset { /** * Constructor for an implied variable reference. - * @param variable function variable + * @param var function variable * @param offset offset into variable * @param indirect if true and variable data-type is a pointer, the offset * is relative to underlying data-type of the pointer-type. This should generally be @@ -51,8 +51,8 @@ public class VariableOffset { * whereas it would be false for stack-references. * @param dataAccess true if content of variable is being read and/or written */ - public VariableOffset(Variable variable, long offset, boolean indirect, boolean dataAccess) { - this.variable = variable; + public VariableOffset(Variable var, long offset, boolean indirect, boolean dataAccess) { + this.variable = Objects.requireNonNull(var, "Variable reference not bound to a variable"); this.offset = offset; this.indirect = indirect; this.dataAccess = dataAccess; @@ -65,20 +65,14 @@ public class VariableOffset { */ public VariableOffset(Reference ref, Variable var) { - indirect = false; - variable = var; - if (variable == null) { - throw new IllegalArgumentException("Variable reference not bound to a variable"); - } - + this.indirect = false; + this.variable = Objects.requireNonNull(var, "Variable reference not bound to a variable"); RefType rt = ref.getReferenceType(); - dataAccess = rt.isRead() || rt.isWrite(); + this.dataAccess = rt.isRead() || rt.isWrite(); if (ref instanceof StackReference && variable.isStackVariable()) { - offset = variable.getStackOffset(); - offset = ((StackReference) ref).getStackOffset() - variable.getStackOffset(); + this.offset = ((StackReference) ref).getStackOffset() - variable.getStackOffset(); } - } /** @@ -94,6 +88,7 @@ public class VariableOffset { /** * Sets the original replaced sub-operand Register. + * @param reg the register */ public void setReplacedElement(Register reg) { replacedElement = reg; @@ -107,9 +102,6 @@ public class VariableOffset { return replacedElement; } - /** - * @see java.lang.Object#toString() - */ @Override public String toString() { StringBuffer buf = new StringBuffer(); @@ -204,7 +196,7 @@ public class VariableOffset { } List list = new ArrayList<>(); - list.add(new LabelString(name.toString(), LabelString.VARIABLE)); + list.add(new LabelString(name.toString(), LabelType.VARIABLE)); if (absOffset != 0 || scalarAdjustment != 0) { long adjustedOffset = (offset < 0 ? -absOffset : absOffset) + scalarAdjustment;