Merge remote-tracking branch 'origin/GP-5408_ghizard_Rework_hierarchical_class_layout_and_vxt_understanding--SQUASHED'

This commit is contained in:
Ryan Kurtz
2025-03-04 14:01:01 -05:00
13 changed files with 2171 additions and 1372 deletions
@@ -1,35 +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;
import ghidra.program.model.data.PointerDataType;
public class ClassUtils {
// Prototype values for now. Need to come to agreement on what these should be
public static final String VBPTR = "{vbptr}";
public static final String VFPTR = "{vfptr}";
/**
* Type used for {@link #VBPTR} and {@link #VFPTR} fields in a class
*/
public static final PointerDataType VXPTR_TYPE = new PointerDataType();
private ClassUtils() {
// no instances
}
}
@@ -0,0 +1,126 @@
/* ###
* 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;
/**
* Virtual Base Table without a program
*/
public class PlaceholderVirtualBaseTable extends VirtualBaseTable {
private int entrySize;
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 entrySize the size of the index field for each table entry as it would be in memory
*/
public PlaceholderVirtualBaseTable(ClassID owner, List<ClassID> parentage, int entrySize) {
super(owner, parentage);
if (entrySize != 4 && entrySize != 8) {
throw new IllegalArgumentException("Invalid size (" + entrySize + "): must be 4 or 8.");
}
this.entrySize = entrySize;
}
/*
* 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 createTableDataType(int numEntries) {
}
int getMaxIndex() {
return maxIndexSeen;
}
public void setBaseClassOffsetAndId(int index, Long offset, ClassID baseId) {
VBTableEntry entry = entriesByIndex.get(index);
if (entry == null) {
entry = new VirtualBaseTableEntry(offset, baseId);
entriesByIndex.put(index, entry);
}
else {
entry.setOffset(offset);
entry.setClassId(baseId);
}
maxIndexSeen = Integer.max(maxIndexSeen, index);
}
public void setBaseClassId(int index, ClassID baseId) {
VBTableEntry entry = entriesByIndex.get(index);
if (entry == null) {
entry = new VirtualBaseTableEntry(baseId);
entriesByIndex.put(index, entry);
}
else {
entry.setClassId(baseId);
}
maxIndexSeen = Integer.max(maxIndexSeen, index);
}
public void setBaseOffset(int index, Long offset) {
VBTableEntry entry = entriesByIndex.get(index);
if (entry == null) {
entry = new VirtualBaseTableEntry(offset);
entriesByIndex.put(index, entry);
}
else {
entry.setOffset(offset);
}
maxIndexSeen = Integer.max(maxIndexSeen, index);
}
@Override
public Long getBaseOffset(int index) throws PdbException {
VBTableEntry entry = entriesByIndex.get(index);
Long offset = (entry == null) ? null : entry.getOffset();
maxIndexSeen = Integer.max(maxIndexSeen, index);
return offset;
}
@Override
public ClassID getBaseClassId(int index) {
VBTableEntry entry = entriesByIndex.get(index);
ClassID id = (entry == null) ? null : entry.getClassId();
maxIndexSeen = Integer.max(maxIndexSeen, index);
return id;
}
@Override
public VBTableEntry getBase(int index) throws PdbException {
VBTableEntry entry = entriesByIndex.get(index);
if (entry != null) {
maxIndexSeen = Integer.max(maxIndexSeen, index);
}
return entry;
}
}
@@ -36,8 +36,6 @@ public class ProgramVirtualBaseTable extends VirtualBaseTable {
private Boolean createdFromMemory = null;
private Boolean createdFromCompiled = null;
private int numEntries = 0;
private int maxIndexSeen = -1;
private Map<Integer, VBTableEntry> entriesByIndex = new HashMap<>();
@@ -47,7 +45,7 @@ public class ProgramVirtualBaseTable extends VirtualBaseTable {
* @param parentage the parentage of the base class(es) of the table
* @param program the program
* @param address the address of the table
* @param entrySize the size for each table entry
* @param entrySize the size of the index field for each table entry in memory
* @param ctm the class type manager
* @param mangledName the mangled name of the table
*/
@@ -76,7 +74,7 @@ public class ProgramVirtualBaseTable extends VirtualBaseTable {
* Returns the mangled name
* @return the mangled name
*/
String getMangledName() {
public String getMangledName() {
return mangledName;
}
@@ -136,16 +134,16 @@ public class ProgramVirtualBaseTable extends VirtualBaseTable {
return entry;
}
// Need to decide if we want to allow this to overwrite existing entry.
public void setBaseClassId(int index, ClassID baseId) throws PdbException {
public void setBaseClassId(int index, ClassID baseId) {
VBTableEntry entry = entriesByIndex.get(index);
if (entry != null) {
throw new PdbException(
"Entry already exists in Virtual Base Table for index: " + index);
if (entry == null) {
entry = new VirtualBaseTableEntry(baseId);
entriesByIndex.put(index, entry);
}
entry = new VirtualBaseTableEntry(baseId);
entriesByIndex.put(index, entry);
maxIndexSeen = Integer.max(maxIndexSeen, index); // do we want this here with a "set" method?
else {
entry.setClassId(baseId);
}
maxIndexSeen = Integer.max(maxIndexSeen, index);
}
}
@@ -68,7 +68,7 @@ public class ProgramVirtualFunctionTable extends VirtualFunctionTable {
* Returns the mangled name
* @return the mangled name
*/
String getMangledName() {
public String getMangledName() {
return mangledName;
}
@@ -24,7 +24,7 @@ public interface VBTableEntry {
* Sets the entry offset value
* @param offset the offset
*/
public void setOffset(long offset);
public void setOffset(Long offset);
/**
* Gets the entry offset value
@@ -24,8 +24,18 @@ 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
/**
* The number of entries in the table
*/
protected Integer numEntries;
/**
* This is the offset within the class where we expect to find the pointer that can point to
* this table
*/
protected Long ptrOffsetInClass;
/**
* Virtual Base Table for a base (parent) class within an owner class. The owner and parent
@@ -83,6 +93,22 @@ public abstract class VirtualBaseTable implements VBTable {
return parentage;
}
/**
* Returns the number of entries in the table
* @return the number of entries; {@code null} if not initialized
*/
public Integer getNumEntries() {
return numEntries;
}
/**
* Gets the offset within the class for the pointer that can point to this table
* @return the offset; {@code null} if not initialized
*/
public Long getPtrOffsetInClass() {
return ptrOffsetInClass;
}
/**
* Sets the owner of the table
* @param ownerArg the class to set as owner
@@ -99,6 +125,22 @@ public abstract class VirtualBaseTable implements VBTable {
this.parentage = parentage;
}
/**
* Sets the number of entries for the table
* @param numEntriesArg the number of entries
*/
public void setNumEntries(Integer numEntriesArg) {
numEntries = numEntriesArg;
}
/**
* Sets the offset within the class for the pointer that can point to this table
* @param offset the offset
*/
public void setPtrOffsetInClass(Long offset) {
ptrOffsetInClass = offset;
}
void emit(StringBuilder builder) {
builder.append("VBT for the following parentage within: " + owner);
builder.append("\n");
@@ -24,21 +24,21 @@ public class VirtualBaseTableEntry implements VBTableEntry {
// Re-evaluate which constructors and setters we need
VirtualBaseTableEntry(long offset) {
public VirtualBaseTableEntry(Long offset) {
this(offset, null);
}
VirtualBaseTableEntry(ClassID baseId) {
public VirtualBaseTableEntry(ClassID baseId) {
this(null, baseId);
}
VirtualBaseTableEntry(Long offset, ClassID baseId) {
public VirtualBaseTableEntry(Long offset, ClassID baseId) {
this.offset = offset;
this.baseId = baseId;
}
@Override
public void setOffset(long offset) {
public void setOffset(Long offset) {
this.offset = offset;
}
@@ -26,6 +26,15 @@ public abstract class VirtualFunctionTable implements VFTable {
protected ClassID owner;
protected List<ClassID> parentage;
/**
* The number of entries in the table
*/
protected int numEntries;
/**
* This is the offset within the class where we expect to find the pointer that can point to
* this table
*/
protected int ptrOffsetInClass;
/**
* Virtual Function Table for a base (parent) class within an owner class. The owner and parent
@@ -37,6 +46,7 @@ public abstract class VirtualFunctionTable implements VFTable {
VirtualFunctionTable(ClassID owner, List<ClassID> parentage) {
this.owner = owner;
this.parentage = new ArrayList<>(parentage);
numEntries = 0;
}
/**
@@ -71,6 +81,22 @@ public abstract class VirtualFunctionTable implements VFTable {
return parentage;
}
/**
* Returns the number of entries in the table
* @return the number of entries
*/
public int getNumEntries() {
return numEntries;
}
/**
* Gets the offset within the class for the pointer that can point to this table
* @return the offset
*/
public int getPtrOffsetInClass() {
return ptrOffsetInClass;
}
/**
* Sets the owner of the table
* @param ownerArg the class to set as owner
@@ -87,6 +113,22 @@ public abstract class VirtualFunctionTable implements VFTable {
this.parentage = parentage;
}
/**
* Sets the number of entries for the table
* @param numEntriesArg the number of entries
*/
public void setNumEntries(int numEntriesArg) {
numEntries = numEntriesArg;
}
/**
* Sets the offset within the class for the pointer that can point to this table
* @param offset the offset
*/
public void setPtrOffsetInClass(int offset) {
ptrOffsetInClass = offset;
}
void emit(StringBuilder builder) {
builder.append("VBT for the following classes within: " + owner);
builder.append("\n");
@@ -137,6 +137,7 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
}
else {
applyCpp(combo, type, lists);
applicator.markFilledInForBase(type.getRecordNumber());
}
return true;
}
@@ -182,6 +183,15 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
}
List<DefaultPdbUniversalMember> myMembers = new ArrayList<>();
addClassTypeBaseClasses(composite, classType, lists.bases(), type);
// Note: I've seen the situation where there is no vftptr for a class where I expected one.
// Situation is where a parent has a vftptr with a vtshape with one entry. The child
// class defines an additional virtual method, and there is an actual table in memory with
// with the appropriate mangled label for this class that has two entries, but the list
// here does not have the vftptr, and I believe it was due to the fact that new new method
// was never called in the code. Thus... do not count on getting a vtshape in this
// situation. Note that a record of a an appropriate two-entry vtshape was found right
// before the class definition, but no pointer to it was created or referred to in the
// field list.
addVftPtrs(composite, classType, lists.vftPtrs(), type, myMembers);
addMembers(composite, classType, lists.nonstaticMembers(), type, myMembers);
@@ -262,20 +272,31 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
private void applyDirectBaseClass(AbstractBaseClassMsType base, CppCompositeType myClassType,
Access defaultAccess) throws PdbException {
CppCompositeType underlyingClassType =
getUnderlyingClassType(base.getBaseClassRecordNumber());
RecordNumber recordNumber = base.getBaseClassRecordNumber();
DataType dt = applicator.getDataType(recordNumber);
if (!(dt instanceof Composite comp)) {
Msg.warn(this, "Composite not found for base class: " + dt);
return;
}
CppCompositeType underlyingClassType = getUnderlyingClassType(recordNumber);
if (underlyingClassType == null) {
return;
}
ClassFieldMsAttributes atts = base.getAttributes();
int offset = applicator.bigIntegerToInt(base.getOffset());
myClassType.addDirectBaseClass(underlyingClassType,
myClassType.addDirectBaseClass(comp, underlyingClassType,
ClassFieldAttributes.convert(atts, defaultAccess), offset);
}
private void applyDirectVirtualBaseClass(AbstractVirtualBaseClassMsType base,
CppCompositeType myClassType, Access defaultAccess) throws PdbException {
CppCompositeType underlyingCt = getUnderlyingClassType(base.getBaseClassRecordNumber());
RecordNumber recordNumber = base.getBaseClassRecordNumber();
DataType dt = applicator.getDataType(recordNumber);
if (!(dt instanceof Composite comp)) {
Msg.warn(this, "Composite not found for base class: " + dt);
return;
}
CppCompositeType underlyingCt = getUnderlyingClassType(recordNumber);
if (underlyingCt == null) {
return;
}
@@ -284,14 +305,20 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
ClassFieldMsAttributes atts = base.getAttributes();
int basePointerOffset = applicator.bigIntegerToInt(base.getBasePointerOffset());
int offsetFromVbt = applicator.bigIntegerToInt(base.getBaseOffsetFromVbt());
myClassType.addDirectVirtualBaseClass(underlyingCt,
myClassType.addDirectVirtualBaseClass(comp, underlyingCt,
ClassFieldAttributes.convert(atts, defaultAccess), basePointerOffset, vbtptr,
offsetFromVbt);
}
private void applyIndirectVirtualBaseClass(AbstractIndirectVirtualBaseClassMsType base,
CppCompositeType myClassType, Access defaultAccess) throws PdbException {
CppCompositeType underlyingCt = getUnderlyingClassType(base.getBaseClassRecordNumber());
RecordNumber recordNumber = base.getBaseClassRecordNumber();
DataType dt = applicator.getDataType(recordNumber);
if (!(dt instanceof Composite comp)) {
Msg.warn(this, "Composite not found for base class: " + dt);
return;
}
CppCompositeType underlyingCt = getUnderlyingClassType(recordNumber);
if (underlyingCt == null) {
return;
}
@@ -300,7 +327,7 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
ClassFieldMsAttributes atts = base.getAttributes();
int basePointerOffset = applicator.bigIntegerToInt(base.getBasePointerOffset());
int offsetFromVbt = applicator.bigIntegerToInt(base.getBaseOffsetFromVbt());
myClassType.addIndirectVirtualBaseClass(underlyingCt,
myClassType.addIndirectVirtualBaseClass(comp, underlyingCt,
ClassFieldAttributes.convert(atts, defaultAccess), basePointerOffset, vbtptr,
offsetFromVbt);
}
@@ -390,7 +417,7 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
applicator.checkCancelled();
if (base instanceof AbstractBaseClassMsType bc) {
RecordNumber recordNumber = bc.getBaseClassRecordNumber();
DataType dt = applicator.getDataTypeOrSchedule(recordNumber);
DataType dt = applicator.getBaseClassDataTypeOrSchedule(recordNumber);
if (dt == null) {
done = false;
}
@@ -402,7 +429,7 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
done = false;
}
recordNumber = vbc.getBaseClassRecordNumber();
dt = applicator.getDataTypeOrSchedule(recordNumber);
dt = applicator.getBaseClassDataTypeOrSchedule(recordNumber);
if (dt == null) {
done = false;
}
@@ -414,7 +441,7 @@ public class CompositeTypeApplier extends AbstractComplexTypeApplier {
done = false;
}
recordNumber = ivbc.getBaseClassRecordNumber();
dt = applicator.getDataTypeOrSchedule(recordNumber);
dt = applicator.getBaseClassDataTypeOrSchedule(recordNumber);
if (dt == null) {
done = false;
}
File diff suppressed because it is too large Load Diff
@@ -220,6 +220,7 @@ public class DefaultPdbApplicator implements PdbApplicator {
// a second PDB analyzer to do the "deferred" processing of functions. Then a mandatory
// second PDB analyzer would, at a minimum, remove the map from the analysis state.
private Map<RecordNumber, DataType> dataTypeByMsTypeNum;
private Set<RecordNumber> filledInStructure;
private Map<RecordNumber, CppCompositeType> classTypeByMsTypeNum;
private ComplexTypeMapper complexTypeMapper;
/**
@@ -267,7 +268,7 @@ public class DefaultPdbApplicator implements PdbApplicator {
Objects.requireNonNull(pdb, "pdb cannot be null");
this.pdb = pdb;
this.monitor = (monitor != null) ? monitor : TaskMonitor.DUMMY;
this.monitor = TaskMonitor.dummyIfNull(monitor);
// FIXME: should not support use of DataTypeManager-only since it will not have the correct
// data organization if it corresponds to a data type archive. Need to evaluate archive
@@ -592,6 +593,10 @@ public class DefaultPdbApplicator implements PdbApplicator {
multiphaseResolver = new MultiphaseDataTypeResolver(this);
// Following should not need to be part of analysis state because types are filled in
// during first round of processing
filledInStructure = new HashSet<>();
classTypeManager = new ClassTypeManager(dataTypeManager);
pdbPrimitiveTypeApplicator = new PdbPrimitiveTypeApplicator(dataTypeManager);
@@ -1054,6 +1059,17 @@ public class DefaultPdbApplicator implements PdbApplicator {
dataTypeByMsTypeNum.put(mappedNumber, dataType);
}
/**
* Stores whether the structure referenced by the record number has been filled in such that
* it can be used as a base class
* @param recordNumber record number of type record
* @param dataType the data type to store
*/
void markFilledInForBase(RecordNumber recordNumber) {
RecordNumber mappedNumber = getMappedRecordNumber(recordNumber);
filledInStructure.add(mappedNumber);
}
/**
* Returns the Ghidra data type associated with the PDB data type.
* This method is intended to be used by appliers that work on this specific type, not by
@@ -1097,6 +1113,26 @@ public class DefaultPdbApplicator implements PdbApplicator {
return null;
}
/**
* Returns the Ghidra data type associated with the PDB record number for the base class
* of another class. In this case, we require the base class structure to be completed
* in order for the child class to be constructed
* This method is intended to be used by appliers that work on this specific type, not by
* appliers that need the data type
* @param recordNumber the PDB type record number
* @return the Ghidra DB data type of the base class
*/
DataType getBaseClassDataTypeOrSchedule(RecordNumber recordNumber) {
RecordNumber mappedNumber = getMappedRecordNumber(recordNumber);
boolean filledIn = filledInStructure.contains(mappedNumber);
DataType dt = dataTypeByMsTypeNum.get(mappedNumber);
if (dt != null && filledIn) {
return dt;
}
multiphaseResolver.scheduleTodo(mappedNumber);
return null;
}
//==============================================================================================
/**
* Stores the Ghidra class type associated with the PDB data type.
@@ -0,0 +1,95 @@
/* ###
* 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.program.model.gclass;
import ghidra.program.model.data.*;
/**
* Utility class for Class-related software modeling.
*/
public class ClassUtils {
// Prototype values for now. Need to come to agreement on what these should be. Might want
// a separate one for a generic virtual table (not "base" or "function") for gcc/Itanium
// standard
/**
* Standard field name for a virtual base table pointer found within a class
*/
public static final String VBPTR = "{vbptr}";
/**
* Standard field name for a virtual function table pointer found within a class
*/
public static final String VFPTR = "{vfptr}";
/**
* Type used for {@link #VBPTR} and {@link #VFPTR} fields in a class
*/
public static final PointerDataType VXPTR_TYPE = new PointerDataType();
/**
* private constructor -- no instances
*/
private ClassUtils() {
// no instances
}
/**
* Returns the category for class internals
* @param composite the class composite
* @return the category path
*/
public static CategoryPath getClassInternalsPath(Composite composite) {
DataTypePath dtp = composite.getDataTypePath();
return new CategoryPath(new CategoryPath(dtp.getCategoryPath(), dtp.getDataTypeName()),
"!internal");
}
/**
* Returns the data type path for a suitable base class
* @param composite the class composite
* @return the base class data type path
*/
public static DataTypePath getBaseClassDataTypePath(Composite composite) {
return new DataTypePath(getClassInternalsPath(composite), composite.getName());
}
/**
* Returns the "self-base" composite for the specified class composite. This could be
* the composite argument itself of could be a component of it
* @param composite the main class type
* @return the self-base composite
*/
public static Composite getSelfBaseType(Composite composite) {
DataTypeManager dtm = composite.getDataTypeManager();
DataTypePath dtp = ClassUtils.getBaseClassDataTypePath(composite);
DataType dt = dtm.getDataType(dtp);
if (dt instanceof Composite base) {
return base;
}
if (composite.getNumComponents() > 0) {
DataTypeComponent component = composite.getComponent(0);
DataType componentType = component.getDataType();
if (componentType instanceof Structure struct) {
if (struct.getDataTypePath().equals(dtp)) {
return struct;
}
}
}
return composite;
}
}