diff --git a/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm b/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm index 284082788f..f5425d3100 100644 --- a/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm +++ b/Ghidra/Features/Base/src/main/help/help/topics/AutoAnalysisPlugin/AutoAnalysis.htm @@ -422,7 +422,7 @@ - Only Demangle Known Mangled Symbols + Demangle Only Known Mangled Symbols Only demangle symbols that follow known compiler mangling patterns. @@ -509,7 +509,7 @@

- The Microsoft Demangler adds the following analysis option: + The Microsoft Demangler adds the following analysis options:

@@ -536,6 +536,33 @@

+
+

+ Use Encoded Anonymous Namespace - + This output option is used to create a more unique namespace node from the + standard output for anonymous namespaces, thus helping to prevent collisions + between multiple types of the same name, but each in their own unique anonymous + namespace. When turned on, variations of the standard namespace + `anonymous namespace' will be replaced with a more specific namespace of + the form _anon_ABCD1234 where the hexadecimal number comes from a number + encoded in the mangled string. The trade-off is that when this option is turned on, + the resultant namespace will not match non-mangled names that have the standard, + non-specific anonymous namespace. +

+
+ +
+

+ Apply Argument UDT Tags - + This output option controls whether user-defined type tags (e.g., class, enum, + struct, union) are placed in front of the named type when the type is used as a + template or function argument. When turned on, the demangled string matches the + common demangler standard. When turned off, the standard demangled strings of + struct AAA<class BBB> and struct AAA<struct BBB> would + both be reduced to struct AAA<BBB>. +

+
+ diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerOptions.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerOptions.java index 101153f5b7..4c696baad2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerOptions.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerOptions.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,15 +15,17 @@ */ package ghidra.app.util.demangler; +import generic.json.Json; + /** * A simple class to contain the various settings for demangling */ public class DemanglerOptions { - private boolean applyCallingConvention = true; - private boolean applySignature = true; - private boolean doDisassembly = true; - private boolean demangleOnlyKnownPatterns = true; + protected boolean applyCallingConvention = true; + protected boolean applySignature = true; + protected boolean doDisassembly = true; + protected boolean demangleOnlyKnownPatterns = true; public DemanglerOptions() { // use default values @@ -118,12 +120,6 @@ public class DemanglerOptions { @Override public String toString() { - //@formatter:off - return "{\n" + - "\tdoDisassembly: " + doDisassembly + ",\n" + - "\tapplySignature: " + applySignature + ",\n" + - "\tdemangleOnlyKnownPatterns: " + demangleOnlyKnownPatterns + ",\n" + - "}"; - //@formatter:on + return Json.toString(this); } } diff --git a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/plugin/core/analysis/MicrosoftDemanglerAnalyzer.java b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/plugin/core/analysis/MicrosoftDemanglerAnalyzer.java index ef10a5c9c5..038fbcbee8 100644 --- a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/plugin/core/analysis/MicrosoftDemanglerAnalyzer.java +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/plugin/core/analysis/MicrosoftDemanglerAnalyzer.java @@ -17,7 +17,9 @@ package ghidra.app.plugin.core.analysis; import ghidra.app.util.demangler.*; import ghidra.app.util.demangler.microsoft.*; +import ghidra.app.util.demangler.microsoft.options.*; import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.OptionType; import ghidra.framework.options.Options; import ghidra.program.model.listing.Program; import ghidra.util.HelpLocation; @@ -58,6 +60,12 @@ public class MicrosoftDemanglerAnalyzer extends AbstractDemanglerAnalyzer { private boolean demangleOnlyKnownPatterns = false; private MsCInterpretation interpretation = MsCInterpretation.FUNCTION_IF_EXISTS; + private static final String APPLY_OPTIONS_LABEL = "msdApplyOptions"; + private static final String OUTPUT_OPTIONS_LABEL = "msdOutputOptions"; + + private MsdApplyOption applyOption; + private MsdOutputOption outputOption; + public MicrosoftDemanglerAnalyzer() { super(NAME, DESCRIPTION); demangler = new MicrosoftDemangler(); @@ -71,43 +79,36 @@ public class MicrosoftDemanglerAnalyzer extends AbstractDemanglerAnalyzer { @Override public void registerOptions(Options options, Program program) { - HelpLocation help = new HelpLocation("AutoAnalysisPlugin", "Demangler_Analyzer"); - options.registerOption(OPTION_NAME_APPLY_SIGNATURE, applyFunctionSignature, help, - OPTION_DESCRIPTION_APPLY_SIGNATURE); + options.registerOption(APPLY_OPTIONS_LABEL, OptionType.CUSTOM_TYPE, + new MsdApplyOption(), help, "Configures how demangling is applied", + () -> new MsdApplyOptionsEditor()); + applyOption = + (MsdApplyOption) options.getCustomOption(APPLY_OPTIONS_LABEL, null); - options.registerOption(OPTION_NAME_APPLY_CALLING_CONVENTION, applyCallingConvention, help, - OPTION_DESCRIPTION_APPLY_CALLING_CONVENTION); - - options.registerOption(OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS, demangleOnlyKnownPatterns, - help, OPTION_DESCRIPTION_USE_KNOWN_PATTERNS); - - options.registerOption(OPTION_NAME_MS_C_INTERPRETATION, interpretation, help, - OPTION_DESCRIPTION_MS_C_INTERPRETATION); + options.registerOption(OUTPUT_OPTIONS_LABEL, OptionType.CUSTOM_TYPE, + new MsdOutputOption(), help, "Controls demangled output", + () -> new MsdOutputOptionsEditor()); + outputOption = (MsdOutputOption) options.getCustomOption(OUTPUT_OPTIONS_LABEL, null); } @Override public void optionsChanged(Options options, Program program) { - applyFunctionSignature = - options.getBoolean(OPTION_NAME_APPLY_SIGNATURE, applyFunctionSignature); - - applyCallingConvention = - options.getBoolean(OPTION_NAME_APPLY_CALLING_CONVENTION, applyCallingConvention); - - demangleOnlyKnownPatterns = - options.getBoolean(OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS, demangleOnlyKnownPatterns); - - interpretation = options.getEnum(OPTION_NAME_MS_C_INTERPRETATION, interpretation); + applyOption = (MsdApplyOption) options.getCustomOption(APPLY_OPTIONS_LABEL, applyOption); + outputOption = + (MsdOutputOption) options.getCustomOption(OUTPUT_OPTIONS_LABEL, outputOption); } @Override protected DemanglerOptions getOptions() { MicrosoftDemanglerOptions options = new MicrosoftDemanglerOptions(); - options.setApplySignature(applyFunctionSignature); - options.setApplyCallingConvention(applyCallingConvention); - options.setDemangleOnlyKnownPatterns(demangleOnlyKnownPatterns); - options.setInterpretation(interpretation); + options.setApplySignature(applyOption.applySignature()); + options.setApplyCallingConvention(applyOption.applyCallingConvention()); + options.setDemangleOnlyKnownPatterns(applyOption.demangleOnlyKnownPatterns()); + options.setInterpretation(applyOption.getInterpretation()); + options.setUseEncodedAnonymousNamespace(outputOption.getUseEncodedAnonymousNamespace()); + options.setApplyUdtArgumentTypeTag(outputOption.getApplyUdtArgumentTypeTag()); options.setErrorOnRemainingChars(true); return options; } diff --git a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftDemangler.java b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftDemangler.java index 5e1daa680e..fe412af3a1 100644 --- a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftDemangler.java +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftDemangler.java @@ -64,13 +64,24 @@ public class MicrosoftDemangler implements Demangler { demangler.setDemangleOnlyKnownPatterns(options.demangleOnlyKnownPatterns()); demangler.setArchitectureSize(mContext.getArchitectureSize()); demangler.setIsFunction(mContext.shouldInterpretAsFunction()); + try { item = demangler.demangle(); if (item == null) { return null; } + // The item.toString() method is influenced by the demangler output options, so we get + // the originalDemangled string before we change the output options to what we desire + // for Ghidra processing. String originalDemangled = item.toString(); - demangler.getOutputOptions().setUseEncodedAnonymousNamespace(true); + // Now we set the particular output options that we didt't want to affect what was + // in the originalDemangled string above. Once set, then when + // MicrosoftDemanglerUtil.convertToDemangledObject() method is called, the newly + // set output options affect the object result. + demangler.getOutputOptions() + .setUseEncodedAnonymousNamespace(options.getUseEncodedAnonymousNamespace()); + demangler.getOutputOptions() + .setApplyUdtArgumentTypeTag(options.getApplyUdtArgumentTypeTag()); object = MicrosoftDemanglerUtil.convertToDemangledObject(item, mangled, originalDemangled); if (object != null) { @@ -112,8 +123,18 @@ public class MicrosoftDemangler implements Demangler { if (mdType == null) { return null; } + // The item.toString() method is influenced by the demangler output options, so we get + // the originalDemangled string before we change the output options to what we desire + // for Ghidra processing. String originalDemangled = mdType.toString(); - demangler.getOutputOptions().setUseEncodedAnonymousNamespace(true); + // Now we set the particular output options that we didt't want to affect what was + // in the originalDemangled string above. Once set, then when + // MicrosoftDemanglerUtil.convertToDemangledObject() method is called, the newly + // set output options affect the object result. + demangler.getOutputOptions() + .setUseEncodedAnonymousNamespace(options.getUseEncodedAnonymousNamespace()); + demangler.getOutputOptions() + .setApplyUdtArgumentTypeTag(options.getApplyUdtArgumentTypeTag()); dataType = MicrosoftDemanglerUtil.convertToDemangledDataType(mdType, mangled, originalDemangled); if (dataType != null) { diff --git a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftDemanglerOptions.java b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftDemanglerOptions.java index ecf8360f8c..a2272a4a39 100644 --- a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftDemanglerOptions.java +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftDemanglerOptions.java @@ -15,6 +15,7 @@ */ package ghidra.app.util.demangler.microsoft; +import generic.json.Json; import ghidra.app.util.demangler.DemanglerOptions; /** @@ -22,24 +23,29 @@ import ghidra.app.util.demangler.DemanglerOptions; */ public class MicrosoftDemanglerOptions extends DemanglerOptions { + // Processing options private boolean errorOnRemainingChars; private MsCInterpretation interpretation; + // Output options: + private boolean useEncodedAnonymousNamespace; + private boolean applyUdtArgumentTypeTag; // specific to MS for now + + /** + * Constructor for MicrosoftDemanglerOptions + * @param errorOnRemainingCharsArg {@code true} to error on remaining characters + */ + public MicrosoftDemanglerOptions(boolean errorOnRemainingCharsArg) { + this(); + errorOnRemainingChars = errorOnRemainingCharsArg; // override defaultInits() + } + /** * Default constructor for MicrosoftDemanglerOptions */ public MicrosoftDemanglerOptions() { - this(true); - interpretation = MsCInterpretation.FUNCTION_IF_EXISTS; - } - - /** - * Constructor for MicrosoftDemanglerOptions - * @param errorOnRemainingChars {@code true} to error on remaining characters - */ - public MicrosoftDemanglerOptions(boolean errorOnRemainingChars) { super(); - this.errorOnRemainingChars = errorOnRemainingChars; + defaultInits(); } /** @@ -48,17 +54,24 @@ public class MicrosoftDemanglerOptions extends DemanglerOptions { */ public MicrosoftDemanglerOptions(DemanglerOptions copy) { super(copy); - if (copy instanceof MicrosoftDemanglerOptions mCopy) { errorOnRemainingChars = mCopy.errorOnRemainingChars; interpretation = mCopy.interpretation; + useEncodedAnonymousNamespace = mCopy.useEncodedAnonymousNamespace; + applyUdtArgumentTypeTag = mCopy.applyUdtArgumentTypeTag; } else { - errorOnRemainingChars = true; - interpretation = MsCInterpretation.FUNCTION_IF_EXISTS; + defaultInits(); } } + private void defaultInits() { + errorOnRemainingChars = true; + interpretation = MsCInterpretation.FUNCTION_IF_EXISTS; + useEncodedAnonymousNamespace = true; + applyUdtArgumentTypeTag = true; + } + /** * Sets the control for erroring on remaining characters at demangler completion * @param errorOnRemainingCharsArg {@code true} to error when remaining characters exist @@ -94,16 +107,46 @@ public class MicrosoftDemanglerOptions extends DemanglerOptions { return interpretation; } + /** + * Sets the output flag to use an anonymous namespace's encoded number to craft a namespace + * containing this number instead of using the generic "`anonymous namespace'" name. Default + * is true (to create a namespace containing the encoded number) + * @param useEncodedAnonymousNamespaceArg {@code true} to use + */ + public void setUseEncodedAnonymousNamespace(boolean useEncodedAnonymousNamespaceArg) { + useEncodedAnonymousNamespace = useEncodedAnonymousNamespaceArg; + } + + /** + * Returns {@code true} if the output flag is set to use an anonymous namespace's encoded + * number to craft a namespace containing the number instead of using the generic + * "`anonymous namespace'" name. + * @return {@code true} if encoded number is used to craft a namespace + */ + public boolean getUseEncodedAnonymousNamespace() { + return useEncodedAnonymousNamespace; + } + + /** + * Sets the output flag for applying user-defined tags (e.g., class, struct, union, enum) + * within template and function arguments. Default is {@code true} (to apply) + * @param applyUdtArgumentTypeTagArg {@code true} to apply the tags + */ + public void setApplyUdtArgumentTypeTag(boolean applyUdtArgumentTypeTagArg) { + applyUdtArgumentTypeTag = applyUdtArgumentTypeTagArg; + } + + /** + * Returns {@code true} if the output interpretation is set to apply user-defined type + * tags (e.g., class, struct, union, enum) within template and function arguments. + * @return {@code true} if applying the tags + */ + public boolean getApplyUdtArgumentTypeTag() { + return applyUdtArgumentTypeTag; + } + @Override public String toString() { - //@formatter:off - return "{\n" + - "\tdoDisassembly: " + doDisassembly() + ",\n" + - "\tapplySignature: " + applySignature() + ",\n" + - "\terrorOnRemainingChars: " + errorOnRemainingChars + ",\n" + - "\tinterpretation: " + interpretation + ",\n" + - "\tdemangleOnlyKnownPatterns: " + demangleOnlyKnownPatterns() + ",\n" + - "}"; - //@formatter:on + return Json.toString(this); } } diff --git a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftMangledContext.java b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftMangledContext.java index e260ef8a37..44b50ae88b 100644 --- a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftMangledContext.java +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/MicrosoftMangledContext.java @@ -37,6 +37,15 @@ public class MicrosoftMangledContext extends MangledContext { super(program, options, mangled, address); } + /** + * Returns the demangler options + * @return the options + */ + @Override + public MicrosoftDemanglerOptions getOptions() { + return (MicrosoftDemanglerOptions) options; + } + /** * Returns the program architecture size * @return the architecture size or zero if not known (program is null) diff --git a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdApplyOption.java b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdApplyOption.java new file mode 100644 index 0000000000..cb4692a138 --- /dev/null +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdApplyOption.java @@ -0,0 +1,118 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.demangler.microsoft.options; + +import java.util.Objects; + +import ghidra.app.plugin.core.analysis.MicrosoftDemanglerAnalyzer; +import ghidra.app.util.demangler.DemanglerOptions; +import ghidra.app.util.demangler.microsoft.MicrosoftDemanglerOptions; +import ghidra.app.util.demangler.microsoft.MsCInterpretation; +import ghidra.framework.options.CustomOption; +import ghidra.framework.options.GProperties; + +/** + * Option class that is paired with the {@link MsdApplyOptionsEditor} so that we can have a + * custom editor for an "Apply" option panel for the {@link MicrosoftDemanglerAnalyzer}. + * Also see {@link MsdOutputOption}, which is another panel. + * The results of both get pushed into the {@link MicrosoftDemanglerOptions} to control the + * analyzer and underlying demangler. + */ +public class MsdApplyOption extends DemanglerOptions implements CustomOption { + + private static final String DEMANGLE_USE_KNOWN_PATTERNS = "demangleOnlyKnownMangledSymbols"; + private static final String APPLY_SIGNATURE = "applyFunctionSignatures"; + private static final String APPLY_CALLING_CONVENTION = "applyFunctionCallingConventions"; + private static final String MS_C_INTERPRETATION = "C-StyleSymbolInterpretation"; + + private static boolean DEFAULT_DEMANGLE_USE_KNOWN_PATTERNS = false; + private static boolean DEFAULT_APPLY_SIGNATURE = true; + private static boolean DEFAULT_APPLY_CALLING_CONVENTION = true; + private static MsCInterpretation DEFAULT_MS_C_INTERPRETATION = + MsCInterpretation.FUNCTION_IF_EXISTS; + + private MsCInterpretation interpretation; + + public MsdApplyOption() { + // required for persistence, but also using for overriding initializations of parent + setDemangleOnlyKnownPatterns(DEFAULT_DEMANGLE_USE_KNOWN_PATTERNS); + setApplySignature(DEFAULT_APPLY_SIGNATURE); + setApplyCallingConvention(DEFAULT_APPLY_CALLING_CONVENTION); + interpretation = DEFAULT_MS_C_INTERPRETATION; + } + + /** + * Sets the interpretation for processing a C-style mangled symbol if there could be multiple + * interpretations + * @param interpretationArg the interpretation to use + */ + public void setInterpretation(MsCInterpretation interpretationArg) { + interpretation = interpretationArg; + } + + /** + * Returns the interpretation for processing a C-style mangled symbol if there could be multiple + * interpretations + * @return the interpretation used + */ + public MsCInterpretation getInterpretation() { + return interpretation; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof MsdApplyOption other)) { + return false; + } + + if (this == obj) { + return true; + } + + return demangleOnlyKnownPatterns == other.demangleOnlyKnownPatterns && + applyCallingConvention == other.applyCallingConvention && + applySignature == other.applySignature && + interpretation == other.interpretation; + } + + @Override + public int hashCode() { + return Objects.hash(demangleOnlyKnownPatterns, applyCallingConvention, + applySignature, interpretation); + } + +//================================================================================================== +// Persistence +//================================================================================================== + + @Override + public void readState(GProperties properties) { + demangleOnlyKnownPatterns = + properties.getBoolean(DEMANGLE_USE_KNOWN_PATTERNS, demangleOnlyKnownPatterns); + applySignature = properties.getBoolean(APPLY_SIGNATURE, applySignature); + applyCallingConvention = + properties.getBoolean(APPLY_CALLING_CONVENTION, applyCallingConvention); + interpretation = properties.getEnum(MS_C_INTERPRETATION, interpretation); + } + + @Override + public void writeState(GProperties properties) { + properties.putBoolean(DEMANGLE_USE_KNOWN_PATTERNS, demangleOnlyKnownPatterns); + properties.putBoolean(APPLY_SIGNATURE, applySignature); + properties.putBoolean(APPLY_CALLING_CONVENTION, applyCallingConvention); + properties.putEnum(MS_C_INTERPRETATION, interpretation); + } +} diff --git a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdApplyOptionsEditor.java b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdApplyOptionsEditor.java new file mode 100644 index 0000000000..b5544b61d5 --- /dev/null +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdApplyOptionsEditor.java @@ -0,0 +1,282 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.demangler.microsoft.options; + +import java.awt.*; +import java.beans.PropertyEditorSupport; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.TitledBorder; + +import docking.options.editor.OptionsEditorAlignable; +import docking.widgets.checkbox.GCheckBox; +import docking.widgets.combobox.GComboBox; +import ghidra.app.util.demangler.microsoft.MsCInterpretation; +import ghidra.framework.options.CustomOptionsEditor; +import ghidra.util.HTMLUtilities; +import ghidra.util.layout.PairLayout; + +/** + * Editor used presenting and receiving GUI changes to {@link MsdApplyOption} + */ +public class MsdApplyOptionsEditor extends PropertyEditorSupport + implements CustomOptionsEditor { + + private static final String USE_KNOWN_PATTERNS_LABEL = "Demangle Only Known Mangled Symbols"; + public static final String APPLY_SIGNATURE_LABEL = "Apply Function Signatures"; + public static final String APPLY_CALLING_CONVENTION_LABEL = + "Apply Function Calling Conventions"; + + public static final String MS_C_INTERPRETATION_LABEL = "C-Style Symbol Interpretation"; + + private static final String[] NAMES = { USE_KNOWN_PATTERNS_LABEL, APPLY_SIGNATURE_LABEL, + APPLY_CALLING_CONVENTION_LABEL, MS_C_INTERPRETATION_LABEL }; + + // help tooltips + private static final String USE_KNOWN_PATTERNS_TOOLTIP = HTMLUtilities.toWrappedHTML( + "Only demangle symbols that follow known compiler mangling patterns. " + + "Leaving this option off may cause non-mangled symbols to get demangled.", + 75); + private static final String APPLY_SIGNATURE_TOOLTIP = HTMLUtilities.toWrappedHTML( + "Apply any recovered function signature, in addition to the function name", + 75); + private static final String APPLY_CALLING_CONVENTION_TOOLTIP = HTMLUtilities.toWrappedHTML( + "Apply any recovered function signature calling convention", + 75); + private static final String MS_C_INTERPRETATION_TOOLTIP = HTMLUtilities.toWrappedHTML( + "When ambiguous, treat C-Style mangled symbol as: function, variable," + + " or function if a function exists", + 75); + + private static final String[] DESCRIPTIONS = { USE_KNOWN_PATTERNS_TOOLTIP, + APPLY_SIGNATURE_TOOLTIP, APPLY_CALLING_CONVENTION_TOOLTIP, MS_C_INTERPRETATION_TOOLTIP }; + + private MsdApplyOption applyOption; + + private Component editorComponent; + + private JLabel interpretationLabel; + private JLabel callingConventionLabel; + private JLabel signatureLabel; + private JLabel knownPatternsLabel; + + private JCheckBox knownPatternsCb; + private JCheckBox signatureCb; + private JCheckBox callingConventionCb; + private JComboBox interpretationComboBox; + + public MsdApplyOptionsEditor() { + editorComponent = buildEditor(); + } + + private Component buildEditor() { + // we want to have a panel with our options so that we may group them together + JPanel panel = new JPanel(new PairLayout(0, 6)); + + knownPatternsCb = new GCheckBox(); + knownPatternsCb.setSelected(false); + knownPatternsCb.setToolTipText(USE_KNOWN_PATTERNS_TOOLTIP); + knownPatternsLabel = new JLabel(USE_KNOWN_PATTERNS_LABEL, SwingConstants.RIGHT); + knownPatternsLabel.setLabelFor(knownPatternsCb); + knownPatternsLabel.setToolTipText(USE_KNOWN_PATTERNS_TOOLTIP); + panel.add(knownPatternsLabel); + panel.add(knownPatternsCb); + + signatureCb = new GCheckBox(); + signatureCb.setSelected(false); + signatureCb.setToolTipText(APPLY_SIGNATURE_TOOLTIP); + signatureLabel = new JLabel(APPLY_SIGNATURE_LABEL, SwingConstants.RIGHT); + signatureLabel.setLabelFor(signatureCb); + signatureLabel.setToolTipText(APPLY_SIGNATURE_TOOLTIP); + panel.add(signatureLabel); + panel.add(signatureCb); + + callingConventionCb = new GCheckBox(); + callingConventionCb.setSelected(false); + callingConventionCb.setToolTipText(APPLY_CALLING_CONVENTION_TOOLTIP); + callingConventionLabel = new JLabel(APPLY_CALLING_CONVENTION_LABEL, SwingConstants.RIGHT); + callingConventionLabel.setLabelFor(callingConventionCb); + callingConventionLabel.setToolTipText(APPLY_CALLING_CONVENTION_TOOLTIP); + panel.add(callingConventionLabel); + panel.add(callingConventionCb); + + interpretationComboBox = new GComboBox<>(MsCInterpretation.values()); + interpretationComboBox.setSelectedItem(false); + interpretationComboBox.setToolTipText(MS_C_INTERPRETATION_TOOLTIP); + interpretationLabel = new JLabel(MS_C_INTERPRETATION_LABEL, SwingConstants.RIGHT); + interpretationLabel.setLabelFor(interpretationComboBox); + interpretationLabel.setToolTipText(MS_C_INTERPRETATION_TOOLTIP); + panel.add(interpretationLabel); + panel.add(interpretationComboBox); + + knownPatternsCb.addItemListener(e -> firePropertyChange()); + signatureCb.addItemListener(e -> { + signatureChangeListener(); + firePropertyChange(); + }); + callingConventionCb.addItemListener(e -> firePropertyChange()); + interpretationComboBox.addItemListener(e -> firePropertyChange()); + + Border emptyBorder = BorderFactory.createEmptyBorder(0, 0, 0, 0); + TitledBorder titledNoLineBorder = BorderFactory.createTitledBorder( + emptyBorder, // The invisible base border + "Apply Options", // The title text + TitledBorder.LEADING, // Title justification (e.g., LEADING, CENTER, TRAILING) + TitledBorder.TOP, // Title position (e.g., TOP, BOTTOM) + null, // Optional: Font + null // Optional: Title color + ); + + // Use an outer panel so we can offset the main panel + JPanel outerPanel = new AlignablePanel(new BorderLayout()); + + panel.setBorder(BorderFactory.createEmptyBorder(5, 20, 0, 0)); + + outerPanel.add(panel, BorderLayout.CENTER); + outerPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(10, 0, 10, 0), titledNoLineBorder)); + + return outerPanel; + } + + @Override + public void setValue(Object value) { + if (!(value instanceof MsdApplyOption option)) { + return; + } + applyOption = option; + setLocalValues(applyOption); + firePropertyChange(); + } + + private void setLocalValues(MsdApplyOption applyOptions) { + boolean knownPatterns = applyOptions.demangleOnlyKnownPatterns(); + if (knownPatterns != knownPatternsCb.isSelected()) { + knownPatternsCb.setSelected(knownPatterns); + } + + boolean applySignature = applyOptions.applySignature(); + if (applySignature != signatureCb.isSelected()) { + signatureCb.setSelected(applySignature); + } + + boolean applyCc = applyOptions.applyCallingConvention(); + if (applyCc != callingConventionCb.isSelected()) { + callingConventionCb.setSelected(applyCc); + } + + MsCInterpretation interpretation = applyOptions.getInterpretation(); + if (interpretation != interpretationComboBox.getSelectedItem()) { + interpretationComboBox.setSelectedItem(interpretation); + } + + signatureChangeListener(); + } + + private void signatureChangeListener() { + // Calling convention enabled only if signature is selected + boolean signatureEnabled = signatureCb.isSelected(); + callingConventionCb.setEnabled(signatureEnabled); + } + + private MsdApplyOption cloneNamespaceValues() { + MsdApplyOption newOptions = new MsdApplyOption(); + newOptions.setApplySignature(signatureCb.isSelected()); + newOptions.setApplyCallingConvention(callingConventionCb.isSelected()); + newOptions.setDemangleOnlyKnownPatterns(knownPatternsCb.isSelected()); + newOptions.setInterpretation((MsCInterpretation) interpretationComboBox.getSelectedItem()); + return newOptions; + } + + @Override + public String[] getOptionDescriptions() { + return DESCRIPTIONS; + } + + @Override + public String[] getOptionNames() { + return NAMES; + } + + @Override + public Object getValue() { + return cloneNamespaceValues(); + } + + @Override + public Component getCustomEditor() { + return editorComponent; + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + // Allows us to mimic the alignment of the normal options for this custom editor and any other + // custom editor in the same view as us + private class AlignablePanel extends JPanel implements OptionsEditorAlignable { + + AlignablePanel(LayoutManager layout) { + super(layout); + } + + @Override + public Dimension getPreferredAlignmentSize() { + // + // Use all labels and components to find the overall preferred size. + // + int maxWidth = 0; + int maxHeight = 0; + Dimension size = getPairDimension(knownPatternsLabel, knownPatternsCb); + maxWidth = Math.max(size.width, maxWidth); + maxHeight = Math.max(size.height, maxHeight); + + size = getPairDimension(signatureLabel, signatureCb); + maxWidth = Math.max(size.width, maxWidth); + maxHeight = Math.max(size.height, maxHeight); + + size = getPairDimension(callingConventionLabel, callingConventionCb); + maxWidth = Math.max(size.width, maxWidth); + maxHeight = Math.max(size.height, maxHeight); + + size = getPairDimension(interpretationLabel, interpretationComboBox); + maxWidth = Math.max(size.width, maxWidth); + maxHeight = Math.max(size.height, maxHeight); + + return new Dimension(maxWidth, maxHeight); + } + + private Dimension getPairDimension(JLabel label, JComponent c) { + // Note: this code is taken from DefaultOptionComponent + Dimension dimension = label.getPreferredSize(); + int labelWidth = dimension.width; + int labelHeight = dimension.height; + int maxHeight = Math.max(labelHeight, c.getPreferredSize().height); + return new Dimension(labelWidth, maxHeight); + } + + @Override + public void setPreferredAlignmentSize(Dimension size) { + // This is called after all preferred sizes have been retrieved and combined + knownPatternsLabel.setPreferredSize(size); + signatureLabel.setPreferredSize(size); + callingConventionLabel.setPreferredSize(size); + interpretationLabel.setPreferredSize(size); + } + } +} diff --git a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdOutputOption.java b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdOutputOption.java new file mode 100644 index 0000000000..6ec479a605 --- /dev/null +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdOutputOption.java @@ -0,0 +1,120 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.demangler.microsoft.options; + +import java.util.Objects; + +import ghidra.app.plugin.core.analysis.MicrosoftDemanglerAnalyzer; +import ghidra.framework.options.CustomOption; +import ghidra.framework.options.GProperties; + +/** + * Options class that is paired with the {@link MsdOutputOptionsEditor} so that we can have a + * custom editor for an "Output" options panel for the {@link MicrosoftDemanglerAnalyzer}. + * Also see {@link MsdApplyOption}, which is another panel. + * The results of both get pushed into the {@link MicrosoftDemanglerOptions} to control the + * analyzer and underlying demangler. + */ +public class MsdOutputOption implements CustomOption { + + private static final String USE_ENCODED_ANONYMOUS_NAMESPACE = "useEncodedAnonymousNamespace"; + private static final String APPLY_TEMPLATE_ARG_TAGS = "applyTagsTemplateArgumentTags"; + + private static boolean DEFAULT_USE_ENCODED_ANONYMOUS_NAMESPACE = true; + private static boolean DEFAULT_APPLY_TEMPLATE_ARG_TAGS = true; + + private boolean useEncodedAnonymousNamespace = DEFAULT_USE_ENCODED_ANONYMOUS_NAMESPACE; + private boolean applyUdtArgumentTypeTag = DEFAULT_APPLY_TEMPLATE_ARG_TAGS; + + public MsdOutputOption() { + // required for persistence + } + + /** + * Sets the output flag to use an anonymous namespace's encoded number to craft a namespace + * containing this number instead of using the generic "`anonymous namespace'" name. Default + * is true (to create a namespace containing the encoded number) + * @param useEncodedAnonymousNamespaceArg {@code true} to use + */ + public void setUseEncodedAnonymousNamespace(boolean useEncodedAnonymousNamespaceArg) { + useEncodedAnonymousNamespace = useEncodedAnonymousNamespaceArg; + } + + /** + * Returns {@code true} if the output flag is set to use an anonymous namespace's encoded + * number to craft a namespace containing the number instead of using the generic + * "`anonymous namespace'" name. + * @return {@code true} if encoded number is used to craft a namespace + */ + public boolean getUseEncodedAnonymousNamespace() { + return useEncodedAnonymousNamespace; + } + + /** + * Sets the output flag for applying user-defined tags (e.g., class, struct, union, enum) + * within template and function arguments. Default is {@code true} (to apply) + * @param applyUdtArgumentTypeTagArg {@code true} to apply the tags + */ + public void setApplyUdtArgumentTypeTag(boolean applyUdtArgumentTypeTagArg) { + applyUdtArgumentTypeTag = applyUdtArgumentTypeTagArg; + } + + /** + * Returns {@code true} if the output interpretation is set to apply user-defined type + * tags (e.g., class, struct, union, enum) within template and function arguments. + * @return {@code true} if applying the tags + */ + public boolean getApplyUdtArgumentTypeTag() { + return applyUdtArgumentTypeTag; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof MsdOutputOption other)) { + return false; + } + + if (this == obj) { + return true; + } + + return useEncodedAnonymousNamespace == other.useEncodedAnonymousNamespace && + applyUdtArgumentTypeTag == other.applyUdtArgumentTypeTag; + } + + @Override + public int hashCode() { + return Objects.hash(applyUdtArgumentTypeTag, useEncodedAnonymousNamespace); + } + +//================================================================================================== +// Persistence +//================================================================================================== + + @Override + public void readState(GProperties properties) { + useEncodedAnonymousNamespace = + properties.getBoolean(USE_ENCODED_ANONYMOUS_NAMESPACE, useEncodedAnonymousNamespace); + applyUdtArgumentTypeTag = + properties.getBoolean(APPLY_TEMPLATE_ARG_TAGS, applyUdtArgumentTypeTag); + } + + @Override + public void writeState(GProperties properties) { + properties.putBoolean(USE_ENCODED_ANONYMOUS_NAMESPACE, useEncodedAnonymousNamespace); + properties.putBoolean(APPLY_TEMPLATE_ARG_TAGS, applyUdtArgumentTypeTag); + } +} diff --git a/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdOutputOptionsEditor.java b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdOutputOptionsEditor.java new file mode 100644 index 0000000000..f417960fd4 --- /dev/null +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/util/demangler/microsoft/options/MsdOutputOptionsEditor.java @@ -0,0 +1,211 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.app.util.demangler.microsoft.options; + +import java.awt.*; +import java.beans.PropertyEditorSupport; + +import javax.swing.*; +import javax.swing.border.Border; +import javax.swing.border.TitledBorder; + +import docking.options.editor.OptionsEditorAlignable; +import docking.widgets.checkbox.GCheckBox; +import ghidra.framework.options.CustomOptionsEditor; +import ghidra.util.HTMLUtilities; +import ghidra.util.layout.PairLayout; + +/** + * Editor used presenting and receiving GUI changes to {@link MsdOutputOption} + */ +public class MsdOutputOptionsEditor extends PropertyEditorSupport implements CustomOptionsEditor { + + private static final String USE_ENCODED_ANON_NS_LABEL = "Use Encoded Anonymous Namespace"; + private static final String APPLY_UDT_ARG_TAGS_LABEL = "Apply UDT Argument Type Tags"; + + private static final String[] NAMES = { USE_ENCODED_ANON_NS_LABEL, APPLY_UDT_ARG_TAGS_LABEL }; + + // help tooltips + private static final String USE_ENCODED_ANON_NS_TOOLTIP = + HTMLUtilities.toWrappedHTML( + "Instead of a variation of Anonymous Namespace, uses the encoded numeric " + + "identifier to output an _anon_ABCD1234 name form.", + 75); + + private static final String APPLY_UDT_ARG_TAGS_TOOLTIP = HTMLUtilities.toWrappedHTML( + "Applies a user-defined type's (UDT) class/enum/struct/union tag when the UDT is a " + + "template or function argument.", + 75); + + private static final String[] DESCRIPTIONS = { USE_ENCODED_ANON_NS_TOOLTIP, + APPLY_UDT_ARG_TAGS_TOOLTIP }; + + private MsdOutputOption msOutputOption; + + private Component editorComponent; + + private JCheckBox useEncodedAnonNsCb; + private JCheckBox useUdtTagsCb; + private JLabel useEncodedAnonNsLabel; + private JLabel useUdtTagsLabel; + + public MsdOutputOptionsEditor() { + editorComponent = buildEditor(); + } + + private Component buildEditor() { + // we want to have a panel with our options so that we may group them together + JPanel panel = new JPanel(new PairLayout(0, 6)); + + useEncodedAnonNsCb = new GCheckBox(); + useEncodedAnonNsCb.setSelected(true); + useEncodedAnonNsCb.setToolTipText(USE_ENCODED_ANON_NS_TOOLTIP); + useEncodedAnonNsLabel = new JLabel(USE_ENCODED_ANON_NS_LABEL, SwingConstants.RIGHT); + useEncodedAnonNsLabel.setLabelFor(useEncodedAnonNsCb); + useEncodedAnonNsLabel.setToolTipText(USE_ENCODED_ANON_NS_TOOLTIP); + panel.add(useEncodedAnonNsLabel); + panel.add(useEncodedAnonNsCb); + + useUdtTagsCb = new GCheckBox(); + useUdtTagsCb.setSelected(false); + useUdtTagsCb.setToolTipText(APPLY_UDT_ARG_TAGS_TOOLTIP); + useUdtTagsLabel = new JLabel(APPLY_UDT_ARG_TAGS_LABEL, SwingConstants.RIGHT); + useUdtTagsLabel.setLabelFor(useUdtTagsCb); + useUdtTagsLabel.setToolTipText(APPLY_UDT_ARG_TAGS_TOOLTIP); + panel.add(useUdtTagsLabel); + panel.add(useUdtTagsCb); + + useEncodedAnonNsCb.addItemListener(e -> firePropertyChange()); + useUdtTagsCb.addItemListener(e -> firePropertyChange()); + + Border emptyBorder = BorderFactory.createEmptyBorder(0, 0, 0, 0); + TitledBorder titledNoLineBorder = BorderFactory.createTitledBorder( + emptyBorder, // The invisible base border + "Output Options", // The title text + TitledBorder.LEADING, // Title justification (e.g., LEADING, CENTER, TRAILING) + TitledBorder.TOP, // Title position (e.g., TOP, BOTTOM) + null, // Optional: Font + null // Optional: Title color + ); + + // Use an outer panel so we can offset the main panel + JPanel outerPanel = new AlignablePanel(new BorderLayout()); + + panel.setBorder(BorderFactory.createEmptyBorder(5, 20, 0, 0)); + + outerPanel.add(panel, BorderLayout.CENTER); + outerPanel.setBorder(BorderFactory.createCompoundBorder( + BorderFactory.createEmptyBorder(10, 0, 10, 0), titledNoLineBorder)); + + return outerPanel; + } + + @Override + public void setValue(Object value) { + if (!(value instanceof MsdOutputOption option)) { + return; + } + msOutputOption = option; + setLocalValues(msOutputOption); + firePropertyChange(); + } + + private void setLocalValues(MsdOutputOption outputOptions) { + boolean useEncoded = outputOptions.getUseEncodedAnonymousNamespace(); + if (useEncoded != useEncodedAnonNsCb.isSelected()) { + useEncodedAnonNsCb.setSelected(useEncoded); + } + boolean applyComplexTag = outputOptions.getApplyUdtArgumentTypeTag(); + if (applyComplexTag != useUdtTagsCb.isSelected()) { + useUdtTagsCb.setSelected(applyComplexTag); + } + } + + private MsdOutputOption cloneNamespaceValues() { + MsdOutputOption newOption = new MsdOutputOption(); + newOption.setUseEncodedAnonymousNamespace(useEncodedAnonNsCb.isSelected()); + newOption.setApplyUdtArgumentTypeTag(useUdtTagsCb.isSelected()); + return newOption; + } + + @Override + public String[] getOptionDescriptions() { + return DESCRIPTIONS; + } + + @Override + public String[] getOptionNames() { + return NAMES; + } + + @Override + public Object getValue() { + return cloneNamespaceValues(); + } + + @Override + public Component getCustomEditor() { + return editorComponent; + } + + @Override + public boolean supportsCustomEditor() { + return true; + } + + // Allows us to mimic the alignment of the normal options for this custom editor and any other + // custom editor in the same view as us + private class AlignablePanel extends JPanel implements OptionsEditorAlignable { + + AlignablePanel(LayoutManager layout) { + super(layout); + } + + @Override + public Dimension getPreferredAlignmentSize() { + // + // Use all labels and components to find the overall preferred size. + // + int maxWidth = 0; + int maxHeight = 0; + Dimension size = getPairDimension(useEncodedAnonNsLabel, useEncodedAnonNsCb); + maxWidth = Math.max(size.width, maxWidth); + maxHeight = Math.max(size.height, maxHeight); + + size = getPairDimension(useUdtTagsLabel, useUdtTagsCb); + maxWidth = Math.max(size.width, maxWidth); + maxHeight = Math.max(size.height, maxHeight); + + return new Dimension(maxWidth, maxHeight); + } + + private Dimension getPairDimension(JLabel label, JComponent c) { + // Note: this code is taken from DefaultOptionComponent + Dimension dimension = label.getPreferredSize(); + int labelWidth = dimension.width; + int labelHeight = dimension.height; + int maxHeight = Math.max(labelHeight, c.getPreferredSize().height); + return new Dimension(labelWidth, maxHeight); + } + + @Override + public void setPreferredAlignmentSize(Dimension size) { + // This is called after all preferred sizes have been retrieved and combined + useEncodedAnonNsLabel.setPreferredSize(size); + useUdtTagsLabel.setPreferredSize(size); + } + } +} diff --git a/Ghidra/Features/MicrosoftDmang/developer_scripts/MDMangDeveloperDemangleNamesScript.java b/Ghidra/Features/MicrosoftDmang/developer_scripts/MDMangDeveloperDemangleNamesScript.java new file mode 100644 index 0000000000..3da73ebe0c --- /dev/null +++ b/Ghidra/Features/MicrosoftDmang/developer_scripts/MDMangDeveloperDemangleNamesScript.java @@ -0,0 +1,135 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.io.*; +import java.util.List; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.lang3.StringUtils; + +import docking.widgets.values.GValuesMap; +import ghidra.app.script.GhidraScript; +import ghidra.app.util.SymbolPath; +import ghidra.features.base.values.GhidraValuesMap; +import ghidra.util.MessageType; +import ghidra.util.StatusListener; +import mdemangler.*; +import utilities.util.FileUtilities; + +public class MDMangDeveloperDemangleNamesScript extends GhidraScript { + + private static final String TITLE = "Demangle Names"; + private static final String INPUT_PROMPT = "Choose an input file"; + private static final String OUTPUT_PROMPT = "Choose an output file"; + + private static boolean validateInputFile(GValuesMap valueMap, StatusListener status) { + File file = valueMap.getFile(INPUT_PROMPT); + if (file == null) { + status.setStatusText("Input file must be selected.", MessageType.ERROR); + return false; + } + if (!file.exists()) { + status.setStatusText(file.getAbsolutePath() + " is not a valid file.", + MessageType.ERROR); + return false; + } + return true; + } + + private static boolean validateOutputFile(GValuesMap valueMap, StatusListener status) { + File fileIn = valueMap.getFile(INPUT_PROMPT); + File fileOut = valueMap.getFile(OUTPUT_PROMPT); + String fileNameIn = fileIn.getAbsolutePath(); + String fileNameOut = fileOut.getAbsolutePath(); + if (fileNameOut.equals(fileNameIn)) { + status.setStatusText("Output file cannot be same as input file '" + fileNameOut + "').", + MessageType.ERROR); + return false; + } + return true; + } + + @Override + protected void run() throws Exception { + + GhidraValuesMap values = new GhidraValuesMap(); + + values.defineFile(INPUT_PROMPT, null); + values.setValidator((valueMap, status) -> { + return validateInputFile(valueMap, status); + }); + values = askValues(TITLE, null, values); + File inputFile = values.getFile(INPUT_PROMPT); + String inputFileName = inputFile.getAbsolutePath(); + + // creating a default output and asking again, to include output file query + String outputFileName = FilenameUtils.removeExtension(inputFileName) + ".out." + + FilenameUtils.getExtension(inputFileName); + values.defineFile(OUTPUT_PROMPT, new File(outputFileName)); + values.setValidator((valueMap, status) -> { + return validateInputFile(valueMap, status) && validateOutputFile(valueMap, status); + }); + setReusePreviousChoices(false); // false for second pass... want our default output + values = askValues(TITLE, null, values); + inputFile = values.getFile(INPUT_PROMPT); // might have changed + inputFileName = inputFile.getAbsolutePath(); // might have changed + File outputFile = values.getFile(OUTPUT_PROMPT); + + if (outputFile.exists()) { + if (!askYesNo("Confirm Overwrite", "Overwrite file: " + outputFile.getName())) { + println("Operation canceled"); + return; + } + } + + FileWriter fileWriter = new FileWriter(outputFile); + try (BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { + String message = "Processing " + inputFileName; + monitor.setMessage(message); + println(message); + List lines = FileUtilities.getLines(inputFile); + for (String name : lines) { + monitor.checkCancelled(); + String output = getProcessedName(name); + bufferedWriter.append(output); + bufferedWriter.append("\n"); + } + message = "Results located in: " + outputFile.getAbsolutePath(); + monitor.setMessage(message); + println(message); + } + } + + private String getProcessedName(String name) { + if (StringUtils.containsWhitespace(name)) { + return getError(name, "contains white space"); + } + MDMang demangler = new MDMang(); + demangler.setMangledSymbol(name); + try { + MDParsableItem item = demangler.demangle(); + SymbolPath sp = MDMangUtils.getSymbolPath(item); + return sp.toString(); + } + catch (MDException e) { + return getError(name, e.getMessage()); + } + } + + private String getError(String name, String reason) { + return "!Failed(" + reason + "): " + name; + } + +} diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/MDOutputOptions.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/MDOutputOptions.java index a2265bc532..11acd45c70 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/MDOutputOptions.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/MDOutputOptions.java @@ -21,7 +21,9 @@ package mdemangler; */ public class MDOutputOptions { - private boolean useEncodedAnonymousNamespaceNumber; + // These defaults match standard output + private boolean useEncodedAnonymousNamespaceNumber = false; + private boolean applyUdtArgumentTypeTag = true; /** * Constructor @@ -48,4 +50,23 @@ public class MDOutputOptions { return useEncodedAnonymousNamespaceNumber; } + /** + * Sets the option for whether to apply user-defined type tags when found as template + * or function arguments + * @param applyUdtArgumentTypeTag {@code true} to apply the tag on a complex type when + * used as a template or function argument + */ + public void setApplyUdtArgumentTypeTag(boolean applyUdtArgumentTypeTag) { + this.applyUdtArgumentTypeTag = applyUdtArgumentTypeTag; + } + + /** + * Returns {@code true} if the demangler will apply user-defined type tags when found as + * template or function arguments, such as the "struct" in "templateName" + * @return {@code true} if the flag is set to apply + */ + public boolean applyUdtArgumentTypeTag() { + return applyUdtArgumentTypeTag; + } + } diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/datatype/complex/MDComplexType.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/datatype/complex/MDComplexType.java index a7a00f667f..9e6b7641e9 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/datatype/complex/MDComplexType.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/datatype/complex/MDComplexType.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. @@ -64,4 +64,13 @@ public class MDComplexType extends MDDataType { qualifiedName.insert(builder); super.insert(builder); } + + public void insertWithoutComplexTag(StringBuilder builder) { + // TODO: look at what needs to be done to get rid of this? + if ((builder.length() != 0) && (builder.charAt(0) != ' ')) { + dmang.insertString(builder, " "); + } + qualifiedName.insert(builder); + } + } diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/functiontype/MDArgumentsList.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/functiontype/MDArgumentsList.java index 2e1b352daf..6bae505175 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/functiontype/MDArgumentsList.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/functiontype/MDArgumentsList.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. @@ -119,7 +119,13 @@ public class MDArgumentsList extends MDParsableItem { } firstArgDone = true; StringBuilder argBuilder = new StringBuilder(); - arg.insert(argBuilder); + if (arg instanceof MDComplexType ct && + !dmang.getOutputOptions().applyUdtArgumentTypeTag()) { + ct.insertWithoutComplexTag(argBuilder); + } + else { + arg.insert(argBuilder); + } dmang.appendString(builder, argBuilder.toString().trim()); // doing toString() allows the Based5 "bug" to be cleaned per parameter. // possible: dmang.appendString(builder, arg.toString().trim()); diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/functiontype/MDFunctionType.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/functiontype/MDFunctionType.java index 82ff3f93bd..a6e9cd96c9 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/functiontype/MDFunctionType.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/functiontype/MDFunctionType.java @@ -137,7 +137,7 @@ public class MDFunctionType extends MDType { // output on underscore-based access-level types (from MDTypeInfoParser '_' prefix), // specifically based5 variants. This could possibly be put into the MDMangVS2015 // demangler, but then we would probably need to describe the standard MDMang output - // as "invalid," as based-on-basedptr is supposed to be invalid. + // as "invalid," as based-on-basedptr is supposed to be invalid. StringBuilder conventionBuilder = new StringBuilder(); convention.insert(conventionBuilder); if (based != null) { @@ -179,6 +179,7 @@ public class MDFunctionType extends MDType { } } } + } /******************************************************************************/ diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDBasicName.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDBasicName.java index 9340d5ffc3..61f8995bbf 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDBasicName.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDBasicName.java @@ -17,6 +17,7 @@ package mdemangler.naming; import ghidra.util.Msg; import mdemangler.*; +import mdemangler.datatype.MDDataType; import mdemangler.object.MDObjectCPP; import mdemangler.template.MDTemplateNameAndArguments; @@ -131,6 +132,20 @@ public class MDBasicName extends MDParsableItem { } } + public void setXtorQual(MDQualifier qual) { + // We should only get a call for setName() due to a contructor or destructor, + // which come from MDSpecialName or from MDTemplateNameAndArguments. + if (specialName != null) { + specialName.setXtorQual(qual); + } + else if (templateNameAndArguments != null) { + templateNameAndArguments.setXtorQual(qual); + } + else { + Msg.warn(this, "name cannot be set"); + } + } + // This needs to be separate from nameModifier. The contrived example that follows // shows that both a nameModifier as well as a castTypeString should be considered // separately, as both can exist. Trying to manage multiple calls to @@ -150,6 +165,18 @@ public class MDBasicName extends MDParsableItem { } } + public void setCastType(MDDataType castType) { + if (specialName != null) { + specialName.setCastType(castType); + } + else if (templateNameAndArguments != null) { + templateNameAndArguments.setCastType(castType); + } + else { + Msg.warn(this, "castType cannot be set"); + } + } + @Override public void insert(StringBuilder builder) { if (reusableName != null) { diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDQualification.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDQualification.java index f13bb15f15..96af29ce29 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDQualification.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDQualification.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. @@ -94,6 +94,13 @@ public class MDQualification extends MDParsableItem implements Iterable