From b774ecb2d6c949e1d0023192d1bf440c98149241 Mon Sep 17 00:00:00 2001 From: dragonmacher <48328597+dragonmacher@users.noreply.github.com> Date: Tue, 11 Feb 2020 15:46:43 -0500 Subject: [PATCH] GT-3481 - Gnu Demangler - Checkpoint 1 - Separation of demangler analyzers; gnu options in analyzer; still require javadocs, help and tests --- .../analysis/AbstractDemanglerAnalyzer.java | 177 ++++++++++++++++ .../core/analysis/DemanglerAnalyzer.java | 138 ------------- .../app/util/demangler/DemangledObject.java | 15 +- .../ghidra/app/util/demangler/Demangler.java | 11 + .../app/util/demangler/DemanglerOptions.java | 10 + .../app/util/demangler/DemanglerParser.java | 30 --- .../app/util/demangler/DemanglerUtil.java | 25 ++- .../viewer/field/NamespacePropertyEditor.java | 7 +- .../DemangleElfWithOptionScript.java | 2 + .../core/analysis/GnuDemanglerAnalyzer.java | 195 ++++++++++++++++++ .../GnuDemanglerOptionsPropertyEditor.java | 179 ++++++++++++++++ .../analysis/GnuDemanglerWrappedOption.java | 125 +++++++++++ .../app/util/demangler/gnu/GnuDemangler.java | 75 ++++--- .../gnu/GnuDemanglerNativeProcess.java | 127 ++++++++++-- .../demangler/gnu/GnuDemanglerOptions.java | 115 +++++++++++ .../demangler/gnu/GnuDemanglerParser.java | 3 +- .../demangler/GnuDemanglerParserTest.java | 89 +++++--- .../util/demangler/gnu/GnuDemanglerTest.java | 64 +++++- .../analysis/MicrosoftDemanglerAnalyzer.java | 65 ++++++ .../microsoft/MicrosoftDemangler.java | 35 ++-- .../PDB/src/main/java/pdb/LoadPdbTask.java | 7 +- .../ghidra/framework/options/Options.java | 4 +- 22 files changed, 1231 insertions(+), 267 deletions(-) create mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DemanglerAnalyzer.java delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerParser.java create mode 100644 Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java create mode 100644 Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerOptionsPropertyEditor.java create mode 100644 Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerWrappedOption.java create mode 100644 Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerOptions.java create mode 100644 Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/plugin/core/analysis/MicrosoftDemanglerAnalyzer.java diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java new file mode 100644 index 0000000000..01a2184080 --- /dev/null +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/AbstractDemanglerAnalyzer.java @@ -0,0 +1,177 @@ +/* ### + * 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.plugin.core.analysis; + +import ghidra.app.services.*; +import ghidra.app.util.demangler.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +// TODO docme +public abstract class AbstractDemanglerAnalyzer extends AbstractAnalyzer { + + public AbstractDemanglerAnalyzer(String name, String description) { + super(name, description, AnalyzerType.BYTE_ANALYZER); + setPriority(AnalysisPriority.DATA_TYPE_PROPOGATION.before().before().before()); + } + + @Override + public boolean canAnalyze(Program program) { + // override this to be enable for a binary containing symbols you wisht to process + return true; + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + + DemanglerOptions options = getOptions(); + if (!validateOptions(options, log)) { + log.error(getName(), "Invalid demangler options--cannot demangle"); + return false; + } + + monitor.initialize(100); + + SymbolTable symbolTable = program.getSymbolTable(); + SymbolIterator it = symbolTable.getPrimarySymbolIterator(set, true); + while (it.hasNext()) { + monitor.checkCanceled(); + + Symbol symbol = it.next(); + if (skipSymbol(symbol)) { + continue; + } + + Address address = symbol.getAddress(); + String mangled = cleanSymbol(address, symbol.getName()); + DemangledObject demangled = demangle(mangled, options, log); + if (demangled != null) { + apply(program, address, demangled, options, log, monitor); + } + + Address min = set.getMinAddress(); + Address max = set.getMaxAddress(); + int distance = (int) (address.getOffset() - min.getOffset()); + int percent = (int) ((distance / max.getOffset()) * 100); + monitor.setProgress(percent); + } + + return true; + } + + // TODO callback before demangling begins... + protected boolean validateOptions(DemanglerOptions options, MessageLog log) { + // override to validate custom options for a particular demangler + return true; + } + + private boolean skipSymbol(Symbol symbol) { + if (symbol.getSource() == SourceType.DEFAULT) { + return true; + } + + // Only demangle global or external symbols when directly parented to a Library namespace + Namespace parentNamespace = symbol.getParentNamespace(); + if (symbol.isExternal()) { + if (!(parentNamespace instanceof Library)) { + return true; + } + } + else if (!parentNamespace.isGlobal()) { + return true; + } + + // Someone has already added arguments or return to the function signature + if (symbol.getSymbolType() == SymbolType.FUNCTION) { + Function function = (Function) symbol.getObject(); + if (function.getSignatureSource() != SourceType.DEFAULT) { + return true; + } + } + + return false; + } + + protected abstract DemangledObject doDemangle(String mangled, DemanglerOptions options, + MessageLog log) throws DemangledException; + + protected DemanglerOptions getOptions() { + // note: these can be stored in the analyzer subclass and updated when the + // analysis options change + DemanglerOptions options = new DemanglerOptions(); + options.setApplySignature(true); + options.setDoDisassembly(true); + options.setDemangleOnlyKnownPatterns(false); + return options; + } + + protected DemangledObject demangle(String mangled, DemanglerOptions options, + MessageLog log) { + + DemangledObject demangled = null; + try { + demangled = doDemangle(mangled, options, log); + } + catch (Throwable e) { + + if (e instanceof DemangledException) { + if (((DemangledException) e).isInvalidMangledName()) { + //ignore invalid names, consider as not an error + return null; + } + } + + log.appendMsg(getName(), + "Unable to demangle symbol: " + mangled + ". Message: " + e.getMessage()); + return null; + } + + return demangled; + } + + protected void apply(Program program, Address address, DemangledObject demangled, + DemanglerOptions options, MessageLog log, TaskMonitor monitor) { + + try { + if (demangled.applyTo(program, address, options, monitor)) { + return; + } + } + catch (Exception e) { + String message = e.getMessage(); + message = message == null ? "" : ". Message: " + message; + log.appendMsg(getName(), + "Unable to demangle symbol at " + address + "; name: " + + demangled.getMangledName() + message); + } + + log.appendMsg(getName(), + "Failed to apply mangled symbol at " + address + "; name: " + + demangled.getMangledName() + " (" + + getName() + "/" + demangled.getClass().getName() + ")"); + } + + protected String cleanSymbol(Address address, String name) { + return SymbolUtilities.getCleanSymbolName(name, address); + } + +} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DemanglerAnalyzer.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DemanglerAnalyzer.java deleted file mode 100644 index c7008c6c3c..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/analysis/DemanglerAnalyzer.java +++ /dev/null @@ -1,138 +0,0 @@ -/* ### - * 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.plugin.core.analysis; - -import ghidra.app.cmd.label.DemanglerCmd; -import ghidra.app.services.*; -import ghidra.app.util.demangler.DemanglerOptions; -import ghidra.app.util.importer.MessageLog; -import ghidra.framework.options.Options; -import ghidra.program.model.address.Address; -import ghidra.program.model.address.AddressSetView; -import ghidra.program.model.listing.*; -import ghidra.program.model.symbol.*; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; - -public class DemanglerAnalyzer extends AbstractAnalyzer { - private static final String NAME = "Demangler"; - private static final String DESCRIPTION = - "After a function is created, this analyzer will attempt to demangle " + - "the name and apply datatypes to parameters."; - - private static final String OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS = - "Only Demangle Known Mangled Symbols"; - private static final String OPTION_DESCRIPTION_USE_KNOWN_PATTERNS = - "Only demangle " + "symbols that follow known compiler mangling patterns. " + - "Leaving this option off may cause non-mangled symbols to get demangled."; - - private final static String OPTION_NAME_COMMIT_SIGNATURE = "Commit Function Signatures"; - private static final String OPTION_DESCRIPTION_COMMIT_SIGNATURE = - "Apply any recovered function signature, in addition to the function name"; - - private boolean doSignatureEnabled = true; - private boolean demangleAllSymbols = false; - - public DemanglerAnalyzer() { - super(NAME, DESCRIPTION, AnalyzerType.BYTE_ANALYZER); - setPriority(AnalysisPriority.DATA_TYPE_PROPOGATION.before().before().before()); - setDefaultEnablement(true); - } - - @Override - public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) - throws CancelledException { - SymbolTable symbolTable = program.getSymbolTable(); - - int progress = 0; - monitor.initialize(symbolTable.getNumSymbols()); - monitor.setShowProgressValue(false); - - SymbolIterator symiter = symbolTable.getPrimarySymbolIterator(set, true); - while (symiter.hasNext()) { - monitor.checkCanceled(); - - Symbol symbol = symiter.next(); - Address address = symbol.getAddress(); - if (address.compareTo(set.getMaxAddress()) > 0) { - break; - } - - if (symbol.getSource() == SourceType.DEFAULT) { - continue; - } - - // Only demangle global memory symbols or external - // symbols directly parented to a Library namespace - Namespace parentNamespace = symbol.getParentNamespace(); - if (symbol.isExternal()) { - if (!(parentNamespace instanceof Library)) { - continue; - } - } - else if (!parentNamespace.isGlobal()) { - continue; - } - - // Someone has already added arguments or return to the function - // signature. - if (symbol.getSymbolType() == SymbolType.FUNCTION) { - Function function = (Function) symbol.getObject(); - if (function.getSignatureSource() != SourceType.DEFAULT) { - continue; - } - } - - // retrieve symbol count each time, as the number of symbols changes while we are working - int count = symbolTable.getNumSymbols(); - monitor.setMaximum(count); - monitor.setProgress((int) ((progress++ / (double) count) * count)); - - DemanglerOptions options = new DemanglerOptions(); - options.setDoDisassembly(true); - options.setApplySignature(doSignatureEnabled); - options.setDemangleOnlyKnownPatterns(demangleAllSymbols); - - DemanglerCmd cmd = new DemanglerCmd(address, symbol.getName(), options); - if (!cmd.applyTo(program)) { - String message = cmd.getStatusMsg(); - if (message != null) { - log.appendMsg(cmd.getName(), message); - log.setStatus(message); - } - } - } - - monitor.setShowProgressValue(true); - return true; - } - - @Override - public void registerOptions(Options options, Program program) { - options.registerOption(OPTION_NAME_COMMIT_SIGNATURE, doSignatureEnabled, null, - OPTION_DESCRIPTION_COMMIT_SIGNATURE); - - options.registerOption(OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS, false, null, - OPTION_DESCRIPTION_USE_KNOWN_PATTERNS); - } - - @Override - public void optionsChanged(Options options, Program program) { - doSignatureEnabled = options.getBoolean(OPTION_NAME_COMMIT_SIGNATURE, doSignatureEnabled); - demangleAllSymbols = - options.getBoolean(OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS, demangleAllSymbols); - } -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java index c0a2e8ca89..ffacc6fc5f 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemangledObject.java @@ -115,6 +115,14 @@ public abstract class DemangledObject { return demangledName; } + /** + * Returns the original mangled name + * @return the name + */ + public String getMangledName() { + return originalMangled; + } + /** * Returns the demangled name of this object. * NOTE: unsupported symbol characters, like whitespace, will be @@ -495,8 +503,11 @@ public abstract class DemangledObject { List symbols = symbolTable.getSymbols(namespaceName, namespace); Symbol namespaceSymbol = - symbols.stream().filter(s -> (s.getSymbolType() == SymbolType.NAMESPACE || - s.getSymbolType() == SymbolType.CLASS)).findFirst().orElse(null); + symbols.stream() + .filter(s -> (s.getSymbolType() == SymbolType.NAMESPACE || + s.getSymbolType() == SymbolType.CLASS)) + .findFirst() + .orElse(null); if (namespaceSymbol == null) { try { namespace = diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/Demangler.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/Demangler.java index e210059d4e..0dd7d84232 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/Demangler.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/Demangler.java @@ -23,8 +23,19 @@ import ghidra.util.classfinder.ExtensionPoint; * the ClassSearcher will not find them. */ public interface Demangler extends ExtensionPoint { + public boolean canDemangle(Program program); + // TODO deprecate + @Deprecated public DemangledObject demangle(String mangled, boolean demangleOnlyKnownPatterns) throws DemangledException; + + // TODO docme + public DemangledObject demangle(String mangled, DemanglerOptions options) + throws DemangledException; + + public default DemanglerOptions createDefaultOptions() { + return new DemanglerOptions(); + } } 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 42d607e6d9..e185f044f7 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 @@ -92,4 +92,14 @@ public class DemanglerOptions { this.demangleOnlyKnownPatterns = demangleOnlyKnownPatterns; } + @Override + public String toString() { + //@formatter:off + return "{\n" + + "\tdoDisassembly: " + doDisassembly + ",\n" + + "\tapplySignature: " + applySignature + ",\n" + + "\tdemangleOnlyKnownPatterns: " + demangleOnlyKnownPatterns + ",\n" + + "}"; + //@formatter:on + } } diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerParser.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerParser.java deleted file mode 100644 index e876588cd2..0000000000 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerParser.java +++ /dev/null @@ -1,30 +0,0 @@ -/* ### - * IP: GHIDRA - * REVIEWED: YES - * - * 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; - - -public interface DemanglerParser { - - /** - * Parses the output of the demangler process and converts - * it into a demangled object. - * @param mangled the original mangled string - e.g., "_ZdaPv" - * @param demangled the demangled string - e.g., "operator_delete[](void*)" - * @return a demangled object - */ - public DemangledObject parse(String mangled, String demangled); -} diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerUtil.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerUtil.java index df7023bcec..a896a61a7d 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerUtil.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/demangler/DemanglerUtil.java @@ -32,7 +32,13 @@ public class DemanglerUtil { private static final Pattern TRAILING_PARAMETER_SPACE_PATTERN = Pattern.compile("([\\(\\,]) "); /** - * Locates all available demanglers, then it attempts to demangle. + * Locates all available demanglers, then it attempts to demangle. This method will + * query all demanglers regardless of architecture. + * + *

This method will use only the default options for demangling. If you need to + * specify options, then you will have to call each specific demangler directly, creating + * the options specifically needed for each demangler. See + * {@link Demangler#createDefaultOptions()}. * * @param mangled the mangled name * @return the demangled object or null @@ -59,6 +65,11 @@ public class DemanglerUtil { * Locates all available demanglers and checks to see if the supplied program is * supported, then it attempts to demangle. * + *

This method will use only the default options for demangling. If you need to + * specify options, then you will have to call each specific demangler directly, creating + * the options specifically needed for each demangler. See + * {@link Demangler#createDefaultOptions()}. + * * @param program the program containing the mangled name * @param mangled the mangled name * @return the demangled object or null @@ -95,12 +106,14 @@ public class DemanglerUtil { } /** - * Converts the list of names into a namespace linked list. + * Converts the list of names into a namespace demangled type. * Given names = { "A", "B", "C" }, which represents "A::B::C". * The following will be created {@literal "Namespace{A}->Namespace{B}->Namespace{C}"} * and Namespace{C} will be returned. * * NOTE: the list will be empty after the call. + * @param names the names to convert + * @return the newly created type */ public static DemangledType convertToNamespaces(List names) { if (names.size() == 0) { @@ -129,13 +142,13 @@ public class DemanglerUtil { private static String replace(String str, Pattern spaceCleanerPattern) { Matcher matcher = spaceCleanerPattern.matcher(str); - StringBuffer buf = new StringBuffer(); + StringBuilder buffy = new StringBuilder(); while (matcher.find()) { String captureGroup = matcher.group(1); - matcher.appendReplacement(buf, captureGroup); + matcher.appendReplacement(buffy, captureGroup); } - matcher.appendTail(buf); - return buf.toString(); + matcher.appendTail(buffy); + return buffy.toString(); } public static void setNamespace(DemangledType dt, DemangledType namespace) { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/NamespacePropertyEditor.java b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/NamespacePropertyEditor.java index 6b4741d4b7..dba11577c2 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/NamespacePropertyEditor.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/util/viewer/field/NamespacePropertyEditor.java @@ -83,11 +83,11 @@ public class NamespacePropertyEditor extends PropertyEditorSupport implements Cu showLibraryInNamespaceCheckBox = new GCheckBox(DISPLAY_LIBRARY_IN_NAMESPACE_LABEL); showLibraryInNamespaceCheckBox.setSelected(true); - showLocalCheckBox.setToolTipText(SHOW_LIBRARY_IN_NAMESPACE_TOOLTIP); + showLibraryInNamespaceCheckBox.setToolTipText(SHOW_LIBRARY_IN_NAMESPACE_TOOLTIP); panel.add(showNonLocalCheckBox); - panel.add(showLocalCheckBox); panel.add(showLibraryInNamespaceCheckBox); + panel.add(showLocalCheckBox); localPrefixField = createLocalPrefixTextField(LOCAL_NAMESPACE_PREFIX_LABEL, LOCAL_PREFIX_TOOLTIP, panel); @@ -169,7 +169,8 @@ public class NamespacePropertyEditor extends PropertyEditorSupport implements Cu if (namespaceOption.isShowLocalNamespace() != showLocalCheckBox.isSelected()) { showLocalCheckBox.setSelected(namespaceOption.isShowLocalNamespace()); } - if (namespaceOption.isShowLibraryInNamespace() != showLibraryInNamespaceCheckBox.isSelected()) { + if (namespaceOption.isShowLibraryInNamespace() != showLibraryInNamespaceCheckBox + .isSelected()) { showLibraryInNamespaceCheckBox.setSelected(namespaceOption.isShowLibraryInNamespace()); } if (namespaceOption.isUseLocalPrefixOverride() != useLocalPrefixCheckBox.isSelected()) { diff --git a/Ghidra/Features/GnuDemangler/ghidra_scripts/DemangleElfWithOptionScript.java b/Ghidra/Features/GnuDemangler/ghidra_scripts/DemangleElfWithOptionScript.java index 2043a82a33..d355b97543 100644 --- a/Ghidra/Features/GnuDemangler/ghidra_scripts/DemangleElfWithOptionScript.java +++ b/Ghidra/Features/GnuDemangler/ghidra_scripts/DemangleElfWithOptionScript.java @@ -75,6 +75,7 @@ public class DemangleElfWithOptionScript extends GhidraScript { return; } + // TODO change to GnuDemanglerOptions DemanglerOptions options = new DemanglerOptions(); options.setDoDisassembly(false); options.setApplySignature(true); @@ -113,6 +114,7 @@ public class DemangleElfWithOptionScript extends GhidraScript { return executableFormat != null && executableFormat.indexOf(MachoLoader.MACH_O_NAME) != -1; } + /// TODO this is here because we did not support program arguments. replace this code private Process createProcess(String executableName) throws Exception { String demanglerName = GnuDemanglerNativeProcess.DEMANGLER_GNU; diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java new file mode 100644 index 0000000000..88238bab48 --- /dev/null +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerAnalyzer.java @@ -0,0 +1,195 @@ +/* ### + * 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.plugin.core.analysis; + +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; + +import ghidra.app.util.demangler.*; +import ghidra.app.util.demangler.gnu.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.*; +import ghidra.program.model.listing.Program; +import ghidra.util.HelpLocation; +import ghidra.util.Msg; + +public class GnuDemanglerAnalyzer extends AbstractDemanglerAnalyzer { + + private static final String NAME = "Demangler GNU"; + private static final String DESCRIPTION = + "After a function is created, this analyzer will attempt to demangle " + + "the name and apply datatypes to parameters."; + + private static final String OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS = + "Only Demangle Known Mangled Symbols"; + private static final String OPTION_DESCRIPTION_USE_KNOWN_PATTERNS = + "Only demangle symbols that follow known compiler mangling patterns. " + + "Leaving this option off may cause non-mangled symbols to get demangled."; + + private static final String OPTION_NAME_APPLY_SIGNATURE = "Apply Function Signatures"; + private static final String OPTION_DESCRIPTION_APPLY_SIGNATURE = + "Apply any recovered function signature, in addition to the function name"; + + // note: we use 'Z' as a trick to be below the other options + private static final String OPTION_NAME_GNU_DEMANGLER = "Z GNU Demangler"; + + private boolean doSignatureEnabled = true; + private boolean demangleOnlyKnownPatterns = false; + private GnuDemanglerOptionsPropertyEditor gnuOptionsEditor = + new GnuDemanglerOptionsPropertyEditor(); + private GnuDemanglerWrappedOption gnuWrappedOptions; + + private GnuDemangler demangler = new GnuDemangler(); + + public GnuDemanglerAnalyzer() { + super(NAME, DESCRIPTION); + setDefaultEnablement(true); + } + + @Override + public boolean canAnalyze(Program program) { + return demangler.canDemangle(program); + } + + @Override + public void registerOptions(Options options, Program program) { + options.registerOption(OPTION_NAME_APPLY_SIGNATURE, doSignatureEnabled, null, + OPTION_DESCRIPTION_APPLY_SIGNATURE); + + options.registerOption(OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS, false, null, + OPTION_DESCRIPTION_USE_KNOWN_PATTERNS); + + options.registerOptionsEditor(null); + + HelpLocation help = new HelpLocation("AutoAnalysisPlugin", "Demangler_Analyzer"); + options.registerOption(OPTION_NAME_GNU_DEMANGLER, OptionType.CUSTOM_TYPE, + new GnuDemanglerWrappedOption(), help, "Advanced GNU demangler options", + gnuOptionsEditor); + + CustomOption customOption = options.getCustomOption(OPTION_NAME_GNU_DEMANGLER, + new GnuDemanglerWrappedOption()); + if (!(customOption instanceof GnuDemanglerWrappedOption)) { + customOption = new GnuDemanglerWrappedOption(); + Msg.debug(this, "Unexpected custom option type for GNU Demangler: " + + customOption.getClass()); + } + gnuWrappedOptions = (GnuDemanglerWrappedOption) customOption; + + } + + @Override + public void optionsChanged(Options options, Program program) { + doSignatureEnabled = options.getBoolean(OPTION_NAME_APPLY_SIGNATURE, doSignatureEnabled); + demangleOnlyKnownPatterns = + options.getBoolean(OPTION_NAME_DEMANGLE_USE_KNOWN_PATTERNS, demangleOnlyKnownPatterns); + + gnuWrappedOptions = + (GnuDemanglerWrappedOption) options.getCustomOption(OPTION_NAME_GNU_DEMANGLER, + new GnuDemanglerWrappedOption()); + } + + @Override + protected DemanglerOptions getOptions() { + + GnuDemanglerOptions options = new GnuDemanglerOptions(); + options.setDoDisassembly(true); + options.setApplySignature(doSignatureEnabled); + options.setDemangleOnlyKnownPatterns(demangleOnlyKnownPatterns); + + options.setUseDeprecatedDemangler(gnuWrappedOptions.useDeprecatedDemangler()); + + String text = null; + if (gnuWrappedOptions.useDemanglerParameters()) { + text = gnuWrappedOptions.getDemanglerParametersText(); + } + options.setDemanglerApplicationArguments(text); + + return options; + } + + @Override + protected boolean validateOptions(DemanglerOptions demanglerOtions, MessageLog log) { + + GnuDemanglerOptions options = (GnuDemanglerOptions) demanglerOtions; + String applicationArguments = options.getDemanglerApplicationArguments(); + if (StringUtils.isBlank(applicationArguments)) { + return true; + } + + // Check that the supplied arguments will work with at least one of the requested + // demanglers. (Different versions of the GNU demangler support different arguments.) + String demanglerName = options.getDemanglerName(); + try { + GnuDemanglerNativeProcess.getDemanglerNativeProcess(demanglerName, + applicationArguments); + return true; + } + catch (IOException e) { + log.error(getName(), "Invalid options for GNU dangler '" + demanglerName + + "': " + applicationArguments); + log.appendException(e); + } + + if (options.useDeprecatedDemangler()) { + // see if the options work in the deprecated demangler + GnuDemanglerOptions deprecatedOptions = options.withDeprecatedDemangler(); + String deprecatedName = deprecatedOptions.getDemanglerName(); + try { + GnuDemanglerNativeProcess.getDemanglerNativeProcess(deprecatedName, + applicationArguments); + return true; + } + catch (IOException e) { + log.error(getName(), + "Invalid options for GNU dangler '" + deprecatedName + "': " + + applicationArguments); + log.appendException(e); + } + } + + return false; + + } + + @Override + protected DemangledObject doDemangle(String mangled, DemanglerOptions demanglerOtions, + MessageLog log) + throws DemangledException { + + GnuDemanglerOptions options = (GnuDemanglerOptions) demanglerOtions; + DemangledObject demangled = null; + try { + demangled = demangler.demangle(mangled, options); + } + catch (DemangledException e) { + if (!options.useDeprecatedDemangler()) { + throw e; // let our parent handle this + } + } + + if (demangled != null) { + return demangled; + } + + if (options.useDeprecatedDemangler()) { + GnuDemanglerOptions newOptions = options.withDeprecatedDemangler(); + demangled = demangler.demangle(mangled, newOptions); + } + + return demangled; + } +} diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerOptionsPropertyEditor.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerOptionsPropertyEditor.java new file mode 100644 index 0000000000..51731b3709 --- /dev/null +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerOptionsPropertyEditor.java @@ -0,0 +1,179 @@ +/* ### + * 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.plugin.core.analysis; + +import java.awt.Component; +import java.awt.Container; +import java.beans.PropertyEditorSupport; +import java.util.Objects; + +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +import docking.widgets.checkbox.GCheckBox; +import ghidra.framework.options.CustomOptionsEditor; +import ghidra.util.layout.HorizontalLayout; +import ghidra.util.layout.VerticalLayout; + +public class GnuDemanglerOptionsPropertyEditor extends PropertyEditorSupport + implements CustomOptionsEditor { + + private static final String USE_DEPRECATED_DEMANGLER = "Use Deprecated Demangler"; + private static final String USE_DEMANGLER_PARAMETERS = "Use Demangler Program Parameters"; + + private static final String USE_DEPRECATED_DEMANGLER_TOOLTIP = + "Signals to use the deprecated demangler when the modern demangler cannot demangle a " + + "given string"; + private static final String USE_DEMANGLER_PARAMETERS_TOOLTIP = + "Signals to use pass the given parameters to the demangler program"; + + private static final String[] NAMES = + { USE_DEPRECATED_DEMANGLER, USE_DEMANGLER_PARAMETERS }; + + private static final String[] DESCRIPTIONS = { USE_DEPRECATED_DEMANGLER_TOOLTIP, + USE_DEMANGLER_PARAMETERS_TOOLTIP }; + + private GnuDemanglerWrappedOption wrappedOption; + + private Component editorComponent; + + private GCheckBox useDeprecatedDemanglerBox; + private GCheckBox useDemanglerParametersBox; + private JTextField demanglerParametersTextField; + + public GnuDemanglerOptionsPropertyEditor() { + 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 VerticalLayout(3)); + + useDeprecatedDemanglerBox = new GCheckBox(USE_DEPRECATED_DEMANGLER); + useDeprecatedDemanglerBox.setSelected(false); + useDeprecatedDemanglerBox.setToolTipText(USE_DEPRECATED_DEMANGLER_TOOLTIP); + useDeprecatedDemanglerBox.addItemListener(e -> firePropertyChange()); + panel.add(useDeprecatedDemanglerBox); + + createParameterComponent(panel); + + return panel; + } + + private void createParameterComponent(Container parent) { + + JPanel textFieldPanel = new JPanel(new HorizontalLayout(0)); + JTextField textField = new JTextField(15); + useDemanglerParametersBox = new GCheckBox(USE_DEMANGLER_PARAMETERS); + useDemanglerParametersBox.setToolTipText(USE_DEMANGLER_PARAMETERS_TOOLTIP); + useDemanglerParametersBox.addItemListener(e -> { + textField.setEnabled(useDemanglerParametersBox.isSelected()); + firePropertyChange(); + }); + + textField.getDocument().addDocumentListener(new DocumentListener() { + @Override + public void changedUpdate(DocumentEvent e) { + firePropertyChange(); + } + + @Override + public void insertUpdate(DocumentEvent e) { + firePropertyChange(); + } + + @Override + public void removeUpdate(DocumentEvent e) { + firePropertyChange(); + } + }); + + textField.setEnabled(false); + + textFieldPanel.add(useDemanglerParametersBox); + textFieldPanel.add(Box.createHorizontalStrut(10)); + textFieldPanel.add(textField); + + parent.add(textFieldPanel); + + demanglerParametersTextField = textField; + } + + @Override + public void setValue(Object value) { + + if (!(value instanceof GnuDemanglerWrappedOption)) { + return; + } + + wrappedOption = (GnuDemanglerWrappedOption) value; + setLocalValues(wrappedOption); + firePropertyChange(); + } + + private void setLocalValues(GnuDemanglerWrappedOption newOption) { + + if (newOption.useDeprecatedDemangler() != useDeprecatedDemanglerBox.isSelected()) { + useDeprecatedDemanglerBox.setSelected(newOption.useDeprecatedDemangler()); + } + + if (newOption.useDemanglerParameters() != useDemanglerParametersBox.isSelected()) { + useDemanglerParametersBox.setSelected(newOption.useDemanglerParameters()); + } + + String newText = newOption.getDemanglerParametersText(); + String currentText = demanglerParametersTextField.getText(); + if (!Objects.equals(newText, currentText)) { + demanglerParametersTextField.setText(newText); + } + } + + @Override + public Object getValue() { + return cloneNamespaceValues(); + } + + private GnuDemanglerWrappedOption cloneNamespaceValues() { + + GnuDemanglerWrappedOption newOption = new GnuDemanglerWrappedOption(); + newOption.setUseDeprecatedDemangler(useDeprecatedDemanglerBox.isSelected()); + newOption.setUseDemanglerParameters(useDemanglerParametersBox.isSelected()); + newOption.setDemanglerParametersText(demanglerParametersTextField.getText()); + return newOption; + } + + @Override + public String[] getOptionNames() { + return NAMES; + } + + @Override + public String[] getOptionDescriptions() { + return DESCRIPTIONS; + } + + @Override + public Component getCustomEditor() { + return editorComponent; + } + + @Override + public boolean supportsCustomEditor() { + return true; + } +} diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerWrappedOption.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerWrappedOption.java new file mode 100644 index 0000000000..502f906e13 --- /dev/null +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/plugin/core/analysis/GnuDemanglerWrappedOption.java @@ -0,0 +1,125 @@ +/* ### + * 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.plugin.core.analysis; + +import java.util.Objects; + +import ghidra.framework.options.CustomOption; +import ghidra.framework.options.SaveState; + +/** + * A simple java bean adapted to the {@link CustomOption} interface. The public + * getters and setters are self-documenting. + */ +public class GnuDemanglerWrappedOption implements CustomOption { + + private static final String USE_DEPRECATED_DEMANGLER = "USE_DEPRECATED_DEMANGLER"; + private static final String USE_DEMANGLER_PARAMETERS = "USE_DEMANGLER_PARAMETERS"; + private static final String DEMANGLER_PARAMETERS = "DEMANGLER_PARAMETERS"; + + private boolean useDeprecatedDemangler = false; + private boolean useDemanglerParameters = false; + private String demanglerParametersText = null; + + public void setUseDeprecatedDemangler(boolean doUse) { + this.useDeprecatedDemangler = doUse; + } + + public boolean useDeprecatedDemangler() { + return useDeprecatedDemangler; + } + + public void setDemanglerParametersText(String text) { + this.demanglerParametersText = text; + } + + public String getDemanglerParametersText() { + return demanglerParametersText; + } + + public void setUseDemanglerParameters(boolean doUse) { + this.useDemanglerParameters = doUse; + } + + public boolean useDemanglerParameters() { + return useDemanglerParameters; + } + + @Override + public void readState(SaveState state) { + useDeprecatedDemangler = + state.getBoolean(USE_DEPRECATED_DEMANGLER, useDemanglerParameters); + useDemanglerParameters = + state.getBoolean(USE_DEPRECATED_DEMANGLER, useDemanglerParameters); + demanglerParametersText = + state.getString(DEMANGLER_PARAMETERS, demanglerParametersText); + } + + @Override + public void writeState(SaveState state) { + state.putBoolean(USE_DEPRECATED_DEMANGLER, useDeprecatedDemangler); + state.putBoolean(USE_DEMANGLER_PARAMETERS, useDemanglerParameters); + state.putString(USE_DEMANGLER_PARAMETERS, demanglerParametersText); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((demanglerParametersText == null) ? 0 : demanglerParametersText.hashCode()); + result = prime * result + (useDemanglerParameters ? 1231 : 1237); + result = prime * result + (useDeprecatedDemangler ? 1231 : 1237); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + + GnuDemanglerWrappedOption other = (GnuDemanglerWrappedOption) obj; + if (!Objects.equals(demanglerParametersText, other.demanglerParametersText)) { + return false; + } + + if (useDemanglerParameters != other.useDemanglerParameters) { + return false; + } + if (useDeprecatedDemangler != other.useDeprecatedDemangler) { + return false; + } + return true; + } + + @Override + public String toString() { + //@formatter:off + return "{\n" + + "\tuseDeprecatedDemangler: " + useDeprecatedDemangler + ",\n" + + "\tuseDemanglerParameters: " + useDemanglerParameters + ",\n" + + "\tdemanglerParametersText: " + demanglerParametersText + ",\n" + + "}"; + //@formatter:on + } +} diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java index ef40aee359..b612071096 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemangler.java @@ -38,17 +38,22 @@ public class GnuDemangler implements Demangler { // needed to instantiate dynamically } + @Override + public DemanglerOptions createDefaultOptions() { + return new GnuDemanglerOptions(); + } + @Override public boolean canDemangle(Program program) { + String executableFormat = program.getExecutableFormat(); if (isELF(executableFormat) || isMacho(executableFormat)) { return true; } - //check if language is GCC - CompilerSpec compilerSpec = program.getCompilerSpec(); - if (compilerSpec.getCompilerSpecID().getIdAsString().toLowerCase().indexOf( - "windows") == -1) { + CompilerSpec spec = program.getCompilerSpec(); + String specId = spec.getCompilerSpecID().getIdAsString(); + if (!specId.toLowerCase().contains("windows")) { return true; } return false; @@ -57,13 +62,21 @@ public class GnuDemangler implements Demangler { @Override public DemangledObject demangle(String mangled, boolean demangleOnlyKnownPatterns) throws DemangledException { + GnuDemanglerOptions options = new GnuDemanglerOptions(); + options.setDemangleOnlyKnownPatterns(demangleOnlyKnownPatterns); + return demangle(mangled, options); + } - if (skip(mangled, demangleOnlyKnownPatterns)) { + @Override + public DemangledObject demangle(String mangled, DemanglerOptions demanglerOtions) + throws DemangledException { + + if (skip(mangled, demanglerOtions)) { return null; } + GnuDemanglerOptions options = getGnuOptions(demanglerOtions); String originalMangled = mangled; - String globalPrefix = null; if (mangled.startsWith(GLOBAL_PREFIX)) { int index = mangled.indexOf("_Z"); @@ -84,15 +97,16 @@ public class GnuDemangler implements Demangler { } try { - GnuDemanglerNativeProcess process = GnuDemanglerNativeProcess.getDemanglerNativeProcess(); - String demangled = process.demangle(mangled).trim(); + GnuDemanglerNativeProcess process = getNativeProcess(options); + String demangled = process.demangle(mangled).trim(); if (mangled.equals(demangled) || demangled.length() == 0) { throw new DemangledException(true); } + boolean onlyKnownPatterns = options.demangleOnlyKnownPatterns(); DemangledObject demangledObject = - parse(mangled, process, demangled, demangleOnlyKnownPatterns); + parse(mangled, process, demangled, onlyKnownPatterns); if (demangledObject == null) { return demangledObject; } @@ -107,6 +121,7 @@ public class GnuDemangler implements Demangler { else { demangledObject.setSignature(demangled); } + demangledObject.setOriginalMangled(originalMangled); if (isDwarf) { @@ -121,7 +136,7 @@ public class GnuDemangler implements Demangler { return demangledObject; } catch (IOException e) { - if (e.getMessage().endsWith("14001")) {//missing runtime dlls, prolly + if (e.getMessage().endsWith("14001")) { ResourceFile installationDir = Application.getInstallationDirectory(); throw new DemangledException("Missing runtime libraries. " + "Please install " + installationDir + File.separatorChar + "support" + File.separatorChar + @@ -131,7 +146,25 @@ public class GnuDemangler implements Demangler { } } - private boolean skip(String mangled, boolean demangleOnlyKnownPatterns) { + private GnuDemanglerOptions getGnuOptions(DemanglerOptions options) { + + if (options instanceof GnuDemanglerOptions) { + return (GnuDemanglerOptions) options; + } + + return new GnuDemanglerOptions(options); + } + + private GnuDemanglerNativeProcess getNativeProcess(GnuDemanglerOptions options) + throws IOException { + + String demanglerName = options.getDemanglerName(); + String applicationOptions = options.getDemanglerApplicationArguments(); + return GnuDemanglerNativeProcess.getDemanglerNativeProcess(demanglerName, + applicationOptions); + } + + private boolean skip(String mangled, DemanglerOptions options) { // Ignore versioned symbols which are generally duplicated at the same address if (mangled.indexOf("@") > 0) { // do not demangle versioned symbols @@ -143,7 +176,7 @@ public class GnuDemangler implements Demangler { return true; } - if (!demangleOnlyKnownPatterns) { + if (!options.demangleOnlyKnownPatterns()) { return false; // let it go through } @@ -167,24 +200,20 @@ public class GnuDemangler implements Demangler { return true; } - private DemangledObject parse(String mangled, GnuDemanglerNativeProcess process, String demangled, + private DemangledObject parse(String mangled, GnuDemanglerNativeProcess process, + String demangled, boolean demangleOnlyKnownPatterns) { - if (demangleOnlyKnownPatterns && !isMangledString(mangled, demangled)) { + if (demangleOnlyKnownPatterns && !isKnownMangledString(mangled, demangled)) { return null; } - try { - GnuDemanglerParser parser = new GnuDemanglerParser(process); - DemangledObject demangledObject = parser.parse(mangled, demangled); - return demangledObject; - } - catch (Exception e) { - throw e; - } + GnuDemanglerParser parser = new GnuDemanglerParser(process); + DemangledObject demangledObject = parser.parse(mangled, demangled); + return demangledObject; } - private boolean isMangledString(String mangled, String demangled) { + private boolean isKnownMangledString(String mangled, String demangled) { // // We get requests to demangle strings that are not mangled. For newer mangled strings // we know how to avoid that. However, older mangled strings can be of many forms. To diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerNativeProcess.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerNativeProcess.java index 941922e671..a058be8526 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerNativeProcess.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerNativeProcess.java @@ -16,29 +16,68 @@ package ghidra.app.util.demangler.gnu; import java.io.*; +import java.nio.charset.Charset; +import java.util.*; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import ghidra.framework.Application; import ghidra.framework.Platform; public class GnuDemanglerNativeProcess { - public static final String DEMANGLER_GNU = "demangler_gnu_v2.33.1"; + public static final String DEMANGLER_GNU = GnuDemanglerOptions.GNU_DEMANGLER_DEFAULT; - private static GnuDemanglerNativeProcess demanglerNativeProcess; + private static final String DEFAULT_NATIVE_OPTIONS = ""; + private static final Map processesByName = + new HashMap<>(); + + private String applicationName; + private String options; private boolean isDisposed; private Process process; private BufferedReader reader; private PrintWriter writer; + // TODO docme public static synchronized GnuDemanglerNativeProcess getDemanglerNativeProcess() throws IOException { - if (demanglerNativeProcess == null) { - demanglerNativeProcess = new GnuDemanglerNativeProcess(); - } - return demanglerNativeProcess; + return getDemanglerNativeProcess(DEMANGLER_GNU); } - private GnuDemanglerNativeProcess() throws IOException { + // TODO docme + public static synchronized GnuDemanglerNativeProcess getDemanglerNativeProcess(String name) + throws IOException { + + return getDemanglerNativeProcess(name, DEFAULT_NATIVE_OPTIONS); + } + + // TODO docme + // TODO we should probably age-off all demanglers by access time + public static synchronized GnuDemanglerNativeProcess getDemanglerNativeProcess(String name, + String nativeOptions) + throws IOException { + + String options = nativeOptions; + if (StringUtils.isBlank(options)) { + options = DEFAULT_NATIVE_OPTIONS; + } + + String key = name + nativeOptions; + GnuDemanglerNativeProcess nativeProcess = processesByName.get(key); + if (nativeProcess == null) { + nativeProcess = new GnuDemanglerNativeProcess(name, options); + processesByName.put(key, nativeProcess); + } + return nativeProcess; + } + + private GnuDemanglerNativeProcess(String applicationName, String options) throws IOException { + this.applicationName = applicationName; + this.options = options; createProcess(); } @@ -51,22 +90,25 @@ public class GnuDemanglerNativeProcess { private String demangle(String mangled, boolean restart) throws IOException { try { - writer.println(mangled); - writer.flush(); - - return reader.readLine(); + return doDemangle(mangled); } catch (IOException e) { dispose(); if (!restart) { - demanglerNativeProcess = null; - throw new IOException("Demangler process is not running."); + processesByName.remove(applicationName); + throw new IOException("Demangler process is not running.", e); } createProcess(); return demangle(mangled, false); } } + private String doDemangle(String mangled) throws IOException { + writer.println(mangled); + writer.flush(); + return reader.readLine(); + } + private void dispose() { try { if (process != null) { @@ -86,19 +128,66 @@ public class GnuDemanglerNativeProcess { private void createProcess() throws IOException { - String executableName = DEMANGLER_GNU + Platform.CURRENT_PLATFORM.getExecutableExtension(); - File commandPath = Application.getOSFile(executableName); - - String[] command = new String[] { commandPath.getAbsolutePath() }; - + String[] command = buildCommand(); process = Runtime.getRuntime().exec(command); InputStream in = process.getInputStream(); OutputStream out = process.getOutputStream(); - reader = new BufferedReader(new InputStreamReader(in)); writer = new PrintWriter(out); + checkForError(command); + isDisposed = false; } + + private String[] buildCommand() throws FileNotFoundException { + + String executableName = + applicationName + Platform.CURRENT_PLATFORM.getExecutableExtension(); + File commandPath = Application.getOSFile(executableName); + + String[] command = new String[] { commandPath.getAbsolutePath() }; + if (!StringUtils.isBlank(options)) { + String[] optionsArray = options.split("\\s"); + command = ArrayUtils.addAll(command, optionsArray); + } + return command; + } + + private void checkForError(String[] command) throws IOException { + + // + // We do not want to read the error stream in the happy path case, as that will block. + // Send a test string over and read the result. If the test string is blank, then + // there was an error. + // + String testResult = doDemangle("test"); + if (!StringUtils.isBlank(testResult)) { + return; + } + + InputStream err = process.getErrorStream(); + String error = null; + try { + List errorLines = IOUtils.readLines(err, Charset.defaultCharset()); + error = StringUtils.join(errorLines, '\n'); + } + catch (IOException e) { + throw new IOException("Unable to read process error stream: ", e); + } + + if (StringUtils.isBlank(error)) { + return; + } + + String executable = command[0]; + String baseName = FilenameUtils.getBaseName(executable); + command[0] = baseName; + + // cleanup full path, as it is ugly in the error message + error = error.replace(executable, ""); + throw new IOException("Error starting demangler with command: '" + + Arrays.toString(command) + "' " + error); + } } diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerOptions.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerOptions.java new file mode 100644 index 0000000000..abf44c2379 --- /dev/null +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerOptions.java @@ -0,0 +1,115 @@ +/* ### + * 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.gnu; + +import org.apache.commons.lang3.StringUtils; + +import ghidra.app.util.demangler.DemanglerOptions; + +public class GnuDemanglerOptions extends DemanglerOptions { + + /** + * Version 2.24 of the GNU demangler. This version supports older formats and older bugs. + */ + public static final String GNU_DEMANGLER_V2_24 = "demangler_gnu_v2_24"; + + /** + * Version 2.33.1 of the GNU demangler. This version supports less formats than older versions. + */ + public static final String GNU_DEMANGLER_V2_33_1 = "demangler_gnu_v2_33_1"; + + /** + * The default version to use of the GNU demangler + */ + public static final String GNU_DEMANGLER_DEFAULT = GNU_DEMANGLER_V2_33_1; + + private String demanglerName = GNU_DEMANGLER_DEFAULT; + private String demanglerApplicationArguments; + private boolean useDeprecatedDemangler; + + public GnuDemanglerOptions() { + // use default values + } + + public GnuDemanglerOptions(DemanglerOptions copy) { + super(copy); + + if (copy instanceof GnuDemanglerOptions) { + GnuDemanglerOptions gCopy = (GnuDemanglerOptions) copy; + demanglerName = gCopy.demanglerName; + demanglerApplicationArguments = gCopy.demanglerApplicationArguments; + useDeprecatedDemangler = gCopy.useDeprecatedDemangler; + } + } + + // TODO docme + public String getDemanglerName() { + return demanglerName; + } + + // TODO docme + // TODO should we validate and or log a message it the name is unknown? + public void setDemanglerName(String name) { + this.demanglerName = name; + } + + // TODO docme + public String getDemanglerApplicationArguments() { + return demanglerApplicationArguments; + } + + // TODO docme + public void setDemanglerApplicationArguments(String args) { + this.demanglerApplicationArguments = args; + } + + // TODO docme + // TODO mabye rename to hasNativeApplicationOptions() + public boolean hasDemanglerApplicationArguments() { + return !StringUtils.isBlank(demanglerApplicationArguments); + } + + // TODO docme + public void setUseDeprecatedDemangler(boolean doUse) { + this.useDeprecatedDemangler = doUse; + } + + // TODO docme + public boolean useDeprecatedDemangler() { + return useDeprecatedDemangler; + } + + // TODO docme + public GnuDemanglerOptions withDeprecatedDemangler() { + GnuDemanglerOptions newOptions = new GnuDemanglerOptions(this); + newOptions.setDemanglerName(GNU_DEMANGLER_V2_24); + return newOptions; + } + + @Override + public String toString() { + //@formatter:off + return "{\n" + + "\tdoDisassembly: " + doDisassembly() + ",\n" + + "\tapplySignature: " + applySignature() + ",\n" + + "\tdemangleOnlyKnownPatterns: " + demangleOnlyKnownPatterns() + ",\n" + + "\tdemanglerName: " + demanglerName + ",\n" + + "\tuseDeprecatedDemangler: " + useDeprecatedDemangler + ",\n" + + "\tdemanglerApplicationArguments: " + demanglerApplicationArguments + ",\n" + + "}"; + //@formatter:on + } +} diff --git a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java index 267eca728c..47c9ecda9b 100644 --- a/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java +++ b/Ghidra/Features/GnuDemangler/src/main/java/ghidra/app/util/demangler/gnu/GnuDemanglerParser.java @@ -27,7 +27,7 @@ import ghidra.app.util.demangler.*; import ghidra.program.model.lang.CompilerSpec; import ghidra.util.StringUtilities; -public class GnuDemanglerParser implements DemanglerParser { +public class GnuDemanglerParser { private static final String CONSTRUCTION_VTABLE_FOR = "construction vtable for "; private static final String VTT_FOR = "VTT for "; @@ -154,7 +154,6 @@ public class GnuDemanglerParser implements DemanglerParser { this.process = process; } - @Override public DemangledObject parse(String mangled, String demangled) { try { return doParse(mangled, demangled); diff --git a/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/GnuDemanglerParserTest.java b/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/GnuDemanglerParserTest.java index d0d8b4c1ae..5424c41c98 100644 --- a/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/GnuDemanglerParserTest.java +++ b/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/GnuDemanglerParserTest.java @@ -33,7 +33,8 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { @Before public void setUp() throws Exception { - process = GnuDemanglerNativeProcess.getDemanglerNativeProcess(); + process = GnuDemanglerNativeProcess + .getDemanglerNativeProcess(GnuDemanglerOptions.GNU_DEMANGLER_V2_33_1); parser = new GnuDemanglerParser(process); } @@ -41,38 +42,37 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { public void test() throws Exception { long start = System.currentTimeMillis(); - demangle(process, parser, "_ZTVN6Magick21DrawableTextAntialiasE"); - demangle(process, parser, "_ZGVZN10KDirLister11emitChangesEvE3dot");//guard variables + demangle("_ZTVN6Magick21DrawableTextAntialiasE"); + demangle("_ZGVZN10KDirLister11emitChangesEvE3dot");//guard variables - demangle(process, parser, "_ZZ18__gthread_active_pvE20__gthread_active_ptr"); + demangle("_ZZ18__gthread_active_pvE20__gthread_active_ptr"); - demangle(process, parser, "_ZNSt10_List_baseIN6Magick5VPathESaIS1_EE5clearEv"); - demangle(process, parser, "_ZTISt14unary_functionIPN9MagickLib12_DrawContextEvE"); - demangle(process, parser, "_ZTSSt14unary_functionIPN9MagickLib12_DrawContextEvE"); - demangle(process, parser, "_ZTCN4Arts17StdoutWriter_implE68_NS_11Object_skelE"); - demangle(process, parser, "_ZN6Magick5ImageD1Ev"); - demangle(process, parser, + demangle("_ZNSt10_List_baseIN6Magick5VPathESaIS1_EE5clearEv"); + demangle("_ZTISt14unary_functionIPN9MagickLib12_DrawContextEvE"); + demangle("_ZTSSt14unary_functionIPN9MagickLib12_DrawContextEvE"); + demangle("_ZTCN4Arts17StdoutWriter_implE68_NS_11Object_skelE"); + demangle("_ZN6Magick5ImageD1Ev"); + demangle( "_ZN6Magick19matteFloodfillImageC2ERKNS_5ColorEjiiN9MagickLib11PaintMethodE"); - demangle(process, parser, "_ZThn8_N14nsPrintSession6AddRefEv");// non-virtual thunk - demangle(process, parser, + demangle("_ZThn8_N14nsPrintSession6AddRefEv");// non-virtual thunk + demangle( "_ZTv0_n24_NSt19basic_ostringstreamIcSt11char_traitsIcE14pool_allocatorIcEED0Ev");// virtual thunk - demangle(process, parser, "_ZTch0_h16_NK8KHotKeys13WindowTrigger4copyEPNS_10ActionDataE");// covariant return thunk + demangle("_ZTch0_h16_NK8KHotKeys13WindowTrigger4copyEPNS_10ActionDataE");// covariant return thunk - demangle(process, parser, "_ZNK2cc14ScrollSnapTypeneERKS0_"); + demangle("_ZNK2cc14ScrollSnapTypeneERKS0_"); List list = loadTextResource(GnuDemanglerParserTest.class, "libMagick.symbols.txt"); for (String mangled : list) { if (mangled == null) { break; } - demangle(process, parser, mangled); + demangle(mangled); } System.out.println("Elapsed Time: " + (System.currentTimeMillis() - start)); } - private void demangle(GnuDemanglerNativeProcess process, GnuDemanglerParser parser, - String mangled) throws IOException { + private void demangle(String mangled) throws IOException { String demangled = process.demangle(mangled); assertNotNull(demangled); assertNotEquals(mangled, demangled); @@ -81,14 +81,12 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { } @Test - public void testParsingBug() { -// std::basic_istream >& std::operator>> >(std::basic_istream >&, char&) - -// _ZStrsIcSt11char_traitsIcEERSt13basic_istreamIT_T0_ES6_RS3_ -// _ZStrsIcSt11char_traitsIcESaIcEERSt13basic_istreamIT_T0_ES7_RSbIS4_S5_T1_E - GnuDemanglerParser parser = new GnuDemanglerParser(null); + public void testOverloadedShiftOperatorParsingBug() { + parser = new GnuDemanglerParser(null); DemangledObject object = parser.parse(null, - "std::basic_istream >& std::operator>> >(std::basic_istream >&, char&)"); + "std::basic_istream >& " + + "std::operator>> >" + + "(std::basic_istream >&, char&)"); String name = object.getName(); assertEquals("operator>>>", name); } @@ -210,6 +208,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { public void testFunctionPointers() throws Exception { String mangled = "__t6XpsMap2ZlZP14CORBA_TypeCodePFRCl_UlUlUlf"; + process = GnuDemanglerNativeProcess + .getDemanglerNativeProcess(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + String demangled = process.demangle(mangled); DemangledObject object = parser.parse(mangled, demangled); @@ -431,6 +432,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { // String mangled = "CalcPortExposedRect__13LScrollerViewCFR4Rectb"; + process = GnuDemanglerNativeProcess + .getDemanglerNativeProcess(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + String demangled = process.demangle(mangled); DemangledObject object = parser.parse(mangled, demangled); @@ -452,6 +456,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { // String mangled = "__dt__Q26MsoDAL9VertFrameFv"; + process = GnuDemanglerNativeProcess + .getDemanglerNativeProcess(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + String demangled = process.demangle(mangled); DemangledObject object = parser.parse(mangled, demangled); @@ -491,6 +498,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { // String mangled = "GetColWidths__13CDataRendererCFRA7_s"; + process = GnuDemanglerNativeProcess + .getDemanglerNativeProcess(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + String demangled = process.demangle(mangled); DemangledObject object = parser.parse(mangled, demangled); @@ -512,6 +522,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { // String mangled = "GetColWidths__13CDataRendererCFPA7_s"; + process = GnuDemanglerNativeProcess + .getDemanglerNativeProcess(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + String demangled = process.demangle(mangled); DemangledObject object = parser.parse(mangled, demangled); @@ -567,6 +580,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { // String mangled = "_gmStage2__FP12SECTION_INFOPiPA12_iiPCs"; + process = GnuDemanglerNativeProcess + .getDemanglerNativeProcess(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + String demangled = process.demangle(mangled); DemangledObject object = parser.parse(mangled, demangled); @@ -598,6 +614,9 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { // String mangled = "__ct__Q24CStr6BufferFR4CStrUl"; + process = GnuDemanglerNativeProcess + .getDemanglerNativeProcess(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + String demangled = process.demangle(mangled); DemangledObject object = parser.parse(mangled, demangled); @@ -1085,6 +1104,7 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { // Mangled: _ZN12uavcan_stm329CanDriverC1ILj64EEERA2_AT__NS_9CanRxItemE // // Demangled: uavcan_stm32::CanDriver::CanDriver<64u>(uavcan_stm32::CanRxItem (&) [2][64u]) + // String mangled = "_ZN12uavcan_stm329CanDriverC1ILj64EEERA2_AT__NS_9CanRxItemE"; @@ -1113,4 +1133,25 @@ public class GnuDemanglerParserTest extends AbstractGenericTest { DemangledObject res = demangler.demangle(mangled, true); assertNull(res); } + + @Test + public void testFunctionWithLambda_WrappingAnotherFunctionCall() throws Exception { + + // + // Mangled: _Z11wrap_360_cdIiEDTcl8wrap_360fp_Lf42c80000EEET_ + // + // Demangled: (wrap_360({parm#1}, (float)[42c80000])) wrap_360_cd(int) + // + + String mangled = "_Z11wrap_360_cdIiEDTcl8wrap_360fp_Lf42c80000EEET_"; + String demangled = process.demangle(mangled); + + DemangledObject object = parser.parse(mangled, demangled); + assertNotNull(object); + assertTrue(object instanceof DemangledFunction); + + // TODO maybe put full output in setUtilDemangled() + String signature = object.getSignature(false); + assertEquals("undefined wrap_360_cd(int)", signature); + } } diff --git a/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/gnu/GnuDemanglerTest.java b/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/gnu/GnuDemanglerTest.java index c691576962..902c64e59d 100644 --- a/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/gnu/GnuDemanglerTest.java +++ b/Ghidra/Features/GnuDemangler/src/test/java/ghidra/app/util/demangler/gnu/GnuDemanglerTest.java @@ -17,11 +17,12 @@ package ghidra.app.util.demangler.gnu; import static org.junit.Assert.*; +import java.io.IOException; + import org.junit.Before; import org.junit.Test; import generic.test.AbstractGenericTest; -import ghidra.app.cmd.label.DemanglerCmd; import ghidra.app.util.demangler.*; import ghidra.program.database.ProgramDB; import ghidra.program.model.address.Address; @@ -30,6 +31,7 @@ import ghidra.program.model.listing.CodeUnit; import ghidra.program.model.listing.Data; import ghidra.program.model.symbol.*; import ghidra.test.ToyProgramBuilder; +import ghidra.util.Msg; import ghidra.util.task.TaskMonitor; public class GnuDemanglerTest extends AbstractGenericTest { @@ -161,7 +163,10 @@ public class GnuDemanglerTest extends AbstractGenericTest { GnuDemangler demangler = new GnuDemangler(); demangler.canDemangle(program);// this perform initialization - DemangledObject result = demangler.demangle(mangled, false); + GnuDemanglerOptions options = new GnuDemanglerOptions(); + options.setDemangleOnlyKnownPatterns(false); + options.setDemanglerName(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + DemangledObject result = demangler.demangle(mangled, options); assertNotNull(result); assertEquals("undefined MyFunction::~MyFunction(void)", result.getSignature(false)); } @@ -184,10 +189,63 @@ public class GnuDemanglerTest extends AbstractGenericTest { GnuDemangler demangler = new GnuDemangler(); demangler.canDemangle(program);// this perform initialization - DemangledObject result = demangler.demangle(mangled, true); + GnuDemanglerOptions options = new GnuDemanglerOptions(); + options.setDemangleOnlyKnownPatterns(true); // do not try + options.setDemanglerName(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + DemangledObject result = demangler.demangle(mangled, options); assertNull(result); } + @Test + public void testDemangler_Format_CodeWarrior_MacOS8or9() throws DemangledException { + + // .scroll__10TTextPanelFUcsi + + String mangled = ".scroll__10TTextPanelFUcsi"; + + GnuDemangler demangler = new GnuDemangler(); + demangler.canDemangle(program);// this perform initialization + + GnuDemanglerOptions options = new GnuDemanglerOptions(); + options.setDemangleOnlyKnownPatterns(false); + options.setDemanglerName(GnuDemanglerOptions.GNU_DEMANGLER_V2_24); + DemangledObject result = demangler.demangle(mangled, options); + assertNotNull(result); + assertEquals("undefined TTextPanel::scroll(unsigned char,short,int)", + result.getSignature(false)); + } + + @Test + public void testGnuNativeProcessWithValidArguments() { + + String demanglerName = GnuDemanglerOptions.GNU_DEMANGLER_DEFAULT; + String applicationArguments = "-s auto"; + try { + GnuDemanglerNativeProcess.getDemanglerNativeProcess(demanglerName, + applicationArguments); + } + catch (IOException e) { + fail("Expected an exception when passing unknown arguments to the native demangler"); + } + } + + @Test + public void testGnuNativeProcessWithUnknownArguments() { + + String demanglerName = GnuDemanglerOptions.GNU_DEMANGLER_DEFAULT; + String applicationArguments = "-s MrBob"; + try { + GnuDemanglerNativeProcess.getDemanglerNativeProcess(demanglerName, + applicationArguments); + fail("Expected an exception when passing unknown arguments to the native demangler"); + } + catch (IOException e) { + // expected + Msg.error(this, "Test error", e); + } + + } + private Address addr(String address) { return program.getAddressFactory().getAddress(address); } 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 new file mode 100644 index 0000000000..c98ceb23ca --- /dev/null +++ b/Ghidra/Features/MicrosoftDemangler/src/main/java/ghidra/app/plugin/core/analysis/MicrosoftDemanglerAnalyzer.java @@ -0,0 +1,65 @@ +/* ### + * 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.plugin.core.analysis; + +import ghidra.app.util.demangler.*; +import ghidra.app.util.demangler.microsoft.MicrosoftDemangler; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.Options; +import ghidra.program.model.listing.Program; + +public class MicrosoftDemanglerAnalyzer extends AbstractDemanglerAnalyzer { + + private static final String NAME = "Demangler Microsoft"; + private static final String DESCRIPTION = + "After a function is created, this analyzer will attempt to demangle " + + "the name and apply datatypes to parameters."; + + private final static String OPTION_NAME_APPLY_SIGNATURE = "Apply Function Signatures"; + private static final String OPTION_DESCRIPTION_APPLY_SIGNATURE = + "Apply any recovered function signature, in addition to the function name"; + private boolean applyFunctionSignature = true; + private MicrosoftDemangler demangler = new MicrosoftDemangler(); + + public MicrosoftDemanglerAnalyzer() { + super(NAME, DESCRIPTION); + setDefaultEnablement(true); + } + + @Override + public boolean canAnalyze(Program program) { + return demangler.canDemangle(program); + } + + @Override + public void registerOptions(Options options, Program program) { + options.registerOption(OPTION_NAME_APPLY_SIGNATURE, applyFunctionSignature, null, + OPTION_DESCRIPTION_APPLY_SIGNATURE); + } + + @Override + public void optionsChanged(Options options, Program program) { + applyFunctionSignature = + options.getBoolean(OPTION_NAME_APPLY_SIGNATURE, applyFunctionSignature); + } + + @Override + protected DemangledObject doDemangle(String mangled, DemanglerOptions options, MessageLog log) + throws DemangledException { + DemangledObject demangled = demangler.demangle(mangled, options); + return demangled; + } +} 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 2f54a0498f..201dbfa52e 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 @@ -15,8 +15,6 @@ */ package ghidra.app.util.demangler.microsoft; -import java.util.regex.Pattern; - import ghidra.app.util.demangler.*; import ghidra.app.util.opinion.MSCoffLoader; import ghidra.app.util.opinion.PeLoader; @@ -30,15 +28,6 @@ import util.demangler.GenericDemangledException; */ public class MicrosoftDemangler implements Demangler { - /** - * This represents an odd symbol that looks mangled, but we don't know what to do with. It - * is of the form: - * ?BobsStuffIO@344text__@@U_text@@?W - * - * where the last character is preceded by a special character, such as ?, *, -, etc - */ - private static Pattern INVALID_TRAILING_CHARS_PATTERN = Pattern.compile(".*@@[?*`%~+/-][A-Z]"); - public MicrosoftDemangler() { } @@ -61,6 +50,19 @@ public class MicrosoftDemangler implements Demangler { } } + @Override + public DemangledObject demangle(String mangled, DemanglerOptions options) + throws DemangledException { + + try { + DemangledObject demangled = demangleMS(mangled, options.demangleOnlyKnownPatterns()); + return demangled; + } + catch (GenericDemangledException e) { + throw new DemangledException(true); + } + } + private DemangledObject demangleMS(String mangled, boolean demangleOnlyKnownPatterns) throws GenericDemangledException { if (mangled == null || mangled.length() == 0) { @@ -69,7 +71,7 @@ public class MicrosoftDemangler implements Demangler { MDMangGhidra demangler = new MDMangGhidra(); try { - demangler.demangle(mangled, demangleOnlyKnownPatterns); //not using return type here. + demangler.demangle(mangled, demangleOnlyKnownPatterns); DemangledObject object = demangler.getObject(); return object; } @@ -81,6 +83,15 @@ public class MicrosoftDemangler implements Demangler { } } +// /** +// * This represents an odd symbol that looks mangled, but we don't know what to do with. It +// * is of the form: +// * ?BobsStuffIO@344text__@@U_text@@?W +// * +// * where the last character is preceded by a special character, such as ?, *, -, etc +// */ +// private static Pattern INVALID_TRAILING_CHARS_PATTERN = Pattern.compile(".*@@[?*`%~+/-][A-Z]"); + // private boolean isMangled(String mangled) { // int atpos = mangled.indexOf("@"); // boolean isMangled = mangled.charAt(0) == '?' && atpos != -1; diff --git a/Ghidra/Features/PDB/src/main/java/pdb/LoadPdbTask.java b/Ghidra/Features/PDB/src/main/java/pdb/LoadPdbTask.java index 7d717f1d89..a27430be46 100644 --- a/Ghidra/Features/PDB/src/main/java/pdb/LoadPdbTask.java +++ b/Ghidra/Features/PDB/src/main/java/pdb/LoadPdbTask.java @@ -82,8 +82,9 @@ class LoadPdbTask extends Task { } try { - AutoAnalysisManager.getAnalysisManager(program).scheduleWorker(worker, null, true, - monitor); + AutoAnalysisManager.getAnalysisManager(program) + .scheduleWorker(worker, null, true, + monitor); } catch (InterruptedException | CancelledException e1) { // ignore @@ -117,7 +118,7 @@ class LoadPdbTask extends Task { private void analyzeSymbols(TaskMonitor monitor, MessageLog log) { - DemanglerAnalyzer demanglerAnalyzer = new DemanglerAnalyzer(); + MicrosoftDemanglerAnalyzer demanglerAnalyzer = new MicrosoftDemanglerAnalyzer(); String analyzerName = demanglerAnalyzer.getName(); Options analysisProperties = program.getOptions(Program.ANALYSIS_PROPERTIES); diff --git a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java index 21066e45ee..ef8432abfe 100644 --- a/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java +++ b/Ghidra/Framework/Generic/src/main/java/ghidra/framework/options/Options.java @@ -15,8 +15,6 @@ */ package ghidra.framework.options; -import ghidra.util.HelpLocation; - import java.awt.Color; import java.awt.Font; import java.beans.PropertyEditor; @@ -26,6 +24,8 @@ import java.util.List; import javax.swing.KeyStroke; +import ghidra.util.HelpLocation; + public interface Options { public static final char DELIMITER = '.'; public final static String DELIMITER_STRING = new String(new char[] { DELIMITER });