GP-5524 - Improve CPP/PDB testing framework

This commit is contained in:
ghizard 2025-04-22 06:07:37 -04:00
parent 23e656ffc0
commit 12d14149f4
16 changed files with 53699 additions and 7363 deletions

View file

@ -0,0 +1,584 @@
/* ###
* 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.
*/
// Developer script to dump certain Program and PDB information for use in testing products.
//
//@category PDB
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
import ghidra.app.script.GhidraScript;
import ghidra.app.util.SymbolPath;
import ghidra.app.util.SymbolPathParser;
import ghidra.app.util.bin.format.pdb2.pdbreader.*;
import ghidra.app.util.bin.format.pdb2.pdbreader.type.*;
import ghidra.app.util.pdb.classtype.*;
import ghidra.program.database.symbol.SymbolManager;
import ghidra.program.model.address.*;
import ghidra.program.model.data.DataOrganization;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.Memory;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.symbol.Symbol;
import ghidra.program.model.symbol.SymbolIterator;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import pdb.PdbPlugin;
import pdb.symbolserver.FindOption;
/**
* Developer script to capture items from Program and PDB for developing tests
*/
public class CaptureHelperScript extends GhidraScript {
private AddressFactory addressFactory;
private Memory memory;
private Address progMaxAddr;
private SymbolManager symbolManager;
private FunctionManager functionManager;
private DataOrganization dataOrganization;
private DataConverter dataConverter;
private int pointerSize;
private Map<Address, NameAndBytes> vbtInfo;
private Map<Address, NameAndBytes> vftInfo;
private Map<Address, NameAndBytes> functionInfo;
private Map<Address, FunctionSignature> signatureInfo;
// The following can have extra non-target address since we do not know the real table lenths.
// For instance, any table that comes before a vftable could run into the a vftable "meta"
// pointer before the vftable symbol.
private Set<Address> vftTargetAddresses;
@Override
public void run() throws Exception {
if (!init()) {
return;
}
String vbtInfoString = getVbTables();
String vftInfoString = getVfTables();
String functionInfoString = getFunctions();
String signatureInfoString = getSignatures();
String pdbVirtualMethods = getPdbVirtualMethods();
print(vbtInfoString);
print(vftInfoString);
print(functionInfoString);
print(signatureInfoString);
print(pdbVirtualMethods);
}
private boolean init() {
if (currentProgram == null) {
println("No Program Open");
return false;
}
addressFactory = currentProgram.getAddressFactory();
memory = currentProgram.getMemory();
progMaxAddr = memory.getMaxAddress();
symbolManager = (SymbolManager) currentProgram.getSymbolTable();
functionManager = currentProgram.getFunctionManager();
dataOrganization = currentProgram.getDataTypeManager().getDataOrganization();
dataConverter = DataConverter.getInstance(dataOrganization.isBigEndian());
pointerSize = dataOrganization.getPointerSize();
vbtInfo = new TreeMap<>();
vftInfo = new TreeMap<>();
functionInfo = new TreeMap<>();
signatureInfo = new TreeMap<>();
return true;
}
private String getVbTables() {
getVxTables(vbtInfo, "??_8*");
return dumpBytes(vbtInfo);
}
private String getVfTables() {
getVxTables(vftInfo, "??_7*");
return dumpBytes(vftInfo);
}
private String getFunctions() {
vftTargetAddresses = getFunctionAddressesFromVfts(vftInfo);
for (Address addr : vftTargetAddresses) {
getFunctionInfo(addr);
}
return dumpBytes(functionInfo);
}
private void getVxTables(Map<Address, NameAndBytes> info, String pattern) {
SymbolIterator iter = symbolManager.getSymbolIterator(pattern, true);
while (iter.hasNext()) {
Symbol symbol = iter.next();
Address addr = symbol.getAddress();
Symbol any;
Address anyAddr = progMaxAddr;
SymbolIterator anyIter = symbolManager.getSymbolIterator(addr, true);
while (anyIter.hasNext()) {
any = anyIter.next();
anyAddr = any.getAddress();
if (anyAddr.compareTo(addr) > 0) {
break;
}
}
Long length = anyAddr.getUnsignedOffset() - addr.getUnsignedOffset();
String name = symbol.getName();
byte[] bytes = new byte[length.intValue()];
try {
memory.getBytes(addr, bytes);
}
catch (MemoryAccessException e) {
Msg.warn(this, "Memory access error");
}
info.put(addr, new NameAndBytes(name, bytes));
}
}
private String dumpBytes(Map<Address, NameAndBytes> info) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<Address, NameAndBytes> entry : info.entrySet()) {
Address addr = entry.getKey();
NameAndBytes nab = entry.getValue();
builder.append(String.format("new AddressNameBytes(\"%s\",\"%s\",\"%s\"),\n",
addr.toString(), nab.name(),
NumericUtilities.convertBytesToString(nab.bytes, " ")));
}
return builder.toString();
}
private Set<Address> getFunctionAddressesFromVfts(Map<Address, NameAndBytes> info) {
Set<Address> results = new TreeSet<>();
for (Map.Entry<Address, NameAndBytes> entry : info.entrySet()) {
Address addr = entry.getKey();
int spaceId = addr.getAddressSpace().getSpaceID();
NameAndBytes nab = entry.getValue();
byte[] bytes = nab.bytes();
if (bytes.length % pointerSize != 0) {
// problem
}
int num = bytes.length / pointerSize;
for (int ordinal = 0; ordinal < num; ordinal++) {
long offset = getOffset(bytes, ordinal);
Address functionAddress = addressFactory.getAddress(spaceId, offset);
if (ignoreAddress(functionAddress)) {
break;
}
results.add(functionAddress);
}
}
return results;
}
private boolean ignoreAddress(Address addr) {
if (addr.getOffset() == 0L) {
return true;
}
for (Symbol symbol : symbolManager.getSymbols(addr)) {
if (symbol.getName().startsWith("??_R4")) {
return true;
}
}
return false;
}
private long getOffset(byte[] bytes, int ordinal) {
int index = pointerSize * ordinal;
if (pointerSize == 4) {
return dataConverter.getInt(bytes, index);
}
else if (pointerSize == 8) {
return dataConverter.getLong(bytes, index);
}
else {
return 0;
}
}
private void getFunctionInfo(Address addr) {
Function function = functionManager.getFunctionAt(addr);
AddressSetView asv = function.getBody();
Address maxAddr = asv.getMaxAddress();
Long length = maxAddr.getUnsignedOffset() - addr.getUnsignedOffset();
byte[] bytes = new byte[length.intValue()];
try {
memory.getBytes(addr, bytes);
}
catch (MemoryAccessException e) {
Msg.warn(this, "Memory access error");
}
FunctionSignature sig = function.getSignature(true);
Symbol symbol = symbolManager.getPrimarySymbol(addr);
String name = symbol.getName(true);
functionInfo.put(addr, new NameAndBytes(name, bytes));
signatureInfo.put(addr, sig);
}
private String getSignatures() {
return dumpSignatures(signatureInfo);
}
private String dumpSignatures(Map<Address, FunctionSignature> signatures) {
StringBuilder builder = new StringBuilder();
for (Map.Entry<Address, FunctionSignature> entry : signatureInfo.entrySet()) {
Address addr = entry.getKey();
FunctionSignature sig = entry.getValue();
builder.append(String.format("\"%s\",\"%s\"\n", addr.toString(), sig.toString()));
}
return builder.toString();
}
private record NameAndBytes(String name, byte[] bytes) {}
private String getPdbVirtualMethods() throws CancelledException {
File pdbFile = locatePdbFile();
if (pdbFile == null) {
return "";
}
Map<SymbolPath, List<String>> info = getPdbMethodInfo(pdbFile);
StringBuilder builder = new StringBuilder();
for (Map.Entry<SymbolPath, List<String>> entry : info.entrySet()) {
SymbolPath classPath = entry.getKey();
builder.append("------------------------------\n");
builder.append(classPath.getPath());
builder.append('\n');
for (String methodInfo : entry.getValue()) {
builder.append(methodInfo);
builder.append('\n');
}
}
return builder.toString();
}
private File locatePdbFile() {
File pdbFile = PdbPlugin.findPdb(currentProgram, FindOption.NO_OPTIONS, monitor);
return pdbFile;
}
private Map<SymbolPath, List<String>> getPdbMethodInfo(File pdbFile) throws CancelledException {
PdbReaderOptions pdbReaderOptions = new PdbReaderOptions();
Map<SymbolPath, List<String>> results = new LinkedHashMap<>();
try (AbstractPdb pdb = PdbParser.parse(pdbFile, pdbReaderOptions, monitor)) {
monitor.setMessage("PDB: Parsing " + pdbFile + "...");
pdb.deserialize();
TypeProgramInterface tpi = pdb.getTypeProgramInterface();
if (tpi == null) {
return results;
}
for (int indexNumber = tpi.getTypeIndexMin(); indexNumber < tpi
.getTypeIndexMaxExclusive(); indexNumber++) {
monitor.checkCancelled();
RecordNumber recordNumber = RecordNumber.typeRecordNumber(indexNumber);
AbstractMsType msType = pdb.getTypeRecord(recordNumber);
if (msType instanceof AbstractComplexMsType acms) {
String className = acms.getName();
SymbolPath classSymbolPath = new SymbolPath(SymbolPathParser.parse(className));
RecordNumber listRecordNumber = acms.getFieldDescriptorListRecordNumber();
AbstractMsType type = pdb.getTypeRecord(listRecordNumber);
if (type instanceof PrimitiveMsType primitive && primitive.isNoType()) {
continue;
}
else if (type instanceof AbstractFieldListMsType fieldListType) {
List<String> commandStrings = new ArrayList<>();
commandStrings.add("length: " + acms.getLength());
List<AbstractVirtualFunctionTablePointerMsType> vftPtrs =
fieldListType.getVftPointers();
processVftPtrs(commandStrings, pdb, vftPtrs);
processBases(commandStrings, pdb, fieldListType.getBaseClassList());
processNonstaticMembers(commandStrings, pdb,
fieldListType.getNonStaticMembers());
processMethods(commandStrings, pdb, fieldListType.getMethodList());
if (!commandStrings.isEmpty()) {
results.put(classSymbolPath, commandStrings);
}
}
else {
throw new PdbException(type.getClass().getSimpleName() + " seen where " +
FieldListMsType.class.getSimpleName() + " expected for record number " +
recordNumber);
}
}
}
}
catch (PdbException | IOException e) {
Msg.warn(this, e);
}
return results;
}
private void processVftPtrs(List<String> commandStrings, AbstractPdb pdb,
List<AbstractVirtualFunctionTablePointerMsType> vftPtrs) {
for (AbstractVirtualFunctionTablePointerMsType vftPtr : vftPtrs) {
commandStrings.add(getVftPtrString(vftPtr));
}
}
private String getVftPtrString(AbstractVirtualFunctionTablePointerMsType vftPtr) {
return String.format("struct.addVirtualFunctionTablePointer(ClassUtils.VXPTR_TYPE, %d);",
vftPtr.getOffset());
}
private void processBases(List<String> commandStrings, AbstractPdb pdb,
List<MsTypeField> bases) {
for (MsTypeField base : bases) {
commandStrings.add(getBaseString(pdb, base));
}
}
private String getBaseString(AbstractPdb pdb, MsTypeField baseType) {
if (baseType instanceof AbstractBaseClassMsType base) {
RecordNumber recordNumber = base.getBaseClassRecordNumber();
AbstractMsType ut = pdb.getTypeRecord(recordNumber);
if (!(ut instanceof AbstractCompositeMsType underlyingType)) {
Msg.warn(this, "Composite not found for base class: " + ut);
return "";
}
String underlyingName = underlyingType.getName();
List<String> parts = SymbolPathParser.parse(underlyingName);
String name = parts.getLast();
return String.format(
"struct.addDirectBaseClass(%s_struct.getComposite(), %s_struct, %s, %d);",
name, name, getAttsString(base.getAttributes()), base.getOffset());
}
else if (baseType instanceof AbstractVirtualBaseClassMsType base) {
RecordNumber recordNumber = base.getBaseClassRecordNumber();
AbstractMsType ut = pdb.getTypeRecord(recordNumber);
if (!(ut instanceof AbstractCompositeMsType underlyingType)) {
Msg.warn(this, "Composite not found for base class: " + ut);
return "";
}
String underlyingName = underlyingType.getName();
List<String> parts = SymbolPathParser.parse(underlyingName);
String name = parts.getLast();
return String.format(
"struct.addDirectVirtualBaseClass(%s_struct.getComposite()" +
", %s_struct, %s, %d, ClassUtils.VXPTR_TYPE, %d);",
name, name, getAttsString(base.getAttributes()),
base.getBasePointerOffset().intValue(), base.getBaseOffsetFromVbt().intValue());
}
else if (baseType instanceof AbstractIndirectVirtualBaseClassMsType base) {
RecordNumber recordNumber = base.getBaseClassRecordNumber();
AbstractMsType ut = pdb.getTypeRecord(recordNumber);
if (!(ut instanceof AbstractCompositeMsType underlyingType)) {
Msg.warn(this, "Composite not found for base class: " + ut);
return "";
}
String underlyingName = underlyingType.getName();
List<String> parts = SymbolPathParser.parse(underlyingName);
String name = parts.getLast();
return String.format(
"struct.addIndirectVirtualBaseClass(%s_struct.getComposite()" +
", %s_struct, %s, %d, ClassUtils.VXPTR_TYPE, %d);",
name, name, getAttsString(base.getAttributes()),
base.getBasePointerOffset().intValue(), base.getBaseOffsetFromVbt().intValue());
}
else {
throw new AssertException(
"Unknown base class type: " + baseType.getClass().getSimpleName());
}
}
private void processNonstaticMembers(List<String> commandStrings, AbstractPdb pdb,
List<AbstractMemberMsType> members) {
for (AbstractMemberMsType member : members) {
commandStrings.add(getMemberString(pdb, member));
}
}
private String getMemberString(AbstractPdb pdb, AbstractMemberMsType member) {
RecordNumber r = member.getFieldTypeRecordNumber();
AbstractMsType t = pdb.getTypeRecord(r);
boolean isZeroLengthArray =
t instanceof AbstractArrayMsType a && BigInteger.ZERO.equals(a.getSize());
return String.format(
"struct.addMember(\"%s\", %sT, %s, %s, %d, null);",
member.getName(), getTypeName(pdb, r), isZeroLengthArray,
getAttsString(member.getAttribute()), member.getOffset().intValue());
}
private String getTypeName(AbstractPdb pdb, RecordNumber r) {
int num = r.getNumber();
return switch (num) {
case 0x13 -> "longlong";
case 0x20 -> "unsignedchar";
case 0x21 -> "unsignedshort";
case 0x22 -> "unsignedlong";
case 0x40 -> "float";
case 0x41 -> "double";
case 0x42 -> "longdouble";
case 0x467 -> "pvoid";
case 0x470 -> "pchar";
case 0x603 -> "pvoid";
case 0x670 -> "pchar";
default -> getOther(pdb, r);
};
}
private String getOther(AbstractPdb pdb, RecordNumber r) {
AbstractMsType type = pdb.getTypeRecord(r);
StringBuilder builder = new StringBuilder();
if (type instanceof PrimitiveMsType pt) {
// Place to set break point during development
int a = 1;
a = a + 1;
}
if (type instanceof AbstractPointerMsType pt) {
builder.append('p');
AbstractMsType t = pdb.getTypeRecord(pt.getUnderlyingRecordNumber());
if (t instanceof AbstractModifierMsType mt) {
AbstractMsType modified = pdb.getTypeRecord(mt.getModifiedRecordNumber());
String str = mt.toString();
if (str.contains("const")) {
builder.append("const");
}
if (str.contains("volatile")) {
builder.append("volatile");
}
builder.append(modified.toString());
}
else {
builder.append(t.toString());
}
}
else {
builder.append(type.toString());
}
return builder.toString();
}
private void processMethods(List<String> commandStrings, AbstractPdb pdb,
List<MsTypeField> methods) {
for (MsTypeField methodType : methods) {
if (methodType instanceof AbstractOneMethodMsType oneMethodType) {
String name = oneMethodType.getName();
ClassFieldMsAttributes attributes = oneMethodType.getAttributes();
RecordNumber procedureTypeRn = oneMethodType.getProcedureTypeRecordNumber();
AbstractMsType t = pdb.getTypeRecord(procedureTypeRn);
if (!(t instanceof AbstractMemberFunctionMsType memberFunc)) {
Msg.warn(this, "Unexpected type found: " + t.getClass().getSimpleName());
continue;
}
int adjuster = memberFunc.getThisAdjuster();
Long offset = oneMethodType.getOffsetInVFTableIfIntroVirtual();
ClassFieldAttributes atts =
ClassFieldAttributes.convert(attributes, Access.BLANK);
if (atts.getProperty() == Property.VIRTUAL) {
commandStrings.add(
getMethodString(pdb, adjuster, offset.intValue(), name, memberFunc));
}
}
else if (methodType instanceof AbstractOverloadedMethodMsType overloadedMethodType) {
String name = overloadedMethodType.getName();
RecordNumber methodsListRn =
overloadedMethodType.getTypeMethodListRecordNumber();
AbstractMsType methodsListTry = pdb.getTypeRecord(methodsListRn);
if (methodsListTry instanceof AbstractMethodListMsType methodsListType) {
List<AbstractMethodRecordMs> recordList = methodsListType.getList();
for (AbstractMethodRecordMs methodRecord : recordList) {
Long offset = methodRecord.getOptionalOffset();
RecordNumber procedureTypeRn = methodRecord.getProcedureTypeRecordNumber();
ClassFieldMsAttributes attributes = methodRecord.getAttributes();
AbstractMsType t = pdb.getTypeRecord(procedureTypeRn);
if (!(t instanceof AbstractMemberFunctionMsType memberFunc)) {
Msg.warn(this,
"Unexpected type found: " + t.getClass().getSimpleName());
continue;
}
int adjuster = memberFunc.getThisAdjuster();
ClassFieldAttributes atts =
ClassFieldAttributes.convert(attributes, Access.BLANK);
if (atts.getProperty() == Property.VIRTUAL) {
commandStrings.add(getMethodString(pdb, adjuster, offset.intValue(),
name, memberFunc));
}
}
}
else {
Msg.warn(this, "Unexexpected method list type: " +
methodsListTry.getClass().getSimpleName());
}
}
else {
Msg.warn(this,
"Unexexpected method type: " + methodType.getClass().getSimpleName());
}
}
}
private String getMethodString(AbstractPdb pdb, int adjuster, int offset, String methodName,
AbstractMemberFunctionMsType memberFunc) {
return String.format(
"struct.addVirtualMethod(%d, %d, new SymbolPath(classSp, \"%s\"), %s);",
adjuster, offset, methodName, getSig(pdb, memberFunc));
}
private String getSig(AbstractPdb pdb, AbstractMemberFunctionMsType type) {
RecordNumber rn = type.getArgListRecordNumber();
StringBuilder builder = new StringBuilder();
builder.append('f');
String rString = getTypeName(pdb, type.getReturnRecordNumber());
builder.append(rString);
type.getReturnRecordNumber();
AbstractMsType lType = pdb.getTypeRecord(rn);
if (lType instanceof PrimitiveMsType primitive && primitive.isNoType()) {
// Arguments list is empty. (There better not have been any arguments up until
// now.)
builder.append("void");
}
else if (lType instanceof AbstractArgumentsListMsType argsList) {
List<RecordNumber> argNumbers = argsList.getArgRecordNumbers();
if (argNumbers.isEmpty()) {
builder.append("void");
}
else {
for (RecordNumber argNumber : argNumbers) {
AbstractMsType aType = pdb.getTypeRecord(argNumber);
if (aType instanceof PrimitiveMsType primitive && primitive.isNoType()) {
// Arguments list is empty. (There better not have been any arguments
// up until now.)
builder.append("void");
break;
}
String aString = getTypeName(pdb, argNumber);
builder.append(aString);
}
}
}
builder.append('T');
return builder.toString();
}
private String getAttsString(ClassFieldMsAttributes msAtts) {
// Note that the conversion here incorporates multiple MS atts into Virtual. If that
// changes, we need to change logic here too
ClassFieldAttributes atts = ClassFieldAttributes.convert(msAtts, Access.BLANK);
if (atts.getAccess().equals(Access.PUBLIC)) {
return Property.VIRTUAL.equals(atts.getProperty()) ? "publicVirtualAttributes"
: "publicDirectAttributes";
}
if (atts.getAccess().equals(Access.PRIVATE)) {
return Property.VIRTUAL.equals(atts.getProperty()) ? "privateVirtualAttributes"
: "privateDirectAttributes";
}
if (atts.getAccess().equals(Access.PROTECTED)) {
return Property.VIRTUAL.equals(atts.getProperty()) ? "protectedVirtualAttributes"
: "protectedDirectAttributes";
}
return "UNHANDLED_ATTRIBUTES";
}
}

