From 5f3de98c7938e8f71440990e00fc5ae36ccfcab0 Mon Sep 17 00:00:00 2001 From: ghizard <50744617+ghizard@users.noreply.github.com> Date: Fri, 7 Jun 2024 12:20:47 -0400 Subject: [PATCH] GP-4627 - MDMangUtils methods to get SymbolPaths --- .../src/main/java/mdemangler/MDMangUtils.java | 118 ++++++++++++++++++ .../java/mdemangler/naming/MDNestedName.java | 5 + .../mdemangler/naming/MDQualification.java | 8 +- .../java/mdemangler/naming/MDQualifier.java | 8 +- .../java/mdemangler/object/MDObjectCPP.java | 15 ++- .../mdemangler/typeinfo/MDTypeInfoParser.java | 26 ++-- .../test/java/mdemangler/MDMangUtilsTest.java | 108 ++++++++++++++++ 7 files changed, 271 insertions(+), 17 deletions(-) create mode 100644 Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/MDMangUtils.java create mode 100644 Ghidra/Features/MicrosoftDmang/src/test/java/mdemangler/MDMangUtilsTest.java diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/MDMangUtils.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/MDMangUtils.java new file mode 100644 index 00000000000..c79368bf31f --- /dev/null +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/MDMangUtils.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 mdemangler; + +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.SymbolPath; +import mdemangler.datatype.complex.MDComplexType; +import mdemangler.datatype.modifier.MDModifierType; +import mdemangler.naming.*; +import mdemangler.object.MDObjectCPP; + +/** + * Utility class for MDMang users (and perhaps internal) + */ +public class MDMangUtils { + + private MDMangUtils() { + // purposefully empty + } + + /** + * Returns SymbolPath for the demangled item + * @param parsableItem the demangled item + * @return the symbol path + */ + public static SymbolPath getSymbolPath(MDParsableItem parsableItem) { + return getSymbolPath(parsableItem, false); + } + + /** + * Returns a more simple SymbolPath for the demangled item. Any embedded object found at + * the main namespace level will have its namespace components retrieved and inserted + * appropriately in the main SymbolPath namespace. However, embedded objects that are more + * deeply placed (such as when used for a template argument) don't and shouldn't take part + * in this simplification + * @param parsableItem the demangled item + * @return the symbol path + */ + public static SymbolPath getSimpleSymbolPath(MDParsableItem parsableItem) { + return getSymbolPath(parsableItem, true); + } + + private static SymbolPath getSymbolPath(MDParsableItem parsableItem, boolean simple) { + List parts = new ArrayList<>(); + // When simple is true, we need to recurse the nested hierarchy to pull the names + // up to the main namespace level, so we set recurse = true + recurseNamespace(parts, parsableItem, simple); + SymbolPath sp = null; + for (String part : parts) { + sp = new SymbolPath(sp, part); + } + return sp; + } + + private static void recurseNamespace(List parts, MDParsableItem item, + boolean recurseNested) { + item = getReferencedType(item); + String name; + MDQualification qualification; + if (item instanceof MDComplexType complexType) { + MDQualifiedName qualName = complexType.getNamespace(); + name = qualName.getName(); + qualification = qualName.getQualification(); + } + else if (item instanceof MDObjectCPP objCpp) { + MDObjectCPP embeddedObj = objCpp.getEmbeddedObject(); + name = embeddedObj.getName(); + qualification = embeddedObj.getQualification(); + } + else { + return; + } + + List myParts = new ArrayList<>(); + // the qualification comes in reverse order... the last is nearest to namespace root + for (MDQualifier qual : qualification) { + if (qual.isNested() && recurseNested) { + MDNestedName nestedName = qual.getNested(); + MDObjectCPP nestedObjCpp = nestedName.getNestedObject(); + List nestedParts = new ArrayList<>(); + recurseNamespace(nestedParts, nestedObjCpp, recurseNested); + myParts.addAll(0, nestedParts); + } + else if (qual.isAnon()) { + myParts.add(0, qual.getAnonymousName()); + } + else { + myParts.add(0, qual.toString()); + } + } + myParts.add(name); + parts.addAll(myParts); + } + + // This method recurses + private static MDParsableItem getReferencedType(MDParsableItem item) { + if (item instanceof MDModifierType m) { + return getReferencedType(m.getReferencedType()); + } + return item; + } + +} diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDNestedName.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDNestedName.java index 291b639e097..a68bb9be146 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDNestedName.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDNestedName.java @@ -55,6 +55,11 @@ public void insert(StringBuilder builder) { public String getMangled() { return mangled; } + + public MDObjectCPP getNestedObject() { + return objectCPP; + } + } /******************************************************************************/ 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 398d5462047..d0278f79e7d 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. @@ -138,6 +138,10 @@ protected void parseInternal() throws MDException { // } } + /** + * Provides iterator of MDQualifiers, where the last iteration is the namespace root + * @return the iterator + */ @Override public Iterator iterator() { return quals.iterator(); diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDQualifier.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDQualifier.java index 636c0441c5b..5de80617b47 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDQualifier.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/naming/MDQualifier.java @@ -44,12 +44,16 @@ public boolean isNested() { return (nameNested != null); } + public boolean isAnon() { + return (nameAnonymous != null); + } + public MDNestedName getNested() { return nameNested; } - public MDReusableName getAnonymousName() { - return nameAnonymous; + public String getAnonymousName() { + return nameAnonymous.getName(); } @Override diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/object/MDObjectCPP.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/object/MDObjectCPP.java index cf262c2f956..d0505a2d77e 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/object/MDObjectCPP.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/object/MDObjectCPP.java @@ -63,7 +63,7 @@ public MDObjectCPP getEmbeddedObject() { */ public String getName() { if (hashedObjectFlag) { - return hashedObject.toString(); + return hashedObject.getName(); } return getQualifiedName().getBasicName().toString(); } @@ -267,10 +267,19 @@ protected void parseInternal() throws MDException { hashString = builder.toString(); } + /** + * Returns the name representation + * @return the name + */ + public String getName() { + // We have made up the name representation with the encompassing tick marks (similar + // to other types). Nothing is sacrosanct about this output. + return "`" + hashString + "'"; + } + @Override public void insert(StringBuilder builder) { - // We have made up the output format. Nothing is sacrosanct about this output. - builder.append("`" + hashString + "'"); + builder.append(getName()); } } diff --git a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/typeinfo/MDTypeInfoParser.java b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/typeinfo/MDTypeInfoParser.java index cc8de823bb5..1715cc43543 100644 --- a/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/typeinfo/MDTypeInfoParser.java +++ b/Ghidra/Features/MicrosoftDmang/src/main/java/mdemangler/typeinfo/MDTypeInfoParser.java @@ -149,8 +149,11 @@ public static MDTypeInfo parse(MDMang dmang, int rttiNum) throws MDException { dmang.increment(); typeInfo = new MDMemberFunctionInfo(dmang); typeInfo.setPrivate(); + // When considering code 'A' to be at index 0, then for this and other processing + // below, those that have an *even* index are *near* pointers and those with + // an *odd* index are *far* pointers typeInfo.setPointerFormat( - (code % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((code - 'A') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case 'C': case 'D': @@ -172,7 +175,7 @@ public static MDTypeInfo parse(MDMang dmang, int rttiNum) throws MDException { typeInfo = new MDVFAdjustor(dmang); typeInfo.setPrivate(); typeInfo.setPointerFormat( - (code % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((code - 'A') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case 'I': // A, B, I, J, Q, R: These might be "this adjustment" with no adjustment case 'J': @@ -180,7 +183,7 @@ public static MDTypeInfo parse(MDMang dmang, int rttiNum) throws MDException { typeInfo = new MDMemberFunctionInfo(dmang); typeInfo.setProtected(); typeInfo.setPointerFormat( - (code % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((code - 'A') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case 'K': case 'L': @@ -202,7 +205,7 @@ public static MDTypeInfo parse(MDMang dmang, int rttiNum) throws MDException { typeInfo = new MDVFAdjustor(dmang); typeInfo.setProtected(); typeInfo.setPointerFormat( - (code % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((code - 'A') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case 'Q': // A, B, I, J, Q, R: These might be "this adjustment" with no adjustment case 'R': @@ -210,7 +213,7 @@ public static MDTypeInfo parse(MDMang dmang, int rttiNum) throws MDException { typeInfo = new MDMemberFunctionInfo(dmang); typeInfo.setPublic(); typeInfo.setPointerFormat( - (code % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((code - 'A') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case 'S': case 'T': @@ -232,7 +235,7 @@ public static MDTypeInfo parse(MDMang dmang, int rttiNum) throws MDException { typeInfo = new MDVFAdjustor(dmang); typeInfo.setPublic(); typeInfo.setPointerFormat( - (code % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((code - 'A') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case 'Y': case 'Z': @@ -277,27 +280,30 @@ public static MDTypeInfo parseSpecialHandlingFunction(MDMang dmang, int rttiNum) char ch = dmang.getAndIncrement(); switch (ch) { // UINFO: (0-5) isFunction, isMember, isvtordisp (0-5) - // UINFO: val%2==0: near; val%2==0: far; + // UINFO: val%2==0: near; val%2==1: far; case '0': case '1': typeInfo = new MDVtordisp(dmang); typeInfo.setPrivate(); + // When considering code '0' to be at index 0, then for this and other processing + // below, those that have an *even* index are *near* pointers and those with + // an *odd* index are *far* pointers typeInfo.setPointerFormat( - (ch % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((ch - '0') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case '2': case '3': typeInfo = new MDVtordisp(dmang); typeInfo.setProtected(); typeInfo.setPointerFormat( - (ch % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((ch - '0') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case '4': case '5': typeInfo = new MDVtordisp(dmang); typeInfo.setPublic(); typeInfo.setPointerFormat( - (ch % 2 == 0) ? PointerFormat._NEAR : PointerFormat._NEAR); + ((ch - '0') % 2 == 0) ? PointerFormat._NEAR : PointerFormat._FAR); break; case '$': char ch2 = dmang.getAndIncrement(); diff --git a/Ghidra/Features/MicrosoftDmang/src/test/java/mdemangler/MDMangUtilsTest.java b/Ghidra/Features/MicrosoftDmang/src/test/java/mdemangler/MDMangUtilsTest.java new file mode 100644 index 00000000000..2a11f9017fb --- /dev/null +++ b/Ghidra/Features/MicrosoftDmang/src/test/java/mdemangler/MDMangUtilsTest.java @@ -0,0 +1,108 @@ +/* ### + * 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 mdemangler; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import generic.test.AbstractGenericTest; +import ghidra.app.util.SymbolPath; +import mdemangler.datatype.MDDataType; + +/** + * This class performs testing of MDMangUtils methods + */ +public class MDMangUtilsTest extends AbstractGenericTest { + + @Test + public void testWithLambdaAndSimpleConversionApplies() throws Exception { + // From record number 604770 + // We cared about the lambda because this is a situation where we need to deal + // with nested types that were causing problems for PDB + String mangled = ".?AV@?0??name0@name1@@YA?AUname2@2@Uname3@2@Uname4@2@@Z@"; + String expected = + "`struct name1::name2 __cdecl name1::name0(struct name1::name3,struct name1::name4)'::`1'::"; + String simpleExpected = "name1::name0::`1'::"; + String expectedDemangled = + "class `struct name1::name2 __cdecl name1::name0(struct name1::name3,struct name1::name4)'::`1'::"; + + MDMangGhidra demangler = new MDMangGhidra(); + MDDataType item = demangler.demangleType(mangled, true); + + String demangled = item.toString(); + SymbolPath symbolPath = MDMangUtils.getSymbolPath(item); + SymbolPath simpleSymbolPath = MDMangUtils.getSimpleSymbolPath(item); + String result = symbolPath.getPath(); + String simpleResult = simpleSymbolPath.getPath(); + + assertEquals(expected, result); + assertEquals(simpleExpected, simpleResult); + assertEquals(expectedDemangled, demangled); + } + + @Test + public void testTypeNamespaceSimpleConversionDoesNotApply1() throws Exception { + String mangled = + ".?AU?$name0@$$QEAV@?0??name1@name2@?Aname3@name4@@UEAAXVname5@4@HAEBVname6@4@@Z@@name7@name8@@"; + String expected = + "name8::name7::name0 && __ptr64>"; + // See MDMangUtils.getSimpleSymbolPath(item) javadoc to understand why expected and + // simpleExpected are the same + String simpleExpected = expected; + String expectedDemangled = + "struct name8::name7::name0 && __ptr64>"; + + MDMangGhidra demangler = new MDMangGhidra(); + MDDataType item = demangler.demangleType(mangled, true); + + String demangled = item.toString(); + SymbolPath symbolPath = MDMangUtils.getSymbolPath(item); + SymbolPath simpleSymbolPath = MDMangUtils.getSimpleSymbolPath(item); + String result = symbolPath.getPath(); + String simpleResult = simpleSymbolPath.getPath(); + + assertEquals(expected, result); + assertEquals(simpleExpected, simpleResult); + assertEquals(expectedDemangled, demangled); + } + + @Test + public void testTypeNamespaceSimpleConversionDoesNotApply2() throws Exception { + String mangled = ".?AU?$name0@$$QEAV@?0???1Aname1@name2@@UEAA@XZ@@name3@name4@@"; + String expected = + "name4::name3::name0 && __ptr64>"; + // See MDMangUtils.getSimpleSymbolPath(item) javadoc to understand why expected and + // simpleExpected are the same + String simpleExpected = expected; + String expectedDemangled = + "struct name4::name3::name0 && __ptr64>"; + + MDMangGhidra demangler = new MDMangGhidra(); + MDDataType item = demangler.demangleType(mangled, true); + + String demangled = item.toString(); + SymbolPath symbolPath = MDMangUtils.getSymbolPath(item); + SymbolPath simpleSymbolPath = MDMangUtils.getSimpleSymbolPath(item); + String result = symbolPath.getPath(); + String simpleResult = simpleSymbolPath.getPath(); + + assertEquals(expected, result); + assertEquals(simpleExpected, simpleResult); + assertEquals(expectedDemangled, demangled); + } + +}