GP-4627 - MDMangUtils methods to get SymbolPaths

This commit is contained in:
ghizard 2024-06-07 12:20:47 -04:00
parent 127f5679d1
commit 5f3de98c79
7 changed files with 271 additions and 17 deletions

View File

@ -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<String> 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<String> 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<String> 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<String> 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;
}
}

View File

@ -55,6 +55,11 @@ public class MDNestedName extends MDParsableItem {
public String getMangled() {
return mangled;
}
public MDObjectCPP getNestedObject() {
return objectCPP;
}
}
/******************************************************************************/

View File

@ -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 @@ public class MDQualification extends MDParsableItem implements Iterable<MDQualif
// }
}
/**
* Provides iterator of MDQualifiers, where the last iteration is the namespace root
* @return the iterator
*/
@Override
public Iterator<MDQualifier> iterator() {
return quals.iterator();

View File

@ -44,12 +44,16 @@ public class MDQualifier extends MDParsableItem {
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

View File

@ -63,7 +63,7 @@ public class MDObjectCPP extends MDObject {
*/
public String getName() {
if (hashedObjectFlag) {
return hashedObject.toString();
return hashedObject.getName();
}
return getQualifiedName().getBasicName().toString();
}
@ -267,10 +267,19 @@ public class MDObjectCPP extends MDObject {
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());
}
}

View File

@ -149,8 +149,11 @@ public class MDTypeInfoParser {
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 class MDTypeInfoParser {
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 class MDTypeInfoParser {
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 class MDTypeInfoParser {
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 class MDTypeInfoParser {
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 class MDTypeInfoParser {
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 class MDTypeInfoParser {
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();

View File

@ -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<lambda_0>@?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'::<lambda_0>";
String simpleExpected = "name1::name0::`1'::<lambda_0>";
String expectedDemangled =
"class `struct name1::name2 __cdecl name1::name0(struct name1::name3,struct name1::name4)'::`1'::<lambda_0>";
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<lambda_0>@?0??name1@name2@?Aname3@name4@@UEAAXVname5@4@HAEBVname6@4@@Z@@name7@name8@@";
String expected =
"name8::name7::name0<class `public: virtual void __cdecl name4::`anonymous namespace'::name2::name1(class Aname3::name5,int,class Aname3::name6 const & __ptr64) __ptr64'::`1'::<lambda_0> && __ptr64>";
// See MDMangUtils.getSimpleSymbolPath(item) javadoc to understand why expected and
// simpleExpected are the same
String simpleExpected = expected;
String expectedDemangled =
"struct name8::name7::name0<class `public: virtual void __cdecl name4::`anonymous namespace'::name2::name1(class Aname3::name5,int,class Aname3::name6 const & __ptr64) __ptr64'::`1'::<lambda_0> && __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<lambda_0>@?0???1Aname1@name2@@UEAA@XZ@@name3@name4@@";
String expected =
"name4::name3::name0<class `public: virtual __cdecl name2::Aname1::~Aname1(void) __ptr64'::`1'::<lambda_0> && __ptr64>";
// See MDMangUtils.getSimpleSymbolPath(item) javadoc to understand why expected and
// simpleExpected are the same
String simpleExpected = expected;
String expectedDemangled =
"struct name4::name3::name0<class `public: virtual __cdecl name2::Aname1::~Aname1(void) __ptr64'::`1'::<lambda_0> && __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);
}
}