GP-5039 - PDB CPP - Move redesigned but neutered VxtManager to master

This commit is contained in:
ghizard 2024-11-08 15:30:05 -05:00
parent 07d7358970
commit a8623b7202
24 changed files with 3620 additions and 1628 deletions

View File

@ -0,0 +1,107 @@
/* ###
* 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.pdb.classtype;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
//----------------------------------------------------------------------------------------------
// TODO: Consider expanding these beyond C++.
// See https://en.wikipedia.org/wiki/Access_modifiers
// These could then be:
// UNKNOWN("UNKNOWN_ACCESS ", -1),
// OPEN("open", 0),
// PUBLIC("internal", 1),
// INTERNAL("internal", 2),
// PACKAGE("package", 3),
// PROTECTED("protected", 4),
// PROTECTED_INTERNAL("protected internal", 5),
// PRIVATE_PROTECTED("private protected", 6),
// FILE("file", 7),
// FILE_PRIVATE("fileprivate", 8),
// PRIVATE("private", 9);
public enum Access {
UNKNOWN("UNKNOWN_ACCESS", -1),
BLANK("", 0), // eliminated 20230524... using defaultAccess on some methods. Could renumber
PUBLIC("public", 1),
PROTECTED("protected", 2),
PRIVATE("private", 3);
private static final Map<Integer, Access> BY_VALUE = new HashMap<>();
static {
for (Access val : values()) {
BY_VALUE.put(val.value, val);
}
}
private final String label;
private final int value;
public String getString() {
return label;
}
@Override
public String toString() {
return label;
}
public int getValue() {
return value;
}
public static Access fromValue(int val) {
return BY_VALUE.getOrDefault(val, UNKNOWN);
}
private Access(String label, int value) {
this.label = label;
this.value = value;
}
/**
* Merge two Access values, leaning toward more restrictive. UNKNOWN is only returned
* if both are UNKNOWN.
* @param other value to merge
* @return the merged value
*/
public Access mergeRestrictive(Access other) {
// No need to test for UNKNOWN as its value is on the permissive end.
if (this.value > other.value) {
return this;
}
return other;
}
/**
* Merge two Access values, leaning toward more permissive. UNKNOWN is only returned
* if both are UNKNOWN.
* @param other value to merge
* @return the merged value
*/
public Access mergePermissive(Access other) {
if (this.value < other.value) {
// Only need special test for UNKNOWN here, as its value is on the permissive end.
if (this == UNKNOWN) {
return other;
}
return this;
}
return other;
}
}

View File

@ -0,0 +1,114 @@
/* ###
* 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.pdb.classtype;
import java.util.*;
import ghidra.app.util.bin.format.pdb2.pdbreader.type.ClassFieldMsAttributes;
/**
*
*/
public class ClassFieldAttributes {
private static final Map<ClassFieldAttributes, ClassFieldAttributes> map = new HashMap<>();
// These initializations use the map above, so it must be initialized first
public static final ClassFieldAttributes UNKNOWN = get(Access.UNKNOWN, Property.UNKNOWN);
public static final ClassFieldAttributes BLANK = get(Access.BLANK, Property.BLANK);
private final Access access;
private final Property property;
public synchronized static ClassFieldAttributes get(Access access, Property property) {
ClassFieldAttributes key = new ClassFieldAttributes(access, property);
ClassFieldAttributes cfa = map.putIfAbsent(key, key);
return (cfa != null) ? cfa : key;
}
public static ClassFieldAttributes convert(ClassFieldMsAttributes msAtts,
Access defaultAccess) {
Access myAccess = switch (msAtts.getAccess()) {
case PUBLIC -> Access.PUBLIC;
case PROTECTED -> Access.PROTECTED;
case PRIVATE -> Access.PRIVATE;
case BLANK -> defaultAccess;
default -> Access.UNKNOWN;
};
Property myProperty = switch (msAtts.getProperty()) {
case VIRTUAL -> Property.VIRTUAL;
case STATIC -> Property.STATIC;
case FRIEND -> Property.FRIEND;
case BLANK -> Property.BLANK;
default -> Property.UNKNOWN;
};
return get(myAccess, myProperty);
}
private ClassFieldAttributes(Access access, Property property) {
this.access = access;
this.property = property;
}
public Access getAccess() {
return access;
}
public Property getProperty() {
return property;
}
public void emit(StringBuilder builder) {
StringBuilder myBuilder = new StringBuilder();
if (access.getValue() > Access.BLANK.getValue()) {
myBuilder.append(access);
myBuilder.append(' ');
}
if (property.getValue() > Property.BLANK.getValue()) {
myBuilder.append(property);
myBuilder.append(' ');
}
builder.append(myBuilder);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
emit(builder);
return builder.toString();
}
@Override
public int hashCode() {
return Objects.hash(access, property);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ClassFieldAttributes other = (ClassFieldAttributes) obj;
return access == other.access && property == other.property;
}
}

View File

@ -0,0 +1,31 @@
/* ###
* 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.pdb.classtype;
/**
* Unique ID of a ClassType. Not sure if there will be different implementation for definition
* vs. compiled vs. program vs. debug. Need to come to grips with this
*/
public interface ClassID extends Comparable<ClassID> {
// For compareTo() method of classes in this hierarchy (for Comparable<ClassID>)
/**
* For internal use
* @return hash of java class in ClassID hierarchy
*/
public int getClassNameHash();
}

View File

@ -0,0 +1,64 @@
/* ###
* 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.pdb.classtype;
import java.util.HashMap;
import java.util.Map;
/**
* Class keys from perspective of C++ language
*/
public enum ClassKey {
UNKNOWN("UNKNOWN_TYPE", -1),
BLANK("", 1),
CLASS("class", 2),
STRUCT("struct", 3),
UNION("union", 4);
private static final Map<Integer, ClassKey> BY_VALUE = new HashMap<>();
static {
for (ClassKey val : values()) {
BY_VALUE.put(val.value, val);
}
}
private final String label;
private final int value;
public String getString() {
return label;
}
@Override
public String toString() {
if (label.length() != 0) {
return label + " ";
}
return label;
}
public int getValue() {
return value;
}
public static ClassKey fromValue(int val) {
return BY_VALUE.getOrDefault(val, UNKNOWN);
}
private ClassKey(String label, int value) {
this.label = label;
this.value = value;
}
}

View File

@ -0,0 +1,101 @@
/* ###
* 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.pdb.classtype;
import ghidra.app.util.SymbolPath;
import ghidra.program.model.data.*;
/**
* Class Type Manager
*/
public class ClassTypeManager {
private static final String CLASS_TYPE_MANAGER_PROTOTYPE2 = "CLASS_TYPE_MANAGER_PROTOTYPE2";
private DataTypeManager dtm;
private PointerDataType defaultPtrType;
private PointerDataType defaultVbtPtr;
private PointerDataType defaultVftPtr;
/**
* Constructor
* @param dtm the data type manager
*/
public ClassTypeManager(DataTypeManager dtm) {
this.dtm = dtm;
defaultPtrType = new PointerDataType(dtm);
defaultVbtPtr = new PointerDataType(new IntegerDataType(dtm));
defaultVftPtr = new PointerDataType(new PointerDataType(dtm));
}
public SymbolPath getSymbolPath(ClassID classId) {
if (classId instanceof ProgramClassID gId) {
return gId.getSymbolPath();
}
return null;
}
/**
* Returns the underlying data type manager
* @return the data type manager
*/
public DataTypeManager getDataTypeManager() {
return dtm;
}
/**
* Returns the default pointer type
* @return the pointer type
*/
public PointerDataType getDefaultPointerType() {
return defaultPtrType;
}
/**
* Returns the default virtual base table pointer type
* @return the pointer type
*/
public PointerDataType getDefaultVbtPtr() {
return defaultVbtPtr;
}
/**
* Returns the default virtual function table pointer type
* @return the pointer type
*/
public PointerDataType getDefaultVftPtr() {
return defaultVftPtr;
}
/**
* Returns the default size of a virtual base table entry
* @return the size of the entry
*/
public int getDefaultVbtTableElementSize() {
return dtm.getDataOrganization().getIntegerSize();
}
/**
* Returns the default size of a virtual function table entry
* @return the size of the entry
*/
public int getDefaultVftTableElementSize() {
return dtm.getDataOrganization().getPointerSize();
}
}

View File