View file

@ -57,6 +57,7 @@ public class CppCompositeType {
private Composite composite; private Composite composite;
private Composite selfBaseType; private Composite selfBaseType;
private Map<String, String> vxtPtrSummary;
private String summarizedClassVxtPtrInfo; private String summarizedClassVxtPtrInfo;
// Order matters for both base classes and members for class layout. Members get offsets, // Order matters for both base classes and members for class layout. Members get offsets,
@ -585,7 +586,23 @@ public class CppCompositeType {
* @return the summary * @return the summary
*/ */
String getSummarizedClassVxtPtrInfo() { String getSummarizedClassVxtPtrInfo() {
return summarizedClassVxtPtrInfo; if (vxtPtrSummary.isEmpty()) {
return "";
}
StringBuilder builder = new StringBuilder();
builder.append(String.format("Class: %s\n", getSymbolPath().toString()));
for (String value : vxtPtrSummary.values()) {
builder.append(value);
}
return builder.toString();
}
/**
* Return developer VxtPtr summary for this class
* @return the summary
*/
Map<String, String> getVxtPtrSummary() {
return vxtPtrSummary;
} }
/** /**
@ -847,6 +864,8 @@ public class CppCompositeType {
finalVbtPtrInfoByOffset = new TreeMap<>(); finalVbtPtrInfoByOffset = new TreeMap<>();
finalVftByOffset = new TreeMap<>(); finalVftByOffset = new TreeMap<>();
finalVbtByOffset = new TreeMap<>(); finalVbtByOffset = new TreeMap<>();
vxtPtrSummary = new TreeMap<>();
} }
/** /**
@ -898,21 +917,29 @@ public class CppCompositeType {
} }
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
Map<String, String> results = new TreeMap<>();
for (VxtPtrInfo info : finalVftPtrInfoByOffset.values()) { for (VxtPtrInfo info : finalVftPtrInfoByOffset.values()) {
List<ClassID> altParentage = List<ClassID> altParentage =
finalizeVxtPtrParentage(vftChildToParentRoot, vftParentToChildRoot, info); finalizeVxtPtrParentage(vftChildToParentRoot, vftParentToChildRoot, info);
builder.append(dumpVxtPtrResult("vft", info, altParentage)); String name = ClassUtils.getSpecialVxTableName(info.finalOffset);
String result = dumpVxtPtrResult("vft", info, altParentage.reversed());
builder.append(result + "\n");
results.put(name, result);
} }
for (VxtPtrInfo info : finalVbtPtrInfoByOffset.values()) { for (VxtPtrInfo info : finalVbtPtrInfoByOffset.values()) {
List<ClassID> altParentage = List<ClassID> altParentage =
finalizeVxtPtrParentage(vbtChildToParentRoot, vbtParentToChildRoot, info); finalizeVxtPtrParentage(vbtChildToParentRoot, vbtParentToChildRoot, info);
builder.append(dumpVxtPtrResult("vbt", info, altParentage)); String name = ClassUtils.getSpecialVxTableName(info.finalOffset);
String result = dumpVxtPtrResult("vbt", info, altParentage.reversed());
builder.append(result + "\n");
results.put(name, result);
} }
if (!builder.isEmpty()) { if (!builder.isEmpty()) {
builder.insert(0, String.format("Class: %s\n", getSymbolPath().toString())); builder.insert(0, String.format("Class: %s\n", getSymbolPath().toString()));
} }
summarizedClassVxtPtrInfo = builder.toString(); summarizedClassVxtPtrInfo = builder.toString();
vxtPtrSummary = results;
} }
/** /**
@ -976,7 +1003,7 @@ public class CppCompositeType {
String name = id.getSymbolPath().toString(); String name = id.getSymbolPath().toString();
r2.add(name); r2.add(name);
} }
return String.format(" %4d %s %s\t%s\n", info.finalOffset(), vxt, r1.toString(), return String.format(" %4d %s %s\t%s", info.finalOffset(), vxt, r1.toString(),
r2.toString()); r2.toString());
} }
@ -1237,8 +1264,7 @@ public class CppCompositeType {
createSelfOwnedDirectVxtPtrInfo(parentInfo, baseId, baseOffset); createSelfOwnedDirectVxtPtrInfo(parentInfo, baseId, baseOffset);
updateVft(vxtManager, baseId, newInfo, parentInfo); updateVft(vxtManager, baseId, newInfo, parentInfo);
storeVxtInfo(propagatedSelfBaseVfts, finalVftPtrInfoByOffset, storeVxtInfo(propagatedSelfBaseVfts, finalVftPtrInfoByOffset,
vftTableIdByOffset, vftTableIdByOffset, vftOffsetByTableId, newInfo);
vftOffsetByTableId, newInfo);
} }
} }
if (cppBaseType.getPropagatedSelfBaseVbts() != null) { if (cppBaseType.getPropagatedSelfBaseVbts() != null) {
@ -1247,8 +1273,7 @@ public class CppCompositeType {
createSelfOwnedDirectVxtPtrInfo(parentInfo, baseId, baseOffset); createSelfOwnedDirectVxtPtrInfo(parentInfo, baseId, baseOffset);
updateVbt(vxtManager, baseId, newInfo, parentInfo); updateVbt(vxtManager, baseId, newInfo, parentInfo);
storeVxtInfo(propagatedSelfBaseVbts, finalVbtPtrInfoByOffset, storeVxtInfo(propagatedSelfBaseVbts, finalVbtPtrInfoByOffset,
vbtTableIdByOffset, vbtTableIdByOffset, vbtOffsetByTableId, newInfo);
vbtOffsetByTableId, newInfo);
} }
} }
} }

View file

@ -0,0 +1,24 @@
/* ###
* 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;
/**
* Address Name and Bytes
* @param addr the address in String format
* @param name the name in String format
* @param bytes the bytes in String format
*/
public record AddressNameBytes(String addr, String name, String bytes) {}

View file

@ -0,0 +1,24 @@
/* ###
* 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;
/**
* Address Name and Bytes
* @param addr the address in String format
* @param name the name in String format
* @param length the length of the section
*/
public record AddressNameLength(String addr, String name, int length) {}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,208 @@
/* ###
* 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;
import java.util.*;
import ghidra.app.cmd.label.SetLabelPrimaryCmd;
import ghidra.app.util.*;
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbException;
import ghidra.app.util.pdb.classtype.MsftVxtManager;
import ghidra.app.util.pdb.pdbapplicator.CppCompositeType;
import ghidra.app.util.pdb.pdbapplicator.ObjectOrientedClassLayout;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
import ghidra.program.model.mem.DumbMemBufferImpl;
import ghidra.program.model.symbol.*;
import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
/**
* Mock PDB for testing
*/
public class MockPdb {
private Set<DataType> types;
private Set<CppCompositeType> cppTypes;
private Map<Address, DataType> typesByAddress;
private Map<Address, Set<SymbolPath>> symbolsByAddress;
private Map<DataType, DataType> resolvedMap;
public MockPdb() {
types = new LinkedHashSet<>();
cppTypes = new LinkedHashSet<>();
typesByAddress = new TreeMap<>();
symbolsByAddress = new TreeMap<>();
resolvedMap = new HashMap<>();
}
public void addSymbol(Address addr, String name) {
Set<SymbolPath> symbols = symbolsByAddress.get(addr);
if (symbols == null) {
symbols = new LinkedHashSet<>();
symbolsByAddress.put(addr, symbols);
}
SymbolPath symbol = new SymbolPath(SymbolPathParser.parse(name));
symbols.add(symbol);
}
/**
* Method to add a type. User should add types in dependency order, starting with leaves
* @param type the type to add
*/
public void addType(DataType type) {
types.add(type);
}
public void addType(CppCompositeType type) {
cppTypes.add(type);
}
public void applyAll(Program program, DataTypeManager dtm,
ObjectOrientedClassLayout layoutOptions, MsftVxtManager vxtManager, TaskMonitor monitor)
throws CancelledException, PdbException {
resolveRegularTypes(dtm);
applyCppTypes(layoutOptions, vxtManager, monitor);
resolveCppTypes(dtm);
if (program != null) {
placeTypes(program);
applySymbols(program);
}
}
public Set<CppCompositeType> getCppTypes() {
return cppTypes;
}
public void applyCppTypes(ObjectOrientedClassLayout layoutOptions,
MsftVxtManager vxtManager, TaskMonitor monitor)
throws PdbException, CancelledException {
for (CppCompositeType type : cppTypes) {
type.createLayout(layoutOptions, vxtManager, monitor);
}
}
public void applyCppType(CppCompositeType type, ObjectOrientedClassLayout layoutOptions,
MsftVxtManager vxtManager, TaskMonitor monitor)
throws PdbException, CancelledException {
type.createLayout(layoutOptions, vxtManager, monitor);
}
/**
* Method to resolve all regular types
* @param dtm the data type manager
*/
private void resolveRegularTypes(DataTypeManager dtm) {
for (DataType type : types) {
DataType resolved = dtm.resolve(type, null);
resolvedMap.put(type, resolved);
}
}
/**
* Method to resolve all CppTypes types
* @param dtm the data type manager
*/
public void resolveCppTypes(DataTypeManager dtm) {
for (CppCompositeType cppType : cppTypes) {
resolveType(dtm, cppType);
}
}
public Composite resolveType(DataTypeManager dtm, CppCompositeType cppType) {
Composite type = cppType.getComposite();
Composite resolved = (Composite) dtm.resolve(type, null);
resolvedMap.put(type, resolved);
return resolved;
}
private void placeTypes(Program program) {
for (Map.Entry<Address, DataType> entry : typesByAddress.entrySet()) {
Address addr = entry.getKey();
DataType resolvedType = resolvedMap.get(entry.getValue());
placeType(program, addr, resolvedType);
}
}
private void placeType(Program program, Address addr, DataType type) {
DumbMemBufferImpl memBuffer =
new DumbMemBufferImpl(program.getMemory(), addr);
DataTypeInstance dti = DataTypeInstance.getDataTypeInstance(type, memBuffer, false);
if (dti == null) {
Msg.warn(MockPdb.class,
"Error: Failed to apply datatype " + type.getName() + " at " + addr);
return;
}
DataType dt = dti.getDataType();
int length = dti.getLength();
try {
program.getListing().clearCodeUnits(addr, addr.add(length - 1), false);
if (dt.getLength() == -1) {
program.getListing().createData(addr, dt, length);
}
else {
program.getListing().createData(addr, dt);
}
}
catch (CodeUnitInsertionException e) {
Msg.warn(MockPdb.class, "Unable to create " + dt.getDisplayName() + " at 0x" +
addr + ": " + e.getMessage());
}
}
public void applySymbols(Program program) {
for (Map.Entry<Address, Set<SymbolPath>> entry : symbolsByAddress.entrySet()) {
Address addr = entry.getKey();
for (SymbolPath sp : entry.getValue()) {
applySymbol(program, addr, sp, false);
}
}
}
public Symbol applySymbol(Program program, Address address, SymbolPath symbolPath,
boolean makePrimary) {
Symbol symbol = null;
try {
Namespace namespace = program.getGlobalNamespace();
String name = symbolPath.getName();
String namespacePath = symbolPath.getParentPath();
if (namespacePath != null) {
namespace = NamespaceUtils.createNamespaceHierarchy(namespacePath, namespace,
program, address, SourceType.IMPORTED);
}
symbol =
program.getSymbolTable().createLabel(address, name, namespace, SourceType.IMPORTED);
if (makePrimary && !symbol.isPrimary()) {
SetLabelPrimaryCmd cmd =
new SetLabelPrimaryCmd(address, symbol.getName(),
symbol.getParentNamespace());
cmd.applyTo(program);
}
}
catch (InvalidInputException e) {
Msg.warn(MockPdb.class,
"Unable to create symbol at " + address + " due to exception: " +
e.toString() + "; symbolPathName: " + symbolPath);
}
return symbol;
}
}

View file

@ -0,0 +1,236 @@
/* ###
* 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;
import java.util.*;
import ghidra.app.util.*;
import ghidra.app.util.bin.format.pdb2.pdbreader.PdbException;
import ghidra.app.util.pdb.classtype.*;
import ghidra.app.util.pdb.pdbapplicator.CppCompositeType;
import ghidra.program.database.ProgramBuilder;
import ghidra.program.database.ProgramDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.lang.CompilerSpec;
import ghidra.program.model.listing.Program;
import ghidra.util.Msg;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.InvalidInputException;
/**
* Class to create the cvf4 program
*/
abstract public class ProgramCreator {
protected final static Pointer pointer = new PointerDataType();
protected final static DataType voidT = VoidDataType.dataType;
protected final static DataType charT = CharDataType.dataType;
protected final static DataType shortT = ShortDataType.dataType;
protected final static DataType intT = IntegerDataType.dataType;
protected final static DataType unsignedT = UnsignedIntegerDataType.dataType;
protected final static DataType longT = LongDataType.dataType;
protected final static DataType longlongT = LongLongDataType.dataType;
protected final static DataType floatT = FloatDataType.dataType;
protected final static DataType doubleT = DoubleDataType.dataType;
protected final static DataType longdoubleT = LongDoubleDataType.dataType;
protected final static DataType pcharT = new PointerDataType(charT);
protected final static DataType pvoidT = new PointerDataType(voidT);
protected final static FunctionDefinitionDataType fvoidvoidT = new FunctionDefinitionDataType(
CategoryPath.ROOT, "_func", intT.getDataTypeManager());
protected final static FunctionDefinitionDataType fintvoidT = new FunctionDefinitionDataType(
CategoryPath.ROOT, "_func", intT.getDataTypeManager());
protected final static FunctionDefinitionDataType fintintT = new FunctionDefinitionDataType(
CategoryPath.ROOT, "_func", intT.getDataTypeManager());
protected final static FunctionDefinitionDataType fpvoidunsignedT =
new FunctionDefinitionDataType(
CategoryPath.ROOT, "_func", intT.getDataTypeManager());
static {
try {
ParameterDefinition parameterDefinition;
fvoidvoidT.setCallingConvention(CompilerSpec.CALLING_CONVENTION_thiscall);
fintvoidT.setReturnType(voidT);
fintvoidT.setArguments(new ParameterDefinition[] {});
DataTypeNamingUtil.setMangledAnonymousFunctionName(fvoidvoidT);
fintvoidT.setCallingConvention(CompilerSpec.CALLING_CONVENTION_thiscall);
fintvoidT.setReturnType(intT);
fintvoidT.setArguments(new ParameterDefinition[] {});
DataTypeNamingUtil.setMangledAnonymousFunctionName(fintvoidT);
fintintT.setCallingConvention(CompilerSpec.CALLING_CONVENTION_thiscall);
fintintT.setReturnType(intT);
parameterDefinition = new ParameterDefinitionImpl("val", intT, "");
fintintT.setArguments(new ParameterDefinition[] { parameterDefinition });
DataTypeNamingUtil.setMangledAnonymousFunctionName(fintintT);
fpvoidunsignedT.setCallingConvention(CompilerSpec.CALLING_CONVENTION_thiscall);
fpvoidunsignedT.setReturnType(pvoidT);
parameterDefinition = new ParameterDefinitionImpl("val", unsignedT, "");
fintintT.setArguments(new ParameterDefinition[] { parameterDefinition });
DataTypeNamingUtil.setMangledAnonymousFunctionName(fpvoidunsignedT);
}
catch (InvalidInputException e) {
//
}
}
protected final ClassFieldAttributes publicVirtualAttributes =
ClassFieldAttributes.get(Access.PUBLIC, Property.VIRTUAL);
protected static ClassFieldAttributes publicDirectAttributes =
ClassFieldAttributes.get(Access.PUBLIC, Property.BLANK);
protected final ClassFieldAttributes protectedVirtualAttributes =
ClassFieldAttributes.get(Access.PROTECTED, Property.VIRTUAL);
protected static ClassFieldAttributes protectedDirectAttributes =
ClassFieldAttributes.get(Access.PROTECTED, Property.BLANK);
protected final ClassFieldAttributes privateVirtualAttributes =
ClassFieldAttributes.get(Access.PRIVATE, Property.VIRTUAL);
protected static ClassFieldAttributes privateDirectAttributes =
ClassFieldAttributes.get(Access.PRIVATE, Property.BLANK);
protected static CppCompositeType createStruct(DataTypeManager dtm, String name, int size) {
Composite composite = new StructureDataType(CategoryPath.ROOT, name, 0, dtm);
SymbolPath symbolPath = new SymbolPath(name);
String mangledName = createMangledName(name, ClassKey.STRUCT);
return CppCompositeType.createCppStructType(CategoryPath.ROOT, symbolPath, composite, name,
mangledName, size);
}
protected static String createMangledName(String className, ClassKey key) {
StringBuilder builder = new StringBuilder();
builder.append(".?A");
switch (key) {
case UNION:
builder.append('T');
break;
case STRUCT:
builder.append('U');
break;
case CLASS:
builder.append('V');
break;
default:
String msg = "Cannot handle type during testing" + key;
Msg.error(null, msg);
throw new AssertException(msg);
}
builder.append(className);
builder.append("@@");
return builder.toString();
}
protected static String convertCommentsToSpeculative(String original) {
return original.replace("Virtual Base", "Virtual Base - Speculative Placement");
}
private String programName;
private String languageId;
private String compilerSpec;
private AddressNameLength sections[];
private AddressNameBytes vbtInfo[];
private AddressNameBytes vftInfo[];
private AddressNameBytes functionInfo[];
public static SymbolPath sp(String s) {
return new SymbolPath(SymbolPathParser.parse(s));
}
public ProgramCreator(String programName, String languageId, String compilerSpec,
AddressNameLength[] sections, AddressNameBytes[] vbtInfo, AddressNameBytes[] vftInfo,
AddressNameBytes[] functionInfo) {
this.programName = programName;
this.languageId = languageId;
this.compilerSpec = compilerSpec;
this.sections = sections;
this.vbtInfo = vbtInfo;
this.vftInfo = vftInfo;
this.functionInfo = functionInfo;
}
abstract protected List<DataType> getRegularTypes(DataTypeManager dtm) throws PdbException;
abstract protected List<CppCompositeType> getCppTypes(DataTypeManager dtm) throws PdbException;
public ProgramTestArtifacts create() throws Exception {
MockPdb pdb = new MockPdb();
ProgramBuilder builder =
new ProgramBuilder(programName, languageId, compilerSpec, this);
for (AddressNameLength info : sections) {
builder.createMemory(info.name(), info.addr(), info.length());
}
Map<String, Address> addressByMangled = getAddressByMangledName(builder);
createVxTables(builder, pdb);
addTypeInfo(builder, pdb);
addFunctionInfo(builder, pdb);
ProgramDB program = builder.getProgram();
return new ProgramTestArtifacts(program, pdb, addressByMangled);
}
public Map<String, Address> getAddressByMangledName(ProgramBuilder builder) {
Map<String, Address> addressesByMangledName = new HashMap<>();
for (AddressNameBytes addressNameBytes : vbtInfo) {
Address addr = builder.addr(addressNameBytes.addr());
addressesByMangledName.put(addressNameBytes.name(), addr);
}
for (AddressNameBytes addressNameBytes : vftInfo) {
Address addr = builder.addr(addressNameBytes.addr());
addressesByMangledName.put(addressNameBytes.name(), addr);
}
return addressesByMangledName;
}
private void createVxTables(ProgramBuilder builder, MockPdb pdb) throws Exception {
for (AddressNameBytes tableInfo : vbtInfo) {
builder.setBytes(tableInfo.addr(), tableInfo.bytes());
pdb.addSymbol(builder.addr(tableInfo.addr()), tableInfo.name());
}
for (AddressNameBytes tableInfo : vftInfo) {
builder.setBytes(tableInfo.addr(), tableInfo.bytes());
pdb.addSymbol(builder.addr(tableInfo.addr()), tableInfo.name());
}
}
private void addTypeInfo(ProgramBuilder builder, MockPdb pdb) throws Exception {
Program program = builder.getProgram();
DataTypeManager dtm = program.getDataTypeManager();
for (DataType type : getRegularTypes(dtm)) {
pdb.addType(type);
}
for (CppCompositeType cppType : getCppTypes(dtm)) {
pdb.addType(cppType);
}
}
private void addFunctionInfo(ProgramBuilder builder, MockPdb pdb) throws Exception {
for (AddressNameBytes funcInfo : functionInfo) {
builder.setBytes(funcInfo.addr(), funcInfo.bytes());
pdb.addSymbol(builder.addr(funcInfo.addr()), funcInfo.name());
}
}
}

View file

@ -0,0 +1,30 @@
/* ###
* 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;
import java.util.Map;
import ghidra.program.model.address.Address;
import ghidra.program.model.listing.Program;
/**
* Results of a program-build
* @param program the Program
* @param pdb the Mock PDB
* @param addressesByMangled the map of addresses for mangled vxt names
*/
public record ProgramTestArtifacts(Program program, MockPdb pdb,
Map<String, Address> addressesByMangled) {}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -128,6 +128,43 @@ public class ClassUtils {
return String.format("%s%08x", VTABLE_PREFIX, ptrOffsetInClass); return String.format("%s%08x", VTABLE_PREFIX, ptrOffsetInClass);
} }
/**
* Indicates whether a label satisfies the format of a vxtable label
* @param type the data type
* @return {@code true} if is a vxtable label format
*/
public static boolean isVTable(DataType type) {
if (!(type instanceof Structure)) {
return false;
}
String name = type.getName();
return validateVtableNameOffset(name) != null;
}
/**
* Validates a Vtable name and returns the encoded offset value
* @param name the name
* @return the offset or {@code null} if invalid name
*/
private static Integer validateVtableNameOffset(String name) {
if (name == null) {
return null;
}
if (!name.startsWith(VTABLE_PREFIX)) {
return null;
}
if (name.length() < VTABLE_PREFIX.length() + 8) {
return null;
}
String sub = name.substring(VTABLE_PREFIX.length(), VTABLE_PREFIX.length() + 8);
try {
return Integer.parseInt(sub, 16);
}
catch (NumberFormatException e) {
return null;
}
}
public static DataType getVftDefaultEntry(DataTypeManager dtm) { public static DataType getVftDefaultEntry(DataTypeManager dtm) {
return new PointerDataType(dtm); return new PointerDataType(dtm);
} }