@ -0,0 +1,501 @@
/* ###
* 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.pdb.classtype;
import java.util.*;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.demangler.DemangledException;
import ghidra.app.util.demangler.DemangledObject;
import ghidra.app.util.demangler.microsoft.MicrosoftDemangler;
import ghidra.app.util.demangler.microsoft.MicrosoftMangledContext;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.CategoryPath;
import ghidra.program.model.mem.Memory;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import mdemangler.MDParsableItem;
import mdemangler.naming.MDQualification;
import mdemangler.naming.MDQualifier;
import mdemangler.object.MDObjectCPP;
import mdemangler.typeinfo.*;
/**
* This class manages MSFT-compliant virtual function tables and virtual base tables for our
* program.
* <p>
* This class also provide lookup mechanisms for locating the appropriate tables desired for
* for particular MSFT-compliant classes of the program. This is particularly useful for
* determining how classes are laid down in memory and for determining which virtual method
* gets called for a class.
* <p>
* The initial design has been shelved for now due to issues. For now, the design relies on
* a stinky solution that assumes the order of the tables in memory is that same as the order of
* the pointers to these tables in the owner class. This seems true based on limited experience.
* Hoping to revisit this soon.
*/
public class MsftVxtManager extends VxtManager {
private Memory memory;
private Map<String, Address> vxtAddressByMangled;
private Map<String, ParentageNode> parentageNodeByMangled;
// These two are for an interim solution where we believe that the order of these tables in
// memory are the same order that their pointers appear in the classes... this is based solely
// on limited experience. The solution stinks and would benefit from the original direction
// of using the parentage. We will try to use the parentage as a litmus test on retrieval
private Map<ClassID, TreeMap<Address, VBTable>> vbtsByOwner;
private Map<ClassID, TreeMap<Address, VFTable>> vftsByOwner;
// Used for locating vft and vbt
// These are explicitly used for storing/retrieving the "program" versions which result from
// locating and parsing the mangled strings for these tables. It is possible that these
// could be used for placeholder or other versions... TBD
private ParentageNode mixedRoot; // originally had separate roots for VFT and VBT
/**
* Constructor for this class
* @param ctm the class type manager
* @param memory the memory of the program
*/
public MsftVxtManager(ClassTypeManager ctm, Memory memory) {
super(ctm);
this.memory = memory;
vxtAddressByMangled = new HashMap<>();
parentageNodeByMangled = new HashMap<>();
vbtsByOwner = new HashMap<>();
vftsByOwner = new HashMap<>();
mixedRoot = new ParentageNode(null);
}
/**
* Finds the putative {@link VBTable} in memory requested for the owning class
* @param owner the owning class of the table
* @return the table
*/
public VBTable findPrimaryVbt(ClassID owner) {
TreeMap<Address, VBTable> map = vbtsByOwner.get(owner);
if (map == null) {
return null;
}
return map.firstEntry().getValue();
}
/**
* Finds the putative {@link VFTable} in memory requested for the owning class
* @param owner the owning class of the table
* @return the table
*/
public VFTable findPrimaryVft(ClassID owner) {
TreeMap<Address, VFTable> map = vftsByOwner.get(owner);
if (map == null) {
return null;
}
return map.firstEntry().getValue();
}
/**
* Finds the putative {@link VBTable} in memory requested for the owning class and the
* specified parentage
* @param owner the owning class of the table
* @param parentage the parentage for the desired table. The parentage must start with the
* parent that contains the pointer to the table and should include the ordered lineage from
* that class through all of its decendents to the owner, excluding the owner
* @return the table
*/
public VBTable findVbt(ClassID owner, List<ClassID> parentage) {
ParentageNode node = findNode(owner, parentage);
if (node == null) {
return null;
}
VBTable vbTable = node.getVBTable();
if (vbTable != null || !parentage.isEmpty()) { // see note below
return vbTable;
}
// Not 100% sure on this... needs more investigation as to why there are mangled strings
// that reference the owner in the parentage. Could there be a situation where there
// is one with the parentage and one without? We are treating them as the same for now
// unless we find counter-examples or difficulties with this.
// Above, we test of parentage.isEmpty, because this special case comes into play only
// if it was empty
node = findNode(owner, List.of(owner));
if (node == null) {
return null;
}
return node.getVBTable();
}
/**
* Finds the putative {@link VFTable} in memory requested for the owning class and the
* specified parentage
* @param owner the owning class of the table
* @param parentage the parentage for the desired table. The parentage must start with the
* parent that contains the pointer to the table and should include the ordered lineage from
* that class through all of its decendents to the owner, excluding the owner
* @return the table
*/
public VFTable findVft(ClassID owner, List<ClassID> parentage) {
ParentageNode node = findNode(owner, parentage);
if (node == null) {
return null;
}
VFTable vfTable = node.getVFTable();
if (vfTable != null || !parentage.isEmpty()) { // see note below
return vfTable;
}
// Not 100% sure on this... needs more investigation as to why there are mangled strings
// that reference the owner in the parentage. Could there be a situation where there
// is one with the parentage and one without? We are treating them as the same for now
// unless we find counter-examples or difficulties with this.
// Above, we test of parentage.isEmpty, because this special case comes into play only
// if it was empty
node = findNode(owner, List.of(owner));
if (node == null) {
return null;
}
return node.getVFTable();
}
/**
* Creates a virtual function and base tables for the {@code Map<String, Address>} of
* addresses-by-mangled names. Any failures are logged
*
* @param categoryPath the base category path used for the collection of class-related
* types
* @param addressByMangledName the map of addresses-by-mangled-names
* @param log the message log
* @param monitor the task monitor
* @throws CancelledException upon user cancellation
*/
public void createVirtualTables(CategoryPath categoryPath,
Map<String, Address> addressByMangledName, MessageLog log, TaskMonitor monitor)
throws CancelledException {
for (Map.Entry<String, Address> entry : addressByMangledName.entrySet()) {
monitor.checkCancelled();
String mangled = entry.getKey();
Address address = entry.getValue();
if (!createVirtualTable(categoryPath, mangled, address, monitor)) {
log.appendMsg("Could not create VxTable for " + mangled);
}
}
}
/**
* Creates a virtual function or base table for the mangled symbol and its associated
* memory address
*
* @param categoryPath the base category path used for the collection of class-related
* types
* @param mangled the mangled name of the type
* @param address the address associated with the mangled name
* @param monitor the task monitor
* @return {@code true} if successful
*/
public boolean createVirtualTable(CategoryPath categoryPath, String mangled, Address address,
TaskMonitor monitor) {
Address a = vxtAddressByMangled.get(mangled);
if (a != null) {
if (!a.equals(address)) {
Msg.warn(this, String.format("New address (%s) does not match existing %s for: %s",
a, address, mangled));
}
else {
Msg.warn(this,
String.format("Entry already exists: address (%s), %s", address, mangled));
}
return false;
}
vxtAddressByMangled.put(mangled, address);
DemanglerResults demanglerResults = getOwnerAndUsersDtp(categoryPath, mangled);
if (demanglerResults == null) {
Msg.warn(this, "Problem obtaining path information from mangled symbol: " + mangled);
return false;
}
OwnerAndParentage ownerAndParentage = demanglerResults.ownerAndParentage();
ClassID owner = ownerAndParentage.owner();
List<ClassID> parentage = ownerAndParentage.parentage();
ParentageNode node = parentageNodeByMangled.get(mangled);
if (node == null) {
node = getOrAddParentageNode(categoryPath, mixedRoot, demanglerResults);
if (node == null) {
return false;
}
parentageNodeByMangled.put(mangled, node);
}
switch (demanglerResults.vtType()) {
case VBT:
ProgramVirtualBaseTable prvbt = new ProgramVirtualBaseTable(owner, parentage,
memory, address, ctm.getDefaultVbtTableElementSize(), ctm, mangled);
if (node.getVBTable() != null) {
Msg.warn(this, "VBT already exists at node for " + mangled);
return false;
}
node.setVBTable(prvbt);
vbtByAddress.put(address, prvbt);
storeVbt(owner, address, prvbt); // temp solution?
break;
case VFT:
ProgramVirtualFunctionTable vft = new ProgramVirtualFunctionTable(owner, parentage,
memory, address, ctm.getDefaultVftTableElementSize(), mangled);
if (node.getVFTable() != null) {
Msg.warn(this, "VFT already exists at node for " + mangled);
return false;
}
node.setVFTable(vft);
vftByAddress.put(address, vft);
storeVft(owner, address, vft); // temp solution?
break;
default:
throw new AssertException("Unhandled VtType: " + demanglerResults.vtType());
}
return true;
}
private void storeVbt(ClassID owner, Address address, VBTable vbt) {
TreeMap<Address, VBTable> map = vbtsByOwner.get(owner);
if (map == null) {
map = new TreeMap<>();
vbtsByOwner.put(owner, map);
}
map.put(address, vbt);
}
private void storeVft(ClassID owner, Address address, VFTable vft) {
TreeMap<Address, VFTable> map = vftsByOwner.get(owner);
if (map == null) {
map = new TreeMap<>();
vftsByOwner.put(owner, map);
}
map.put(address, vft);
}
private ParentageNode findNode(ClassID owner, List<ClassID> parentage) {
if (!(owner instanceof ProgramClassID ownerGId)) {
return null;
}
SymbolPath ownerSp = ownerGId.getSymbolPath();
ParentageNode ownerNode = mixedRoot.getBranch(ownerSp.toString());
if (ownerNode == null) {
return null;
}
ParentageNode resultNode = null;
ParentageNode node = ownerNode;
for (ClassID id : parentage) {
if (!(id instanceof ProgramClassID gId)) {
return null;
}
SymbolPath sp = gId.getSymbolPath();
ParentageNode next = node.getBranch(sp.toString());
if (next != null) {
node = next;
resultNode = node;
}
// Regardless of found or not, go to next in parentage.
// Skips unnecessary peer-through parents
}
if (resultNode == null) {
return ownerNode;
}
// Need to have found at least one in parentage, but since the owner is part of our
// parentage tree, we cannot use the fact that we are still on owner (which can also
// be in our parentage list) as the flag for knowing if we found something in the
// parentage, so we have a separate found flag
return resultNode;
}
private ParentageNode getOrAddParentageNode(CategoryPath categoryPath, ParentageNode root,
DemanglerResults demanglerResults) {
ParentageNode node = root;
OwnerAndParentage ownerAndParentage = demanglerResults.ownerAndParentage();
ClassID owner = ownerAndParentage.owner(); // owner should be same as first on list
List<ClassID> parentage = ownerAndParentage.parentage();
if (!(owner instanceof ProgramClassID)) {
Msg.error(this, "ClassID error for " + ownerAndParentage);
return null;
}
ProgramClassID gId = (ProgramClassID) owner;
node = node.getOrAddBranch(gId.getSymbolPath().toString());
for (ClassID id : parentage) {
if (!(id instanceof ProgramClassID)) {
Msg.error(this, "ClassID error for " + ownerAndParentage);
return null;
}
gId = (ProgramClassID) id;
node = node.getOrAddBranch(gId.getSymbolPath().toString());
}
return node;
}
private static MDParsableItem doDemangle(String mangledString) {
MicrosoftDemangler demangler = new MicrosoftDemangler();
// Options, Program, and Address will have no bearing on what we are looking for
MicrosoftMangledContext context =
demangler.createMangledContext(mangledString, null, null, null);
try {
DemangledObject demangledObject = demangler.demangle(context);
if (demangledObject == null) {
// Couldn't demangle.
return null;
}
return demangler.getMdItem();
}
catch (DemangledException e) {
// Couldn't demangle.
return null;
}
}
private enum VtType {
VFT, VBT
}
private record OwnerAndParentage(ClassID owner, List<ClassID> parentage) {}
private record DemanglerResults(VtType vtType, OwnerAndParentage ownerAndParentage) {}
/**
* Gets the owner and users of the VxT from the mangled name
* @param categoryPath the base CategoryPath for types tree being used
* @param mangledString the mangled string to be decoded
* @return the associated complex type or null if the string couldn't be demangled
*/
private static DemanglerResults getOwnerAndUsersDtp(CategoryPath categoryPath,
String mangledString) {
MDParsableItem parsableItem = doDemangle(mangledString);
if (!(parsableItem instanceof MDObjectCPP cppItem)) {
return null;
}
MDTypeInfo typeInfo = cppItem.getTypeInfo();
if (!(typeInfo instanceof MDVxTable vxTable)) {
return null;
}
SymbolPath ownerSp = getOwnerSymbolPath(cppItem.getQualification());
List<SymbolPath> parentageSps = getParentageSymbolPaths(vxTable.getNestedQualifications());
List<ClassID> parentage = new ArrayList<>();
ClassID owner = new ProgramClassID(categoryPath, ownerSp);
for (SymbolPath sp : parentageSps) {
ClassID user = new ProgramClassID(categoryPath, sp);
parentage.add(user); // owner is the parentage if parentageSps was empty
}
OwnerAndParentage ownerAndParentage = new OwnerAndParentage(owner, parentage);
return switch (typeInfo) {
case MDVFTable f -> new DemanglerResults(VtType.VFT, ownerAndParentage);
case MDVBTable b -> new DemanglerResults(VtType.VBT, ownerAndParentage);
default -> null;
};
}
private static List<SymbolPath> getParentageSymbolPaths(List<MDQualification> qualifications) {
if (qualifications == null) {
return null;
}
List<SymbolPath> paths = new ArrayList<>();
for (MDQualification qualification : qualifications) {
SymbolPath symbolPath = getOwnerSymbolPath(qualification);
paths.add(symbolPath);
}
return paths;
}
private static SymbolPath getOwnerSymbolPath(MDQualification qualification) {
Iterator<MDQualifier> it = qualification.iterator();
if (!it.hasNext()) {
return null;
}
List<String> parts = new ArrayList<>();
do {
MDQualifier qual = it.next();
parts.add(0, qual.toString());
}
while (it.hasNext());
return new SymbolPath(parts);
}
//==============================================================================================
private static class ParentageNode {
private ParentageNode parent = null;
private Map<String, ParentageNode> branches;
private String name;
// Might want to store more than just one VXT... could store generic, pdb, program
// versions... could mix function and base too (one tree instead of two)?
private VFTable vft;
private VBTable vbt;
private ParentageNode(String name) {
this.name = name;
branches = new HashMap<>();
}
private ParentageNode getOrAddBranch(String branchName) {
ParentageNode branch = branches.get(branchName);
if (branch == null) {
branch = new ParentageNode(branchName);
branch.parent = this;
branches.put(branchName, branch);
}
return branch;
}
private ParentageNode getBranch(String branchName) {
return branches.get(branchName);
}
private void setVFTable(VFTable vftArg) {
vft = vftArg;
}
private void setVBTable(VBTable vbtArg) {
vbt = vbtArg;
}
private VFTable getVFTable() {
return vft;
}
private VBTable getVBTable() {
return vbt;
}
@SuppressWarnings("unused")
private String getName() {
return name;
}
@SuppressWarnings("unused")
private ParentageNode getParent() {
return parent;
}
}
}

View File

@ -0,0 +1,120 @@
/* ###
* IP: GHIDRA
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util.pdb.classtype;
import java.util.Objects;
import ghidra.app.util.SymbolPath;
import ghidra.program.model.data.CategoryPath;
/**
* Unique ID of a ProgramClassType. Not sure if there will be different implementation for
* definition vs. compiled vs. program vs. debug. See ClassID.
*/
public class ProgramClassID implements ClassID {
// All of the internals of this might change, but we need something to work with for now.
// It might end up being a hash/guid/long value.
// We were trying to use DataTypePath, but that doesn't work in light of conflicts, as we
// started with a DataTypePath for the type, which later got resolved to a .conflict (so
// DataTypePath changed out from underneath us).
private final SymbolPath symbolPath;
private final CategoryPath categoryPath;
static final int classNameHash = Objects.hash(ProgramClassID.class.getName());
/**
* Constructor
* @param categoryPath the category path for the claass
* @param symbolPath the symbol path for the class
*/
public ProgramClassID(CategoryPath categoryPath, SymbolPath symbolPath) {
this.categoryPath = categoryPath;
this.symbolPath = symbolPath;
}
/**
* Returns the category path
* @return the category path
*/
public CategoryPath getCategoryPath() {
return categoryPath;
}
/**
* Returns the symbol path
* @return the symbol path
*/
public SymbolPath getSymbolPath() {
return symbolPath;
}
// Might want to do something with data type ID if resolved
// long doIt(DataTypeManager dtm, DataType dt) {
// int x = DataTypeUtilities.getConflictValue(dt);
// long dataTypeID;
// dataTypeID = dtm.getID(dt);
// UniversalID uid = dt.getUniversalID();
// return dataTypeID;
// }
@Override
public int getClassNameHash() {
return classNameHash;
}
@Override
public String toString() {
return String.format("%s --- %s", categoryPath, symbolPath);
}
@Override
public int compareTo(ClassID o) {
int ret;
if (!(o instanceof ProgramClassID oID)) {
ret = getClassNameHash() - o.getClassNameHash();
if (ret != 0) {
throw new AssertionError("Logic problem with compareTo");
}
return ret;
}
ret = symbolPath.compareTo(oID.symbolPath);
if (ret != 0) {
return ret;
}
return categoryPath.compareTo(oID.categoryPath);
}
@Override
public int hashCode() {
return Objects.hash(categoryPath, symbolPath);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ProgramClassID other = (ProgramClassID) obj;
return Objects.equals(categoryPath, other.categoryPath) &&
Objects.equals(symbolPath, other.symbolPath);
}
}

View File

@ -0,0 +1,149 @@
/* ###
* 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.pdb.classtype;
import java.util.*;
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbException;
import ghidra.program.model.address.Address;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
/**
* Virtual Base Table from perspective of program with memory. Values are read from memory
*/
public class ProgramVirtualBaseTable extends VirtualBaseTable {
private Memory memory;
private Address address;
private int entrySize;
private String mangledName; // remove?
private Boolean createdFromMemory = null;
private Boolean createdFromCompiled = null;
private int numEntries = 0;
private int maxIndexSeen = -1;
private Map<Integer, VBTableEntry> entriesByIndex = new HashMap<>();
/**
* Constructor
* @param owner the class that owns the table
* @param parentage the parentage of the base class(es) of the table
* @param memory the program memory
* @param address the address of the table
* @param entrySize the size for each table entry
* @param ctm the class type manager
* @param mangledName the mangled name of the table
*/
public ProgramVirtualBaseTable(ClassID owner, List<ClassID> parentage, Memory memory,
Address address, int entrySize, ClassTypeManager ctm, String mangledName) {
super(owner, parentage);
if (entrySize != 4 && entrySize != 8) {
throw new IllegalArgumentException("Invalid size (" + entrySize + "): must be 4 or 8.");
}
this.memory = memory;
this.address = address;
this.entrySize = entrySize;
this.mangledName = mangledName;
createdFromMemory = true;
}
/**
* Returns the address of the table in program memory
* @return the address
*/
public Address getAddress() {
return address;
}
/**
* Returns the mangled name
* @return the mangled name
*/
String getMangledName() {
return mangledName;
}
/*
* For the next method below... once we determine the number of virtual bases (virtual and
* indirect virtual) for each class (from PDB or other), we can determine the number of
* entries in each VBT. For a VBT for the main class, the number is equal... if for some
* parentage, then the number can reflect the number of the parent. TODO: can VBT overlay/extend one from parent????????????????????????????????????????????
*/
/**
* TBD: need to determine table size to do this. Might want to place a symbol (diff method?).
*/
void placeTableDataType(int numEntries) {
}
int getMaxIndex() {
return maxIndexSeen;
}
@Override
public Long getBaseOffset(int index) throws PdbException {
Address entryAddress = address.add(index * entrySize);
try {
Long offset = (entrySize == 4) ? (long) memory.getInt(entryAddress)
: memory.getLong(entryAddress);
return offset;
}
catch (MemoryAccessException e) {
throw new PdbException(
"MemoryAccessException while trying to parse virtual base table entry at address: " +
entryAddress);
}
finally {
maxIndexSeen = Integer.max(maxIndexSeen, index);
}
}
@Override
public ClassID getBaseClassId(int index) throws PdbException {
VBTableEntry entry = entriesByIndex.get(index);
if (entry == null) {
throw new PdbException("No entry in Virtual Base Table for index: " + index);
}
maxIndexSeen = Integer.max(maxIndexSeen, index);
return entry.getClassId();
}
@Override
public VBTableEntry getBase(int index) throws PdbException {
VBTableEntry entry = entriesByIndex.get(index);
if (entry == null) {
throw new PdbException("No entry in Virtual Base Table for index: " + index);
}
maxIndexSeen = Integer.max(maxIndexSeen, index);
return entry;
}
// Need to decide if we want to allow this to overwrite existing entry.
public void setBaseClassId(int index, ClassID baseId) throws PdbException {
VBTableEntry entry = entriesByIndex.get(index);
if (entry != null) {
throw new PdbException(
"Entry already exists in Virtual Base Table for index: " + index);
}
entry = new VirtualBaseTableEntry(baseId);
entriesByIndex.put(index, entry);
maxIndexSeen = Integer.max(maxIndexSeen, index); // do we want this here with a "set" method?
}
}

View File

@ -0,0 +1,82 @@
/* ###
* 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.pdb.classtype;
import java.util.List;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbException;
import ghidra.program.model.address.Address;
import ghidra.program.model.mem.Memory;
/**
* Manages virtual function table lookups.
*/
public class ProgramVirtualFunctionTable extends VirtualFunctionTable {
private Memory memory;
private Address address;
private int defaultEntrySize; // Might go away, as would constructor param
private String mangledName;
/**
* Constructor
* @param owner the owner class
* @param parentage the parentage for the table
* @param memory the program memory
* @param address the address of the table in memory
* @param defaultEntrySize the default entry size
* @param mangledName the mangled name for the table
*/
public ProgramVirtualFunctionTable(ClassID owner, List<ClassID> parentage, Memory memory,
Address address, int defaultEntrySize, String mangledName) {
super(owner, parentage);
if (defaultEntrySize != 4 && defaultEntrySize != 8) {
throw new IllegalArgumentException(
"Invalid size (" + defaultEntrySize + "): must be 4 or 8.");
}
this.memory = memory;
this.address = address;
this.defaultEntrySize = defaultEntrySize;
this.mangledName = mangledName;
}
/**
* Returns the address of the table in program memory
* @return the address
*/
public Address getAddress() {
return address;
}
/**
* Returns the mangled name
* @return the mangled name
*/
String getMangledName() {
return mangledName;
}
@Override
public Address getAddress(int ordinal) throws PdbException {
throw new UnsupportedOperationException();
}
@Override
public SymbolPath getPath(int index) throws PdbException {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,62 @@
/* ###
* 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.pdb.classtype;
import java.util.HashMap;
import java.util.Map;
/**
*
*/
public enum Property {
UNKNOWN("INVALID_PROPERTY", -1),
BLANK("", 0), // means non-virtual, non-static, non-friend
VIRTUAL("virtual", 1),
STATIC("static", 2),
FRIEND("friend", 3);
// Also consider <intro>, <pure>, <intro,pure>. See MSFT.
private static final Map<Integer, Property> BY_VALUE = new HashMap<>();
static {
for (Property val : values()) {
BY_VALUE.put(val.value, val);
}
}
private final String label;
private final int value;
public String getString() {
return label;
}
@Override
public String toString() {
return label;
}
public int getValue() {
return value;
}
public static Property fromValue(int val) {
return BY_VALUE.getOrDefault(val, UNKNOWN);
}
private Property(String label, int value) {
this.label = label;
this.value = value;
}
}

View File

@ -0,0 +1,23 @@
/* ###
* 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.pdb.classtype;
/**
* Compiler-generated virtual base table
*/
public interface VBTable {
// empty for now
}

View File

@ -0,0 +1,47 @@
/* ###
* 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.pdb.classtype;
/**
* Represents an entry within a virtual base table
*/
public interface VBTableEntry {
/**
* Sets the entry offset value
* @param offset the offset
*/
public void setOffset(long offset);
/**
* Gets the entry offset value
* @return the offset value
*/
public Long getOffset();
/**
* Sets the entry class ID
* @param baseId the ID
*/
public void setClassId(ClassID baseId);
/**
* Gets the entry class ID
* @return the ID
*/
public ClassID getClassId();
}

View File

@ -0,0 +1,23 @@
/* ###
* 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.pdb.classtype;
/**
* Compiler-generated virtual function table
*/
public interface VFTable {
// empty for now
}

View File

@ -0,0 +1,111 @@
/* ###
* 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.pdb.classtype;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbException;
/**
* Abstract class for virtual base tables
*/
public abstract class VirtualBaseTable implements VBTable {
protected ClassID owner; // Does this belong here in this abstract class?
protected List<ClassID> parentage; // Not sure this belongs in this abstract class
/**
* Virtual Base Table for a base (parent) class within an owner class. The owner and parent
* class can be null if not known, but methods are offered to fill them in if/when this
* information becomes available
* @param owner class that owns this VBT (can own more than one). Can be null
* @param parentage class of parents for which this VBT is used (when "this" cast to parent).
*/
public VirtualBaseTable(ClassID owner, List<ClassID> parentage) {
this.owner = owner;
this.parentage = new ArrayList<>(parentage);
}
/**
* Returns the offset of the base class in the layout class pertaining whose entry in the
* VBTable is at the index location
* VBTable
* @param index the index in the table
* @return the offset in the layout class
* @throws PdbException if problem retrieving the offset value
*/
public abstract Long getBaseOffset(int index) throws PdbException;
/**
* Returns the ClassID of the base class in the layout class pertaining whose entry in the
* VBTable is at the index location
* @param index the index in the table
* @return the ClassID of the base class
* @throws PdbException if an entry does not exist for the index
*/
public abstract ClassID getBaseClassId(int index) throws PdbException;
/**
* Returns a {@link VBTableEntry} for the base class in the layout class pertaining whose
* entry in the VBTable is at the index location
* @param index the index in the table
* @return the ClassID of the base class
* @throws PdbException if an entry does not exist for the index
*/
public abstract VBTableEntry getBase(int index) throws PdbException;
/**
* Returns the owning class
* @return the owner
*/
public ClassID getOwner() {
return owner;
}
/**
* Returns the parentage of the table
* @return the parentage
*/
public List<ClassID> getParentage() {
return parentage;
}
/**
* Sets the owner of the table
* @param ownerArg the class to set as owner
*/
public void setOwner(ClassID ownerArg) {
owner = ownerArg;
}
/**
* Sets the parentage of the parentage for the table
* @param parentage the parentage
*/
public void setParentage(List<ClassID> parentage) {
this.parentage = parentage;
}
void emit(StringBuilder builder) {
builder.append("VBT for the following parentage within: " + owner);
builder.append("\n");
for (ClassID id : parentage) {
builder.append(" " + id);
builder.append("\n");
}
}
}

View File

@ -0,0 +1,59 @@
/* ###
* 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.pdb.classtype;
/**
* Represents the Entry for a Virtual Base Table
*/
public class VirtualBaseTableEntry implements VBTableEntry {
private Long offset;
private ClassID baseId;
// Re-evaluate which constructors and setters we need
VirtualBaseTableEntry(long offset) {
this(offset, null);
}
VirtualBaseTableEntry(ClassID baseId) {
this(null, baseId);
}
VirtualBaseTableEntry(Long offset, ClassID baseId) {
this.offset = offset;
this.baseId = baseId;
}
@Override
public void setOffset(long offset) {
this.offset = offset;
}
@Override
public Long getOffset() {
return offset;
}
@Override
public void setClassId(ClassID baseId) {
this.baseId = baseId;
}
@Override
public ClassID getClassId() {
return baseId;
}
}

View File

@ -0,0 +1,98 @@
/* ###
* 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.pdb.classtype;
import java.util.ArrayList;
import java.util.List;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbException;
import ghidra.program.model.address.Address;
public abstract class VirtualFunctionTable implements VFTable {
protected ClassID owner;
protected List<ClassID> parentage;
/**
* Virtual Function Table for a base (parent) class within an owner class. The owner and parent
* class can be null if not known, but methods are offered to fill them in if/when this
* information becomes available
* @param owner class that owns this VBT (can own more than one). Can be null
* @param parentage parentage for which this VBT is used. Can be null
*/
VirtualFunctionTable(ClassID owner, List<ClassID> parentage) {
this.owner = owner;
this.parentage = new ArrayList<>(parentage);
}
/**
* Returns the address value at the index in the table
* @param index the index
* @return the address
* @throws PdbException upon error retrieving the value
*/
public abstract Address getAddress(int index) throws PdbException;
/**
* Returns the symbol path of the function at the index in the table
* @param index the index
* @return the symbol path
* @throws PdbException upon error retrieving the value
*/
public abstract SymbolPath getPath(int index) throws PdbException;
/**
* Returns the owning class
* @return the owner
*/
public ClassID getOwner() {
return owner;
}
/**
* Returns the parentage of the table
* @return the parentage
*/
public List<ClassID> getParentage() {
return parentage;
}
/**
* Sets the owner of the table
* @param ownerArg the class to set as owner
*/
public void setOwner(ClassID ownerArg) {
owner = ownerArg;
}
/**
* Sets the parentage of the parentage for the table
* @param parentage the parentage
*/
public void setParentage(List<ClassID> parentage) {
this.parentage = parentage;
}
void emit(StringBuilder builder) {
builder.append("VBT for the following classes within: " + owner);
builder.append("\n");
for (ClassID id : parentage) {
builder.append(" " + id);
builder.append("\n");
}
}
}

View File

@ -0,0 +1,78 @@
/* ###
* 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.pdb.classtype;
import java.util.HashMap;
import java.util.Map;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.PointerDataType;
/**
* Manages virtual base table lookup for PDB classes.
*/
public class VxtManager {
protected ClassTypeManager ctm;
protected Map<Address, VirtualBaseTable> vbtByAddress;
protected Map<Address, VirtualFunctionTable> vftByAddress;
/**
* Virtual Base Table Lookup Manager
* @param ctm class type manager
*/
public VxtManager(ClassTypeManager ctm) {
this.ctm = ctm;
vbtByAddress = new HashMap<>();
vftByAddress = new HashMap<>();
}
/**
* Returns the default VBT pointer type for the program
* @return the pointer type
*/
public PointerDataType getDefaultVbtPtr() {
return ctm.getDefaultVbtPtr();
}
/**
* Returns the default VFT pointer type for the program
* @return the pointer type
*/
public PointerDataType getDefaultVftPtr() {
return ctm.getDefaultVftPtr();
}
/**
* Returns the VBT located at the address
* @param address the address
* @return the VBT or null if a table is not found
*/
public VirtualBaseTable getVbt(Address address) {
return vbtByAddress.get(address);
}
/**
* Returns the VFT located at the address
* @param address the address
* @return the VFT or null if a table is not found
*/
public VirtualFunctionTable getVft(Address address) {
return vftByAddress.get(address);
}
}

View File

@ -1,238 +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.util.pdb.pdbapplicator;
import java.util.*;
import ghidra.app.util.bin.format.pdb2.pdbreader.type.ClassFieldMsAttributes;
/**
*
*/
public class ClassFieldAttributes {
private static final Map<ClassFieldAttributes, ClassFieldAttributes> map = new HashMap<>();
// These initializations use the map above, so it must be initialized first
public static final ClassFieldAttributes UNKNOWN = get(Access.UNKNOWN, Property.UNKNOWN);
public static final ClassFieldAttributes BLANK = get(Access.BLANK, Property.BLANK);
private final Access access;
private final Property property;
public static ClassFieldAttributes get(Access access, Property property) {
ClassFieldAttributes key = new ClassFieldAttributes(access, property);
ClassFieldAttributes cfa = map.putIfAbsent(key, key);
return (cfa != null) ? cfa : key;
}
static ClassFieldAttributes convert(ClassFieldMsAttributes msAtts, Access defaultAccess) {
Access myAccess = switch (msAtts.getAccess()) {
case PUBLIC -> Access.PUBLIC;
case PROTECTED -> Access.PROTECTED;
case PRIVATE -> Access.PRIVATE;
case BLANK -> defaultAccess;
default -> Access.UNKNOWN;
};
Property myProperty = switch (msAtts.getProperty()) {
case VIRTUAL -> Property.VIRTUAL;
case STATIC -> Property.STATIC;
case FRIEND -> Property.FRIEND;
case BLANK -> Property.BLANK;
default -> Property.UNKNOWN;
};
return get(myAccess, myProperty);
}
private ClassFieldAttributes(Access access, Property property) {
this.access = access;
this.property = property;
}
Access getAccess() {
return access;
}
Property getProperty() {
return property;
}
void emit(StringBuilder builder) {
StringBuilder myBuilder = new StringBuilder();
if (access.getValue() > Access.BLANK.getValue()) {
myBuilder.append(access);
myBuilder.append(' ');
}
if (property.getValue() > Property.BLANK.getValue()) {
myBuilder.append(property);
myBuilder.append(' ');
}
builder.append(myBuilder);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
emit(builder);
return builder.toString();
}
@Override
public int hashCode() {
return Objects.hash(access, property);
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
ClassFieldAttributes other = (ClassFieldAttributes) obj;
return access == other.access && property == other.property;
}
//----------------------------------------------------------------------------------------------
// TODO: Consider expanding these beyond C++.
// See https://en.wikipedia.org/wiki/Access_modifiers
// These could then be:
// UNKNOWN("UNKNOWN_ACCESS ", -1),
// OPEN("open", 0),
// PUBLIC("internal", 1),
// INTERNAL("internal", 2),
// PACKAGE("package", 3),
// PROTECTED("protected", 4),
// PROTECTED_INTERNAL("protected internal", 5),
// PRIVATE_PROTECTED("private protected", 6),
// FILE("file", 7),
// FILE_PRIVATE("fileprivate", 8),
// PRIVATE("private", 9);
static enum Access {
UNKNOWN("UNKNOWN_ACCESS", -1),
BLANK("", 0), // eliminated 20230524... using defaultAccess on some methods. Could renumber
PUBLIC("public", 1),
PROTECTED("protected", 2),
PRIVATE("private", 3);
private static final Map<Integer, Access> BY_VALUE = new HashMap<>();
static {
for (Access val : values()) {
BY_VALUE.put(val.value, val);
}
}
private final String label;
private final int value;
public String getString() {
return label;
}
@Override
public String toString() {
return label;
}
public int getValue() {
return value;
}
public static Access fromValue(int val) {
return BY_VALUE.getOrDefault(val, UNKNOWN);
}
private Access(String label, int value) {
this.label = label;
this.value = value;
}
/**
* Merge two Access values, leaning toward more restrictive. UNKNOWN is only returned
* if both are UNKNOWN.
* @param other value to merge
* @return the merged value
*/
public Access mergeRestrictive(Access other) {
// No need to test for UNKNOWN as its value is on the permissive end.
if (this.value > other.value) {
return this;
}
return other;
}
/**
* Merge two Access values, leaning toward more permissive. UNKNOWN is only returned
* if both are UNKNOWN.
* @param other value to merge
* @return the merged value
*/
public Access mergePermissive(Access other) {
if (this.value < other.value) {
// Only need special test for UNKNOWN here, as its value is on the permissive end.
if (this == UNKNOWN) {
return other;
}
return this;
}
return other;
}
}
//----------------------------------------------------------------------------------------------
static enum Property {
UNKNOWN("INVALID_PROPERTY", -1),
BLANK("", 0), // means non-virtual, non-static, non-friend
VIRTUAL("virtual", 1),
STATIC("static", 2),
FRIEND("friend", 3);
// Also consider <intro>, <pure>, <intro,pure>. See MSFT.
private static final Map<Integer, Property> BY_VALUE = new HashMap<>();
static {
for (Property val : values()) {
BY_VALUE.put(val.value, val);
}
}
private final String label;
private final int value;
public String getString() {
return label;
}
@Override
public String toString() {
return label;
}
public int getValue() {
return value;
}
public static Property fromValue(int val) {
return BY_VALUE.getOrDefault(val, UNKNOWN);
}
private Property(String label, int value) {
this.label = label;
this.value = value;
}
}
}

View File

@ -23,7 +23,8 @@ import ghidra.app.util.SymbolPath;
import ghidra.app.util.bin.format.pdb.DefaultCompositeMember; import ghidra.app.util.bin.format.pdb.DefaultCompositeMember;
import ghidra.app.util.bin.format.pdb2.pdbreader.*; import ghidra.app.util.bin.format.pdb2.pdbreader.*;
import ghidra.app.util.bin.format.pdb2.pdbreader.type.*; import ghidra.app.util.bin.format.pdb2.pdbreader.type.*;
import ghidra.app.util.pdb.pdbapplicator.ClassFieldAttributes.Access; import ghidra.app.util.pdb.classtype.Access;
import ghidra.app.util.pdb.classtype.ClassFieldAttributes;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
@ -82,19 +83,25 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
myApplicator.predefineClass(fixedSymbolPath); myApplicator.predefineClass(fixedSymbolPath);
myComposite = new StructureDataType(categoryPath, fixedSymbolPath.getName(), size, myComposite = new StructureDataType(categoryPath, fixedSymbolPath.getName(), size,
myApplicator.getDataTypeManager()); myApplicator.getDataTypeManager());
myClassType = new CppCompositeType(fixedSymbolPath, myComposite, mangledName); myClassType =
new CppCompositeType(myApplicator.getRootPdbCategory(), fixedSymbolPath,
myComposite, mangledName);
myClassType.setClass(); myClassType.setClass();
} }
else if (compositeMsType instanceof AbstractStructureMsType) { else if (compositeMsType instanceof AbstractStructureMsType) {
myComposite = new StructureDataType(categoryPath, fixedSymbolPath.getName(), size, myComposite = new StructureDataType(categoryPath, fixedSymbolPath.getName(), size,
myApplicator.getDataTypeManager()); myApplicator.getDataTypeManager());
myClassType = new CppCompositeType(fixedSymbolPath, myComposite, mangledName); myClassType =
new CppCompositeType(myApplicator.getRootPdbCategory(), fixedSymbolPath,
myComposite, mangledName);
myClassType.setStruct(); myClassType.setStruct();
} }
else if (compositeMsType instanceof AbstractUnionMsType) { else if (compositeMsType instanceof AbstractUnionMsType) {
myComposite = new UnionDataType(categoryPath, fixedSymbolPath.getName(), myComposite = new UnionDataType(categoryPath, fixedSymbolPath.getName(),
myApplicator.getDataTypeManager()); myApplicator.getDataTypeManager());
myClassType = new CppCompositeType(fixedSymbolPath, myComposite, mangledName); myClassType =
new CppCompositeType(myApplicator.getRootPdbCategory(), fixedSymbolPath,
myComposite, mangledName);
myClassType.setUnion(); myClassType.setUnion();
} }
else { // InterfaceMsType else { // InterfaceMsType
@ -183,7 +190,7 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
// we do it here. Set breakpoint here to investigate. // we do it here. Set breakpoint here to investigate.
} }
classType.createLayout(applicator.getPdbApplicatorOptions().getCompositeLayout(), classType.createLayout(applicator.getPdbApplicatorOptions().getCompositeLayout(),
applicator.getVbtManager(), applicator.getCancelOnlyWrappingMonitor()); applicator.getVxtManager(), applicator.getCancelOnlyWrappingMonitor());
} }
//============================================================================================== //==============================================================================================
@ -224,9 +231,8 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
throws PdbException, CancelledException { throws PdbException, CancelledException {
AbstractCompositeMsType cType = (AbstractCompositeMsType) type; AbstractCompositeMsType cType = (AbstractCompositeMsType) type;
ClassFieldAttributes.Access defaultAccess = Access defaultAccess = (type instanceof AbstractClassMsType) ? Access.PRIVATE
(type instanceof AbstractClassMsType) ? ClassFieldAttributes.Access.PRIVATE : Access.PUBLIC;
: ClassFieldAttributes.Access.PUBLIC;
for (AbstractMsType baseType : msBases) { for (AbstractMsType baseType : msBases) {
applicator.checkCancelled(); applicator.checkCancelled();
@ -334,9 +340,8 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
private void addMembers(Composite composite, CppCompositeType myClassType, private void addMembers(Composite composite, CppCompositeType myClassType,
List<AbstractMemberMsType> msMembers, AbstractCompositeMsType type, List<AbstractMemberMsType> msMembers, AbstractCompositeMsType type,
List<DefaultPdbUniversalMember> myMembers) throws CancelledException, PdbException { List<DefaultPdbUniversalMember> myMembers) throws CancelledException, PdbException {
ClassFieldAttributes.Access defaultAccess = Access defaultAccess =
(type instanceof AbstractClassMsType) ? ClassFieldAttributes.Access.PRIVATE (type instanceof AbstractClassMsType) ? Access.PRIVATE : Access.PUBLIC;
: ClassFieldAttributes.Access.PUBLIC;
for (int index = 0; index < msMembers.size(); index++) { for (int index = 0; index < msMembers.size(); index++) {
applicator.checkCancelled(); applicator.checkCancelled();
AbstractMemberMsType memberType = msMembers.get(index); AbstractMemberMsType memberType = msMembers.get(index);

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -24,7 +24,7 @@ import ghidra.app.util.SymbolPath;
import ghidra.app.util.bin.format.pdb.*; import ghidra.app.util.bin.format.pdb.*;
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbException; import ghidra.app.util.bin.format.pdb2.pdbreader.PdbException;
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbLog; import ghidra.app.util.bin.format.pdb2.pdbreader.PdbLog;
import ghidra.app.util.pdb.pdbapplicator.PdbVbtManager.PdbVirtualBaseTable; import ghidra.app.util.pdb.classtype.*;
import ghidra.program.model.data.*; import ghidra.program.model.data.*;
import ghidra.util.Msg; import ghidra.util.Msg;
import ghidra.util.exception.AssertException; import ghidra.util.exception.AssertException;
@ -45,10 +45,11 @@ public class CppCompositeType {
private List<Member> layoutMembers; private List<Member> layoutMembers;
private List<Member> layoutVftPtrMembers; private List<Member> layoutVftPtrMembers;
private boolean isFinal; private boolean isFinal;
private Type type; private ClassKey classKey;
private String className; // String for now. private String className; // String for now.
private String mangledName; private String mangledName;
private int size; private int size;
private CategoryPath baseCategoryPath;
private SymbolPath symbolPath; private SymbolPath symbolPath;
private Composite composite; private Composite composite;
private CategoryPath categoryPath; private CategoryPath categoryPath;
@ -80,7 +81,8 @@ public class CppCompositeType {
private Map<Integer, PlaceholderVirtualBaseTable> placeholderVirtualBaseTables; private Map<Integer, PlaceholderVirtualBaseTable> placeholderVirtualBaseTables;
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
public CppCompositeType(SymbolPath symbolPath, Composite composite, String mangledName) { public CppCompositeType(CategoryPath baseCategoryPath, SymbolPath symbolPath,
Composite composite, String mangledName) {
Objects.requireNonNull(symbolPath, "symbolPath may not be null"); Objects.requireNonNull(symbolPath, "symbolPath may not be null");
Objects.requireNonNull(composite, "composite may not be null"); Objects.requireNonNull(composite, "composite may not be null");
syntacticBaseClasses = new ArrayList<>(); syntacticBaseClasses = new ArrayList<>();
@ -92,7 +94,8 @@ public class CppCompositeType {
layoutVftPtrMembers = new ArrayList<>(); layoutVftPtrMembers = new ArrayList<>();
isFinal = false; isFinal = false;
type = Type.UNKNOWN; classKey = ClassKey.UNKNOWN;
this.baseCategoryPath = baseCategoryPath;
this.symbolPath = symbolPath; this.symbolPath = symbolPath;
this.composite = composite; this.composite = composite;
placeholderVirtualBaseTables = new HashMap<>(); placeholderVirtualBaseTables = new HashMap<>();
@ -100,47 +103,51 @@ public class CppCompositeType {
this.mangledName = mangledName; this.mangledName = mangledName;
} }
public static CppClassType createCppClassType(SymbolPath symbolPath, Composite composite, public static CppClassType createCppClassType(CategoryPath baseCategoryPath,
String mangledName) { SymbolPath symbolPath, Composite composite, String mangledName) {
return new CppClassType(symbolPath, composite, mangledName); return new CppClassType(baseCategoryPath, symbolPath, composite, mangledName);
} }
public static CppClassType createCppClassType(SymbolPath symbolPath, Composite composite, public static CppClassType createCppClassType(CategoryPath baseCategoryPath,
String name, String mangledName, int size) { SymbolPath symbolPath, Composite composite, String name, String mangledName, int size) {
CppClassType cppType = new CppClassType(symbolPath, composite, mangledName); CppClassType cppType =
new CppClassType(baseCategoryPath, symbolPath, composite, mangledName);
cppType.setName(name); cppType.setName(name);
cppType.setSize(size); cppType.setSize(size);
return cppType; return cppType;
} }
public static CppStructType createCppStructType(SymbolPath symbolPath, Composite composite, public static CppStructType createCppStructType(CategoryPath baseCategoryPath,
String mangledName) { SymbolPath symbolPath, Composite composite, String mangledName) {
return new CppStructType(symbolPath, composite, mangledName); return new CppStructType(baseCategoryPath, symbolPath, composite, mangledName);
} }
public static CppStructType createCppStructType(SymbolPath symbolPath, Composite composite, public static CppStructType createCppStructType(CategoryPath baseCategoryPath,
String name, String mangledName, int size) { SymbolPath symbolPath, Composite composite, String name, String mangledName, int size) {
CppStructType cppType = new CppStructType(symbolPath, composite, mangledName); CppStructType cppType =
new CppStructType(baseCategoryPath, symbolPath, composite, mangledName);
cppType.setName(name); cppType.setName(name);
cppType.setSize(size); cppType.setSize(size);
return cppType; return cppType;
} }
private static class CppClassType extends CppCompositeType { private static class CppClassType extends CppCompositeType {
private CppClassType(SymbolPath symbolPath, Composite composite, String mangledName) { private CppClassType(CategoryPath baseCategoryPath, SymbolPath symbolPath,
super(symbolPath, composite, mangledName); Composite composite, String mangledName) {
super(baseCategoryPath, symbolPath, composite, mangledName);
setClass(); setClass();
} }
} }
private static class CppStructType extends CppCompositeType { private static class CppStructType extends CppCompositeType {
private CppStructType(SymbolPath symbolPath, Composite composite, String mangledName) { private CppStructType(CategoryPath baseCategoryPath, SymbolPath symbolPath,
super(symbolPath, composite, mangledName); Composite composite, String mangledName) {
super(composite.getCategoryPath(), symbolPath, composite, mangledName);
setStruct(); setStruct();
} }
} }
static boolean validateMangledCompositeName(String mangledCompositeTypeName, Type type) { static boolean validateMangledCompositeName(String mangledCompositeTypeName, ClassKey type) {
if (mangledCompositeTypeName == null) { if (mangledCompositeTypeName == null) {
return false; return false;
} }
@ -155,17 +162,20 @@ public class CppCompositeType {
} }
switch (mangledCompositeTypeName.charAt(3)) { switch (mangledCompositeTypeName.charAt(3)) {
case 'T': case 'T':
if ((type.compareTo(Type.UNION) != 0) && (type.compareTo(Type.UNKNOWN) != 0)) { if ((type.compareTo(ClassKey.UNION) != 0) &&
(type.compareTo(ClassKey.UNKNOWN) != 0)) {
PdbLog.message("Warning: Mismatched complex type 'T' for " + type); PdbLog.message("Warning: Mismatched complex type 'T' for " + type);
} }
break; break;
case 'U': case 'U':
if ((type.compareTo(Type.STRUCT) != 0) && (type.compareTo(Type.UNKNOWN) != 0)) { if ((type.compareTo(ClassKey.STRUCT) != 0) &&
(type.compareTo(ClassKey.UNKNOWN) != 0)) {
PdbLog.message("Warning: Mismatched complex type 'U' for " + type); PdbLog.message("Warning: Mismatched complex type 'U' for " + type);
} }
break; break;
case 'V': case 'V':
if ((type.compareTo(Type.CLASS) != 0) && (type.compareTo(Type.UNKNOWN) != 0)) { if ((type.compareTo(ClassKey.CLASS) != 0) &&
(type.compareTo(ClassKey.UNKNOWN) != 0)) {
PdbLog.message("Warning: Mismatched complex type 'V' for " + type); PdbLog.message("Warning: Mismatched complex type 'V' for " + type);
} }
break; break;
@ -200,7 +210,7 @@ public class CppCompositeType {
return composite; return composite;
} }
private CategoryPath getCategoryPath() { public CategoryPath getCategoryPath() {
return categoryPath; return categoryPath;
} }
@ -213,20 +223,20 @@ public class CppCompositeType {
} }
public void setClass() { public void setClass() {
type = Type.CLASS; classKey = ClassKey.CLASS;
} }
public void setStruct() { public void setStruct() {
type = Type.STRUCT; classKey = ClassKey.STRUCT;
} }
public void setUnion() { public void setUnion() {
type = Type.UNION; classKey = ClassKey.UNION;
} }
// not sure if user can see Type when returned. // not sure if user can see Type when returned.
public Type getType() { public ClassKey getType() {
return type; return classKey;
} }
public void setName(String className) { public void setName(String className) {
@ -237,6 +247,10 @@ public class CppCompositeType {
return className; return className;
} }
public DataTypePath getDataTypePath() {
return composite.getDataTypePath();
}
public void setMangledName(String mangledName) { public void setMangledName(String mangledName) {
this.mangledName = mangledName; this.mangledName = mangledName;
} }
@ -563,7 +577,7 @@ public class CppCompositeType {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append(type); builder.append(classKey);
builder.append(className); builder.append(className);
if (isFinal) { if (isFinal) {
builder.append(" final"); builder.append(" final");
@ -615,7 +629,7 @@ public class CppCompositeType {
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
public void createLayoutFromSyntacticDescription(VbtManager vbtManager, TaskMonitor monitor) { public void createLayoutFromSyntacticDescription(VxtManager vxtManager, TaskMonitor monitor) {
for (SyntacticBaseClass base : syntacticBaseClasses) { for (SyntacticBaseClass base : syntacticBaseClasses) {
if (base instanceof DirectSyntacticBaseClass) { if (base instanceof DirectSyntacticBaseClass) {
@ -628,21 +642,21 @@ public class CppCompositeType {
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
public void createLayout(ObjectOrientedClassLayout layoutOptions, VbtManager vbtManager, public void createLayout(ObjectOrientedClassLayout layoutOptions, VxtManager vxtManager,
TaskMonitor monitor) throws PdbException, CancelledException { TaskMonitor monitor) throws PdbException, CancelledException {
if (vbtManager instanceof PdbVbtManager) { // Information from PDB/program symbols if (vxtManager instanceof MsftVxtManager) { // Information from PDB/program symbols
// TODO: both same for now // TODO: both same for now
//doSpeculativeLayout(vbtManager, monitor); //doSpeculativeLayout(vxtManager, monitor);
createVbtBasedLayout(layoutOptions, vbtManager, monitor); createVbtBasedLayout(layoutOptions, vxtManager, monitor);
} }
else { else {
createSpeculativeLayout(layoutOptions, vbtManager, monitor); createSpeculativeLayout(layoutOptions, vxtManager, monitor);
} }
} }
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
public void createVbtBasedLayout(ObjectOrientedClassLayout layoutOptions, VbtManager vbtManager, public void createVbtBasedLayout(ObjectOrientedClassLayout layoutOptions, VxtManager vxtManager,
TaskMonitor monitor) throws PdbException, CancelledException { TaskMonitor monitor) throws PdbException, CancelledException {
CategoryPath cn; CategoryPath cn;
hasDirect = false; hasDirect = false;
@ -667,7 +681,7 @@ public class CppCompositeType {
// TODO: consider moving down below next line. // TODO: consider moving down below next line.
boolean allVbtFound = boolean allVbtFound =
reconcileVirtualBaseTables(composite.getDataTypeManager(), vbtManager); reconcileVirtualBaseTables(composite.getDataTypeManager(), vxtManager);
addLayoutPdbMembers(directClassPdbMembers, layoutMembers); addLayoutPdbMembers(directClassPdbMembers, layoutMembers);
insertVirtualFunctionTablePointers(directClassPdbMembers); insertVirtualFunctionTablePointers(directClassPdbMembers);
@ -772,7 +786,7 @@ public class CppCompositeType {
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
public void createSpeculativeLayout(ObjectOrientedClassLayout layoutOptions, public void createSpeculativeLayout(ObjectOrientedClassLayout layoutOptions,
VbtManager vbtManager, TaskMonitor monitor) throws PdbException, CancelledException { VxtManager vxtManager, TaskMonitor monitor) throws PdbException, CancelledException {
// Speculative Layout uses recursion to try to know the order of members. However, MSFT // Speculative Layout uses recursion to try to know the order of members. However, MSFT
// rearranges the order of the Base Class records such that they are not necessarily in // rearranges the order of the Base Class records such that they are not necessarily in
// the order that the class was declared, and it seems that the member order follows the // the order that the class was declared, and it seems that the member order follows the
@ -807,7 +821,7 @@ public class CppCompositeType {
// TODO: consider moving down below next line. // TODO: consider moving down below next line.
boolean allVbtFound = boolean allVbtFound =
reconcileVirtualBaseTables(composite.getDataTypeManager(), vbtManager); reconcileVirtualBaseTables(composite.getDataTypeManager(), vxtManager);
addLayoutPdbMembers(directClassPdbMembers, layoutMembers); addLayoutPdbMembers(directClassPdbMembers, layoutMembers);
insertVirtualFunctionTablePointers(directClassPdbMembers); insertVirtualFunctionTablePointers(directClassPdbMembers);
@ -943,7 +957,7 @@ public class CppCompositeType {
return placeholderVirtualBaseTables; return placeholderVirtualBaseTables;
} }
private boolean reconcileVirtualBaseTables(DataTypeManager dtm, VbtManager vbtManager) private boolean reconcileVirtualBaseTables(DataTypeManager dtm, VxtManager vxtManager)
throws PdbException { throws PdbException {
if (placeholderVirtualBaseTables.size() > 1) { if (placeholderVirtualBaseTables.size() > 1) {
// study this. // study this.
@ -957,14 +971,14 @@ public class CppCompositeType {
if (!table.validateOffset()) { if (!table.validateOffset()) {
// TODO study this. // TODO study this.
} }
DataType vbptr = getVbptrDataType(dtm, vbtManager, table); DataType vbptr = getVbptrDataType(dtm, vxtManager, table);
allVbtFound &= allVbtFound &=
addOrUpdateVbtAndVbtptrMember(vbtManager, table, vbptr, vbtptrOffset, getName()); addOrUpdateVbtAndVbtptrMember(vxtManager, table, vbptr, vbtptrOffset, getName());
} }
return allVbtFound; return allVbtFound;
} }
private DataType getVbptrDataType(DataTypeManager dtm, VbtManager vbtManager, private DataType getVbptrDataType(DataTypeManager dtm, VxtManager vxtManager,
PlaceholderVirtualBaseTable table) { PlaceholderVirtualBaseTable table) {
DataType vbptr = null; DataType vbptr = null;
for (int index = 1; index < table.getMaxOffset(); index++) { for (int index = 1; index < table.getMaxOffset(); index++) {
@ -975,106 +989,67 @@ public class CppCompositeType {
} }
} }
if (vbptr == null) { if (vbptr == null) {
vbptr = vbtManager.getFallbackVbptr(); vbptr = vxtManager.getDefaultVbtPtr();
} }
return vbptr; return vbptr;
} }
private class CppCompositeAndMember { private record CppParentageAndMember(List<CppCompositeType> parentage, Member member) {}
private CppCompositeType cppType;
private Member member;
private CppCompositeAndMember(CppCompositeType cppType, Member member) { private boolean addOrUpdateVbtAndVbtptrMember(VxtManager vxtManager,
this.cppType = cppType;
this.member = member;
}
private CppCompositeType getComposite() {
return cppType;
}
private Member getMember() {
return member;
}
}
private boolean addOrUpdateVbtAndVbtptrMember(VbtManager vbtManager,
PlaceholderVirtualBaseTable table, DataType vbptr, int vbtptrOffset, String myClass) PlaceholderVirtualBaseTable table, DataType vbptr, int vbtptrOffset, String myClass)
throws PdbException { throws PdbException {
List<String> subMangled = new ArrayList<>(); // TODO: if we want to match vbtables with the pointers in *virtual* base classes, we are
//subMangled.add(getMangledName()); // not currently doing the work... we are only looking for direct bases, as they are
CppCompositeAndMember cAndM = findDirectBaseCompositeAndMember(this, 0, vbtptrOffset); // what dictate the placement for our current class (though cross-checks could be done
if (cAndM == null) { // with all vbtables (at some point))
List<CppCompositeType> parentage = new ArrayList<>();
CppParentageAndMember cAndP = findDirectBaseParentageAndMember(this, 0, vbtptrOffset);
if (cAndP == null) {
insertMember("{vbptr}", vbptr, false, vbtptrOffset, "{vbptr} for " + myClass); insertMember("{vbptr}", vbptr, false, vbtptrOffset, "{vbptr} for " + myClass);
} }
else if (!"{vbptr}".equals(cAndM.getMember().getName())) { else if (!"{vbptr}".equals(cAndP.member().getName())) {
String message = "PDB: Collision of non-{vbptr}."; String message = "PDB: Collision of non-{vbptr}.";
PdbLog.message(message); PdbLog.message(message);
Msg.info(this, message); Msg.info(this, message);
return false; return false;
} }
else { else {
CppCompositeType compositeThatContainsMember = cAndM.getComposite(); parentage = cAndP.parentage();
String mangled = compositeThatContainsMember.getMangledName();
subMangled.add(mangled);
} }
if (!(vbtManager instanceof PdbVbtManager)) { if (!(vxtManager instanceof MsftVxtManager mvxtManager)) {
return false; return false;
} }
int entrySize = 4; // Default to something (could be wrong) int entrySize = 4; // Default to something (could be wrong)
if (vbptr instanceof PointerDataType) { if (vbptr instanceof PointerDataType) {
entrySize = ((PointerDataType) vbptr).getDataType().getLength(); entrySize = ((PointerDataType) vbptr).getDataType().getLength();
} }
boolean x = findVbt(table, mvxtManager, entrySize, symbolPath, parentage);
return findVbtBySymbolConstruction(table, (PdbVbtManager) vbtManager, entrySize, return x;
getMangledName(), type, subMangled);
} }
private boolean findVbtBySymbolConstruction(PlaceholderVirtualBaseTable table, private boolean findVbt(PlaceholderVirtualBaseTable table, MsftVxtManager mvbtm, int entrySize,
PdbVbtManager vbtm, int entrySize, String mangledCompositeTypeName, Type mainType, SymbolPath ownerSp, List<CppCompositeType> parentage) {
List<String> subMangledCompositeTypeNames) {
if (!validateMangledCompositeName(mangledCompositeTypeName, mainType)) {
return false;
}
for (String mangled : subMangledCompositeTypeNames) {
if (!validateMangledCompositeName(mangled, Type.UNKNOWN)) {
return false;
}
}
StringBuilder builder = new StringBuilder();
builder.append("??_8");
builder.append(mangledCompositeTypeName.substring(4));
builder.append("7B"); // Hope will always be 'B' ("const")
builder.append("@");
String possibleName = builder.toString();
if (findAndUpdate(table, vbtm, entrySize, possibleName)) {
return true;
}
for (String mangled : subMangledCompositeTypeNames) {
builder.deleteCharAt(builder.length() - 1);
builder.append(mangled.substring(4));
builder.append("@");
possibleName = builder.toString();
if (findAndUpdate(table, vbtm, entrySize, possibleName)) {
return true;
}
}
return false;
}
boolean findAndUpdate(PlaceholderVirtualBaseTable table, PdbVbtManager vbtm, int entrySize, ClassID mId = new ProgramClassID(baseCategoryPath, ownerSp);
String mangledTableName) { List<ClassID> cIdParentage = new ArrayList<>();
PdbVirtualBaseTable vbt = vbtm.createVirtualBaseTableByName(mangledTableName, entrySize); for (CppCompositeType t : parentage) {
if (vbt == null) { ClassID id = new ProgramClassID(t.baseCategoryPath, t.getSymbolPath());
return false; cIdParentage.add(id);
} }
table.setName(mangledTableName); ProgramVirtualBaseTable vbt = (ProgramVirtualBaseTable) mvbtm.findPrimaryVbt(mId);
//ProgramVirtualBaseTable vbt = (ProgramVirtualBaseTable) mvbtm.findVbt(mId, cIdParentage);
table.setVirtualBaseTable(vbt); table.setVirtualBaseTable(vbt);
return true;
return vbt != null;
} }
private CppCompositeAndMember findDirectBaseCompositeAndMember(CppCompositeType cppType, private CppParentageAndMember findDirectBaseParentageAndMember(CppCompositeType cppType,
int offsetCppType, int vbtptrOffset) throws PdbException { int offsetCppType, int vbtptrOffset) throws PdbException {
for (LayoutBaseClass base : cppType.layoutBaseClasses) { for (LayoutBaseClass base : cppType.layoutBaseClasses) {
if (!(base instanceof DirectLayoutBaseClass)) { if (!(base instanceof DirectLayoutBaseClass)) {
@ -1086,16 +1061,18 @@ public class CppCompositeType {
if (vbtptrOffset >= directBaseOffset && if (vbtptrOffset >= directBaseOffset &&
vbtptrOffset < directBaseOffset + directBaseLength) { vbtptrOffset < directBaseOffset + directBaseLength) {
CppCompositeType childCppType = directBase.getBaseClassType(); CppCompositeType childCppType = directBase.getBaseClassType();
CppCompositeAndMember cAndM = CppParentageAndMember cAndP =
findDirectBaseCompositeAndMember(childCppType, directBaseOffset, vbtptrOffset); findDirectBaseParentageAndMember(childCppType, directBaseOffset, vbtptrOffset);
if (cAndM == null) { if (cAndP == null) {
Member member = childCppType.findLayoutMemberOrVftPtrMember(vbtptrOffset); Member member = childCppType.findLayoutMemberOrVftPtrMember(vbtptrOffset);
if (member == null) { if (member == null) {
return null; return null;
} }
cAndM = new CppCompositeAndMember(childCppType, member); cAndP = new CppParentageAndMember(new ArrayList<>(), member);
} }
return cAndM; List<CppCompositeType> parentage = cAndP.parentage();
parentage.add(childCppType);
return cAndP;
} }
} }
return null; return null;
@ -1674,7 +1651,7 @@ public class CppCompositeType {
//---------------------------------------------------------------------------------------------- //----------------------------------------------------------------------------------------------
static class PlaceholderVirtualBaseTable { static class PlaceholderVirtualBaseTable {
private String name; private String name;
private PdbVirtualBaseTable pdbVirtualBaseTable = null; private ProgramVirtualBaseTable vbt = null;
// We do not know if every index will be given. We can check after the fact, and once // We do not know if every index will be given. We can check after the fact, and once
// the set of sequential integers is assured, we could create a list. // the set of sequential integers is assured, we could create a list.
@ -1697,19 +1674,19 @@ public class CppCompositeType {
this.name = name; this.name = name;
} }
void setVirtualBaseTable(PdbVirtualBaseTable pdbVirtualBaseTable) { void setVirtualBaseTable(ProgramVirtualBaseTable vbt) {
this.pdbVirtualBaseTable = pdbVirtualBaseTable; this.vbt = vbt;
} }
boolean canLookupOffset() { boolean canLookupOffset() {
return pdbVirtualBaseTable != null; return vbt != null;
} }
long getOffset(int ordinal) throws PdbException { long getOffset(int ordinal) throws PdbException {
if (pdbVirtualBaseTable == null) { if (vbt != null) {
throw new PdbException("pdbVirtualBaseTable not initialized"); return vbt.getBaseOffset(ordinal);
} }
return pdbVirtualBaseTable.getOffset(ordinal); throw new PdbException("pdbVirtualBaseTable not initialized");
} }
Map<Integer, PlaceholderVirtualBaseTableEntry> getEntries() { Map<Integer, PlaceholderVirtualBaseTableEntry> getEntries() {
@ -1751,50 +1728,4 @@ public class CppCompositeType {
} }
} }
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
//----------------------------------------------------------------------------------------------
static enum Type {
UNKNOWN("UNKNOWN_TYPE", -1),
BLANK("", 1),
CLASS("class", 2),
STRUCT("struct", 3),
UNION("union", 4);
private static final Map<Integer, Type> BY_VALUE = new HashMap<>();
static {
for (Type val : values()) {
BY_VALUE.put(val.value, val);
}
}
private final String label;
private final int value;
public String getString() {
return label;
}
@Override
public String toString() {
if (label.length() != 0) {
return label + " ";
}
return label;
}
public int getValue() {
return value;
}
public static Type fromValue(int val) {
return BY_VALUE.getOrDefault(val, UNKNOWN);
}
private Type(String label, int value) {
this.label = label;
this.value = value;
}
}
} }

View File

@ -36,6 +36,7 @@ import ghidra.app.util.bin.format.pdb2.pdbreader.type.PrimitiveMsType;
import ghidra.app.util.bin.format.pe.cli.tables.CliAbstractTableRow; import ghidra.app.util.bin.format.pe.cli.tables.CliAbstractTableRow;
import ghidra.app.util.importer.MessageLog; import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.pdb.PdbCategories; import ghidra.app.util.pdb.PdbCategories;
import ghidra.app.util.pdb.classtype.*;
import ghidra.framework.options.Options; import ghidra.framework.options.Options;
import ghidra.program.database.data.DataTypeUtilities; import ghidra.program.database.data.DataTypeUtilities;
import ghidra.program.disassemble.DisassemblerContextImpl; import ghidra.program.disassemble.DisassemblerContextImpl;
@ -190,6 +191,7 @@ public class DefaultPdbApplicator implements PdbApplicator {
private Address imageBase; private Address imageBase;
private int linkerModuleNumber = -1; private int linkerModuleNumber = -1;
private DataTypeManager dataTypeManager; private DataTypeManager dataTypeManager;
private ClassTypeManager classTypeManager;
private PdbAddressManager pdbAddressManager; private PdbAddressManager pdbAddressManager;
private List<SymbolGroup> symbolGroups; private List<SymbolGroup> symbolGroups;
@ -204,14 +206,14 @@ public class DefaultPdbApplicator implements PdbApplicator {
//============================================================================================== //==============================================================================================
// If we have symbols and memory with VBTs in them, then a better VbtManager is created. // If we have symbols and memory with VBTs in them, then a better VbtManager is created.
VbtManager vbtManager; private VxtManager vxtManager;
PdbRegisterNameToProgramRegisterMapper registerNameToRegisterMapper; private PdbRegisterNameToProgramRegisterMapper registerNameToRegisterMapper;
//============================================================================================== //==============================================================================================
private MultiphaseDataTypeResolver multiphaseResolver; private MultiphaseDataTypeResolver multiphaseResolver;
private int resolveCount; private int resolveCount;
private int conflictCount; private int conflictCount;
private PdbCategories categoryUtils; private PdbCategories pdbCategories;
private PdbPrimitiveTypeApplicator pdbPrimitiveTypeApplicator; private PdbPrimitiveTypeApplicator pdbPrimitiveTypeApplicator;
private TypeApplierFactory typeApplierParser; private TypeApplierFactory typeApplierParser;
// We may need to put the following map into the "analysis state" for access by // We may need to put the following map into the "analysis state" for access by
@ -589,6 +591,9 @@ public class DefaultPdbApplicator implements PdbApplicator {
pdbPeHeaderInfoManager = new PdbPeHeaderInfoManager(this); pdbPeHeaderInfoManager = new PdbPeHeaderInfoManager(this);
multiphaseResolver = new MultiphaseDataTypeResolver(this); multiphaseResolver = new MultiphaseDataTypeResolver(this);
classTypeManager = new ClassTypeManager(dataTypeManager);
pdbPrimitiveTypeApplicator = new PdbPrimitiveTypeApplicator(dataTypeManager); pdbPrimitiveTypeApplicator = new PdbPrimitiveTypeApplicator(dataTypeManager);
typeApplierParser = new TypeApplierFactory(this); typeApplierParser = new TypeApplierFactory(this);
@ -627,17 +632,21 @@ public class DefaultPdbApplicator implements PdbApplicator {
if (!pdbAddressManager.isInitialized()) { if (!pdbAddressManager.isInitialized()) {
pdbAddressManager.initialize(this, imageBase); pdbAddressManager.initialize(this, imageBase);
} }
categoryUtils = setPdbCatogoryUtils(pdb.getFilename()); pdbCategories = setPdbCatogoryUtils(pdb.getFilename());
symbolGroups = createSymbolGroups(); symbolGroups = createSymbolGroups();
linkerModuleNumber = findLinkerModuleNumber(); linkerModuleNumber = findLinkerModuleNumber();
if (program != null) { if (program != null) {
// Currently, this must happen after symbolGroups are created. // Currently, this must happen after symbolGroups are created.
PdbVbtManager pdbVbtManager = new PdbVbtManager(this); MsftVxtManager msftVxtManager =
vbtManager = pdbVbtManager; new MsftVxtManager(getClassTypeManager(), program.getMemory());
msftVxtManager.createVirtualTables(getRootPdbCategory(), findVirtualTableSymbols(), log,
monitor);
vxtManager = msftVxtManager;
registerNameToRegisterMapper = new PdbRegisterNameToProgramRegisterMapper(program); registerNameToRegisterMapper = new PdbRegisterNameToProgramRegisterMapper(program);
} }
else { else {
vbtManager = new VbtManager(getDataTypeManager()); vxtManager = new VxtManager(getClassTypeManager());
} }
preWorkDone = true; preWorkDone = true;
} }
@ -689,6 +698,47 @@ public class DefaultPdbApplicator implements PdbApplicator {
return mySymbolGroups; return mySymbolGroups;
} }
private Map<String, Address> findVirtualTableSymbols() throws CancelledException, PdbException {
Map<String, Address> myAddressByVxtMangledName = new HashMap<>();
PdbDebugInfo debugInfo = pdb.getDebugInfo();
if (debugInfo == null) {
return myAddressByVxtMangledName;
}
SymbolGroup symbolGroup = getSymbolGroup();
if (symbolGroup == null) {
return myAddressByVxtMangledName;
}
PublicSymbolInformation publicSymbolInformation = debugInfo.getPublicSymbolInformation();
List<Long> offsets = publicSymbolInformation.getModifiedHashRecordSymbolOffsets();
monitor.setMessage("PDB: Searching for VxT symbols...");
monitor.initialize(offsets.size());
MsSymbolIterator iter = symbolGroup.getSymbolIterator();
for (long offset : offsets) {
monitor.checkCancelled();
iter.initGetByOffset(offset);
if (!iter.hasNext()) {
break;
}
AbstractMsSymbol symbol = iter.peek();
if (symbol instanceof AbstractPublicMsSymbol pubSymbol) {
String name = pubSymbol.getName();
if (name.startsWith("??_7") || name.startsWith("??_8")) {
Address address = getAddress(pubSymbol);
if (!isInvalidAddress(address, name)) {
myAddressByVxtMangledName.put(name, address);
}
}
}
monitor.incrementProgress(1);
}
return myAddressByVxtMangledName;
}
//============================================================================================== //==============================================================================================
// Basic utility methods. // Basic utility methods.
//============================================================================================== //==============================================================================================
@ -807,6 +857,10 @@ public class DefaultPdbApplicator implements PdbApplicator {
return dataTypeManager; return dataTypeManager;
} }
ClassTypeManager getClassTypeManager() {
return classTypeManager;
}
// for PdbTypeApplicator (new) // for PdbTypeApplicator (new)
DataOrganization getDataOrganization() { DataOrganization getDataOrganization() {
return dataTypeManager.getDataOrganization(); return dataTypeManager.getDataOrganization();
@ -819,6 +873,14 @@ public class DefaultPdbApplicator implements PdbApplicator {
//============================================================================================== //==============================================================================================
// CategoryPath-related methods. // CategoryPath-related methods.
//============================================================================================== //==============================================================================================
/**
* Get root CategoryPath for the PDB
* @return the root CategoryPath
*/
CategoryPath getRootPdbCategory() {
return pdbCategories.getRootCategoryPath();
}
/** /**
* Get the {@link CategoryPath} associated with the {@link SymbolPath} specified, rooting * Get the {@link CategoryPath} associated with the {@link SymbolPath} specified, rooting
* it either at the PDB Category * it either at the PDB Category
@ -827,7 +889,7 @@ public class DefaultPdbApplicator implements PdbApplicator {
* @return {@link CategoryPath} created for the input * @return {@link CategoryPath} created for the input
*/ */
CategoryPath getCategory(SymbolPath symbolPath) { CategoryPath getCategory(SymbolPath symbolPath) {
return categoryUtils.getCategory(symbolPath); return pdbCategories.getCategory(symbolPath);
} }
/** /**
@ -839,7 +901,7 @@ public class DefaultPdbApplicator implements PdbApplicator {
* @return the CategoryPath * @return the CategoryPath
*/ */
CategoryPath getTypedefsCategory(int moduleNumber, SymbolPath symbolPath) { CategoryPath getTypedefsCategory(int moduleNumber, SymbolPath symbolPath) {
return categoryUtils.getTypedefsCategory(moduleNumber, symbolPath); return pdbCategories.getTypedefsCategory(moduleNumber, symbolPath);
} }
/** /**
@ -847,7 +909,7 @@ public class DefaultPdbApplicator implements PdbApplicator {
* @return the {@link CategoryPath} * @return the {@link CategoryPath}
*/ */
CategoryPath getAnonymousFunctionsCategory() { CategoryPath getAnonymousFunctionsCategory() {
return categoryUtils.getAnonymousFunctionsCategory(); return pdbCategories.getAnonymousFunctionsCategory();
} }
/** /**
@ -855,7 +917,7 @@ public class DefaultPdbApplicator implements PdbApplicator {
* @return the {@link CategoryPath} * @return the {@link CategoryPath}
*/ */
CategoryPath getAnonymousTypesCategory() { CategoryPath getAnonymousTypesCategory() {
return categoryUtils.getAnonymousTypesCategory(); return pdbCategories.getAnonymousTypesCategory();
} }
// /** // /**
@ -1482,10 +1544,10 @@ public class DefaultPdbApplicator implements PdbApplicator {
} }
//============================================================================================== //==============================================================================================
// Virtual-Base-Table-related methods. // Virtual-Base/Function-Table-related methods.
//============================================================================================== //==============================================================================================
VbtManager getVbtManager() { VxtManager getVxtManager() {
return vbtManager; return vxtManager;
} }
//============================================================================================== //==============================================================================================
@ -2299,8 +2361,7 @@ public class DefaultPdbApplicator implements PdbApplicator {
} }
private Symbol createSymbolInternal(Address address, SymbolPath symbolPath, private Symbol createSymbolInternal(Address address, SymbolPath symbolPath,
boolean isNewFunctionSignature, boolean isNewFunctionSignature, String plateAddition) {
String plateAddition) {
Symbol existingSymbol = program.getSymbolTable().getPrimarySymbol(address); Symbol existingSymbol = program.getSymbolTable().getPrimarySymbol(address);
if (existingSymbol == null || isNewFunctionSignature) { if (existingSymbol == null || isNewFunctionSignature) {

View File

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,6 +16,7 @@
package ghidra.app.util.pdb.pdbapplicator; package ghidra.app.util.pdb.pdbapplicator;
import ghidra.app.util.bin.format.pdb.*; import ghidra.app.util.bin.format.pdb.*;
import ghidra.app.util.pdb.classtype.ClassFieldAttributes;
import ghidra.program.model.data.DataType; import ghidra.program.model.data.DataType;
import ghidra.util.exception.CancelledException; import ghidra.util.exception.CancelledException;

View File

@ -0,0 +1,623 @@
/* ###
* 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.pdb.classtype;
import static org.junit.Assert.*;
import java.util.*;
import generic.test.AbstractGenericTest;
import ghidra.app.plugin.core.checksums.MyTestMemory;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.SymbolPathParser;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressIterator;
import ghidra.program.model.data.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.util.LittleEndianDataConverter;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* Unit tests for the {@link MsftVxtManager}.
* <p>
* See {@link MsftVxtManager} for a description of what tests need to work
*/
public class MsftVxtManagerTest extends AbstractGenericTest {
private static MessageLog log = new MessageLog();
private static TaskMonitor monitor = TaskMonitor.DUMMY;
private static DataTypeManager dtm32;
private static DataTypeManager dtm64;
// Didn't intend to modify this class to need these, but need them while modifying PdbVxtManager
// to use them
private static ClassTypeManager ctm32;
private static ClassTypeManager ctm64;
private ClassID a1Id = new ProgramClassID(CategoryPath.ROOT, sp("A1NS::A1"));
private ClassID a2Id = new ProgramClassID(CategoryPath.ROOT, sp("A2NS::A2"));
private ClassID aId = new ProgramClassID(CategoryPath.ROOT, sp("ANS::A"));
private ClassID b1Id = new ProgramClassID(CategoryPath.ROOT, sp("B1NS::B1"));
private ClassID b2Id = new ProgramClassID(CategoryPath.ROOT, sp("B2NS::B2"));
private ClassID bId = new ProgramClassID(CategoryPath.ROOT, sp("BNS::B"));
private ClassID cId = new ProgramClassID(CategoryPath.ROOT, sp("CNS::C"));
private ClassID dId = new ProgramClassID(CategoryPath.ROOT, sp("DNS::D"));
private ClassID eId = new ProgramClassID(CategoryPath.ROOT, sp("ENS::E"));
private ClassID fId = new ProgramClassID(CategoryPath.ROOT, sp("FNS::F"));
private ClassID gId = new ProgramClassID(CategoryPath.ROOT, sp("GNS::G"));
private ClassID hId = new ProgramClassID(CategoryPath.ROOT, sp("HNS::H"));
private ClassID iId = new ProgramClassID(CategoryPath.ROOT, sp("INS::I"));
private ClassID jId = new ProgramClassID(CategoryPath.ROOT, sp("JNS::J"));
private ClassID kId = new ProgramClassID(CategoryPath.ROOT, sp("KNS::K"));
private ClassID lId = new ProgramClassID(CategoryPath.ROOT, sp("LNS::L"));
private ClassID mId = new ProgramClassID(CategoryPath.ROOT, sp("MNS::M"));
private static Memory memory32;
private static Memory memory64;
private static List<String> vbtSymbols = new ArrayList<>();
private static List<String> vftSymbols = new ArrayList<>();
private static List<Address> vxtAddresses32;
private static List<Address> vxtAddresses64;
private static Map<String, Address> addressByVxtMangledName32;
private static Map<String, Address> addressByVxtMangledName64;
// private static PointerDataType vftptr32;
// private static PointerDataType vftptr64;
// private static PointerDataType vbtptr32;
// private static PointerDataType vbtptr64;
private static MsftVxtManager mVxtManager32;
private static MsftVxtManager mVxtManager64;
static {
BitFieldPackingImpl bitFieldPacking = new BitFieldPackingImpl();
bitFieldPacking.setUseMSConvention(true);
// DataOrganization based on x86win.cspec
// The DataOrganizationImpl currently has defaults of a 32-bit windows cspec, but could
// change in the future.
DataOrganizationImpl dataOrg32 = DataOrganizationImpl.getDefaultOrganization(null);
dtm32 = new TestDummyDataTypeManager() {
HashMap<String, DataType> dataTypeMap = new HashMap<>();
@Override
public DataOrganization getDataOrganization() {
return dataOrg32;
}
@Override
public DataType addDataType(DataType dataType, DataTypeConflictHandler handler) {
// handler ignored - tests should not induce conflicts
String pathname = dataType.getPathName();
DataType myDt = dataTypeMap.get(pathname);
if (myDt != null) {
return myDt;
}
DataType dt = dataType.clone(this);
dataTypeMap.put(pathname, dt);
return dt;
}
@Override
public DataType findDataType(String dataTypePath) {
return dataTypeMap.get(dataTypePath);
}
@Override
public DataType getDataType(CategoryPath path, String name) {
return super.getDataType(new DataTypePath(path, name).getPath());
}
@Override
public DataType getDataType(String dataTypePath) {
return dataTypeMap.get(dataTypePath);
}
};
// DataOrganization based on x86-64-win.cspec
DataOrganizationImpl dataOrg64 = DataOrganizationImpl.getDefaultOrganization(null);
DataOrganizationTestUtils.initDataOrganizationWindows64BitX86(dataOrg64);
dtm64 = new TestDummyDataTypeManager() {
HashMap<String, DataType> dataTypeMap = new HashMap<>();
@Override
public DataOrganization getDataOrganization() {
return dataOrg64;
}
@Override
public DataType addDataType(DataType dataType, DataTypeConflictHandler handler) {
// handler ignored - tests should not induce conflicts
String pathname = dataType.getPathName();
DataType myDt = dataTypeMap.get(pathname);
if (myDt != null) {
return myDt;
}
DataType dt = dataType.clone(this);
dataTypeMap.put(pathname, dt);
return dt;
}
@Override
public DataType findDataType(String dataTypePath) {
return dataTypeMap.get(dataTypePath);
}
@Override
public DataType getDataType(CategoryPath path, String name) {
return super.getDataType(new DataTypePath(path, name).getPath());
}
@Override
public DataType getDataType(String dataTypePath) {
return dataTypeMap.get(dataTypePath);
}
};
// Didn't intend to modify this class to need these, but need them while modifying
// PdbVxtManager to use them
ctm32 = new ClassTypeManager(dtm32);
ctm64 = new ClassTypeManager(dtm64);
// vftptr32 = new PointerDataType(new PointerDataType(dtm32));
// vftptr64 = new PointerDataType(new PointerDataType(dtm64));
// vbtptr32 = new PointerDataType(new IntegerDataType(dtm32));
// vbtptr64 = new PointerDataType(new IntegerDataType(dtm64));
createVxTables();
mVxtManager32 = new MsftVxtManager(ctm32, memory32);
mVxtManager64 = new MsftVxtManager(ctm64, memory64);
try {
mVxtManager32.createVirtualTables(CategoryPath.ROOT, addressByVxtMangledName32, log,
monitor);
mVxtManager64.createVirtualTables(CategoryPath.ROOT, addressByVxtMangledName64, log,
monitor);
}
catch (CancelledException e) {
// do nothing
}
}
//==============================================================================================
private static SymbolPath sp(String s) {
return new SymbolPath(SymbolPathParser.parse(s));
}
//==============================================================================================
private static void addBytesForIntegers(int[] ints, byte[] bytes, int startOffset) {
int maxOffset = startOffset + 4 * ints.length;
int index = 0;
for (int offset = startOffset; offset < maxOffset; offset += 4) {
LittleEndianDataConverter.INSTANCE.getBytes(ints[index++], bytes, offset);
}
}
static class MemoryPreparer {
private int nextOffset = 0;
private List<int[]> intArrays = new ArrayList<>();
private List<Integer> offsets = new ArrayList<>();
private List<Address> addresses = new ArrayList<>();
private MyTestMemory memory = null;
private int mockAddressCounter = 0;
void addAddresses(int numAddresses, boolean is64bit) {
int[] integers;
if (is64bit) {
integers = new int[numAddresses * 2];
for (int i = 0; i < numAddresses; i++) {
integers[i * 2] = mockAddressCounter;
integers[i * 2 + 1] = 0;
}
}
else {
integers = new int[numAddresses * 2];
for (int i = 0; i < numAddresses; i++) {
integers[i] = mockAddressCounter;
}
}
addIntegers(integers);
}
void addIntegers(int[] integers) {
offsets.add(nextOffset);
intArrays.add(integers);
nextOffset += 4 * integers.length;
}
List<Integer> getOffsets() {
return offsets;
}
void finalizeMemory() {
byte[] bytes = new byte[nextOffset];
for (int index = 0; index < offsets.size(); index++) {
addBytesForIntegers(intArrays.get(index), bytes, offsets.get(index));
}
memory = new CppCompositeTestMemory(bytes);
AddressIterator iter = memory.getAddresses(true);
if (!iter.hasNext()) {
return;
}
Address address = iter.next();
for (Integer offset : offsets) {
addresses.add(address.add(offset));
}
}
Memory getMemory() {
return memory;
}
List<Address> getAddresses() {
return addresses;
}
private static class CppCompositeTestMemory extends MyTestMemory {
public CppCompositeTestMemory(byte[] bytes) {
super(bytes);
}
@Override
public int getInt(Address addr) throws MemoryAccessException {
byte bytes[] = new byte[4];
int num = getBytes(addr, bytes, 0, 4);
assertEquals(num, 4);
return LittleEndianDataConverter.INSTANCE.getInt(bytes);
}
@Override
public long getLong(Address addr) throws MemoryAccessException {
byte bytes[] = new byte[8];
int num = getBytes(addr, bytes, 0, 8);
assertEquals(num, 8);
return LittleEndianDataConverter.INSTANCE.getLong(bytes);
}
}
}
static void createVxTables() {
MemoryPreparer preparer32 = new MemoryPreparer();
MemoryPreparer preparer64 = new MemoryPreparer();
vbtSymbols = new ArrayList<>();
vftSymbols = new ArrayList<>();
//==========================================================================================
vbtSymbols.add("??_8A@ANS@@7B@");
preparer32.addIntegers(new int[] { -4, 8, 16 });
preparer64.addIntegers(new int[] { -8, 16, 32 });
vbtSymbols.add("??_8B@BNS@@7B@");
preparer32.addIntegers(new int[] { -4, 8, 16 });
preparer64.addIntegers(new int[] { -8, 16, 32 });
vbtSymbols.add("??_8C@CNS@@7B@");
preparer32.addIntegers(new int[] { -4, 8, 16, 24, 32 });
preparer64.addIntegers(new int[] { -8, 16, 32, 48, 64 });
vbtSymbols.add("??_8D@DNS@@7BC@CNS@@@");
preparer32.addIntegers(new int[] { -4, 36, 44, 52, 60 });
preparer64.addIntegers(new int[] { -8, 72, 88, 104, 120 });
vbtSymbols.add("??_8D@DNS@@7BA@ANS@@@");
preparer32.addIntegers(new int[] { -4, 24, 32 });
preparer64.addIntegers(new int[] { -8, 48, 64 });
vbtSymbols.add("??_8D@DNS@@7BB@BNS@@@");
preparer32.addIntegers(new int[] { -4, 28, 36 });
preparer64.addIntegers(new int[] { -8, 56, 72 });
vbtSymbols.add("??_8E@ENS@@7BA@ANS@@@");
preparer32.addIntegers(new int[] { -4, 12, 20, 28, 36, 44 });
preparer64.addIntegers(new int[] { -8, 24, 40, 56, 72, 88 });
vbtSymbols.add("??_8E@ENS@@7BB@BNS@@@");
preparer32.addIntegers(new int[] { -4, -20, -12 });
preparer64.addIntegers(new int[] { -8, -40, -24 });
vbtSymbols.add("??_8F@FNS@@7B@");
preparer32.addIntegers(new int[] { 0, 8 });
preparer64.addIntegers(new int[] { 0, 16 });
vbtSymbols.add("??_8G@GNS@@7B@");
preparer32.addIntegers(new int[] { 0, 12 });
preparer64.addIntegers(new int[] { 0, 24 });
vbtSymbols.add("??_8H@HNS@@7B@");
preparer32.addIntegers(new int[] { 0, 12 });
preparer64.addIntegers(new int[] { 0, 24 });
vbtSymbols.add("??_8I@INS@@7BG@GNS@@@");
preparer32.addIntegers(new int[] { 0, 28 });
preparer64.addIntegers(new int[] { 0, 56 });
vbtSymbols.add("??_8I@INS@@7BH@HNS@@@");
preparer32.addIntegers(new int[] { 0, 16 });
preparer64.addIntegers(new int[] { 0, 32 });
vbtSymbols.add("??_8J@JNS@@7B@");
preparer32.addIntegers(new int[] { 0, 8 });
preparer64.addIntegers(new int[] { 0, 16 });
vbtSymbols.add("??_8K@KNS@@7B@");
preparer32.addIntegers(new int[] { 0, 12 });
preparer64.addIntegers(new int[] { 0, 24 });
vbtSymbols.add("??_8L@LNS@@7B@");
preparer32.addIntegers(new int[] { 0, 16 });
preparer64.addIntegers(new int[] { 0, 32 });
vbtSymbols.add("??_8M@MNS@@7BA@ANS@@E@ENS@@@");
preparer32.addIntegers(new int[] { -4, 100, 108, 116, 124, 132 });
preparer64.addIntegers(new int[] { -8, 200, 216, 232, 248, 264 });
vbtSymbols.add("??_8M@MNS@@7BC@CNS@@@");
preparer32.addIntegers(new int[] { -4, 84, 92, 100, 108 });
preparer64.addIntegers(new int[] { -8, 168, 184, 200, 216 });
vbtSymbols.add("??_8M@MNS@@7BA@ANS@@D@DNS@@@");
preparer32.addIntegers(new int[] { -4, 72, 80 });
preparer64.addIntegers(new int[] { -8, 144, 160 });
vbtSymbols.add("??_8M@MNS@@7BB@BNS@@D@DNS@@@");
preparer32.addIntegers(new int[] { -4, 76, 84 });
preparer64.addIntegers(new int[] { -8, 152, 168 });
vbtSymbols.add("??_8M@MNS@@7BG@GNS@@@");
preparer32.addIntegers(new int[] { 0, 48 });
preparer64.addIntegers(new int[] { 0, 96 });
vbtSymbols.add("??_8M@MNS@@7BH@HNS@@@");
preparer32.addIntegers(new int[] { 0, 36 });
preparer64.addIntegers(new int[] { 0, 72 });
vbtSymbols.add("??_8M@MNS@@7B@");
preparer32.addIntegers(new int[] { 0, 20 });
preparer64.addIntegers(new int[] { 0, 40 });
vbtSymbols.add("??_8M@MNS@@7BB@BNS@@E@ENS@@@");
preparer32.addIntegers(new int[] { -4, -20, -12 });
preparer64.addIntegers(new int[] { -8, -40, -24 });
//==========================================================================================
// Below: writing one int to simulate one address for 32-bit and tow ints for 64-bit (lsb)
// Later... mock up even better
vftSymbols.add("??_7A1@A1NS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7A2@A2NS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7A@ANS@@6B01@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7A@ANS@@6BA1@A1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7A@ANS@@6BA2@A2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7B1@B1NS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7B2@B2NS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7B@BNS@@6B01@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7B@BNS@@6BB1@B1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7B@BNS@@6BB2@B2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7C@CNS@@6B01@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7C@CNS@@6BA1@A1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7C@CNS@@6BA2@A2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7C@CNS@@6BB1@B1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7C@CNS@@6BB2@B2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7D@DNS@@6BC@CNS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7D@DNS@@6BA@ANS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7D@DNS@@6BB@BNS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7D@DNS@@6BA1@A1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7D@DNS@@6BA2@A2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7D@DNS@@6BB1@B1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7D@DNS@@6BB2@B2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7E@ENS@@6BA@ANS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7E@ENS@@6BA1@A1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7E@ENS@@6BA2@A2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7E@ENS@@6BB1@B1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7E@ENS@@6BB2@B2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7E@ENS@@6BB@BNS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7F@FNS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7G@GNS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7H@HNS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7I@INS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7J@JNS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7K@KNS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7L@LNS@@6B@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7M@MNS@@6BA@ANS@@E@ENS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7M@MNS@@6BC@CNS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7M@MNS@@6BA@ANS@@D@DNS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7M@MNS@@6BB@BNS@@D@DNS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
vftSymbols.add("??_7M@MNS@@6BA1@A1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7M@MNS@@6BA2@A2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7M@MNS@@6BB1@B1NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7M@MNS@@6BB2@B2NS@@@");
preparer32.addAddresses(3, false);
preparer64.addAddresses(3, true);
vftSymbols.add("??_7M@MNS@@6BB@BNS@@E@ENS@@@");
preparer32.addAddresses(1, false);
preparer64.addAddresses(1, true);
//==========================================================================================
preparer32.finalizeMemory();
preparer64.finalizeMemory();
memory32 = preparer32.getMemory();
memory64 = preparer64.getMemory();
vxtAddresses32 = preparer32.getAddresses();
vxtAddresses64 = preparer64.getAddresses();
addressByVxtMangledName32 = new HashMap<>();
addressByVxtMangledName64 = new HashMap<>();
if (vbtSymbols.size() + vftSymbols.size() != vxtAddresses32.size() ||
vbtSymbols.size() + vftSymbols.size() != vxtAddresses64.size()) {
throw new AssertException("Fatal: list sizes do not match");
}
int aCount = 0;
for (String vbtSymbol : vbtSymbols) {
addressByVxtMangledName32.put(vbtSymbol, vxtAddresses32.get(aCount));
addressByVxtMangledName64.put(vbtSymbol, vxtAddresses64.get(aCount));
aCount++;
}
for (String vftSymbol : vftSymbols) {
addressByVxtMangledName32.put(vftSymbol, vxtAddresses32.get(aCount));
addressByVxtMangledName64.put(vftSymbol, vxtAddresses64.get(aCount));
aCount++;
}
}
//==============================================================================================
//==============================================================================================
// No tests at this point because of need to rework the design
}