GP-6693 New script to add call reference for associated structure member vfunction token cursor location.

This commit is contained in:
ghidra007
2026-04-13 18:10:25 +00:00
parent 56429bcfdb
commit 08d4fcba59
@@ -0,0 +1,309 @@
/* ###
* 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.
*/
// This script requires the cursor to be on a vftable->function token in the decompiler and will
// create a CALL reference to the associated function in the listing if it is possible to identify a
// single corresponding applied vftable structure and identify the associated function in the
// listing.
//
//@category C++
import java.util.ArrayList;
import java.util.List;
import ghidra.app.decompiler.*;
import ghidra.app.decompiler.component.DecompilerUtils;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.address.Address;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.*;
import ghidra.program.model.mem.DumbMemBufferImpl;
import ghidra.program.model.mem.MemoryAccessException;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.CancelledException;
public class AddVfunctionCallRefScript extends GhidraScript {
private DecompInterface decomplib;
DecompileResults lastResults = null;
@Override
public void run() throws Exception {
try {
// get the decompiler context
// setup the decompiler
decomplib = setUpDecompiler(currentProgram);
if (decomplib == null) {
println("Could not setup the decompiler.");
return;
}
@SuppressWarnings("unused")
// get the function associated with the current cursor location in the decompiler
// if the cursor is not on a vftable->function construct then it will return null
// if a single function cannot be tied to this construct then return null
Function function = getFunctionAtLocation();
// previous function spits out appropriate error message if it returns null
if (function == null) {
return;
}
println("Associated function found: " + function.getEntryPoint());
Address associatedAddress = getAssociatedAddress();
if (associatedAddress == null) {
println("Cannot find single associated listing address to put reference on.");
return;
}
// add reference to the function on the CALL operand
addOperandReference(associatedAddress, function);
println("Reference added to " + associatedAddress.toString());
// uncomment this line to add a comment link
//setPreComment(associatedAddress, "{@symbol " + function.getEntryPoint() + "}");
}
finally {
if (decomplib != null) {
decomplib.dispose();
}
}
}
private void addOperandReference(Address address, Function function) {
Instruction instruction = currentProgram.getListing().getInstructionAt(address);
instruction.addOperandReference(0, function.getEntryPoint(), RefType.UNCONDITIONAL_CALL,
SourceType.ANALYSIS);
}
private Function getFunctionAtLocation() throws CancelledException {
if (currentLocation instanceof DecompilerLocation dloc) {
// get the Varnode under the cursor
ClangToken tokenAtCursor = dloc.getToken();
Structure structure = getStructure(tokenAtCursor);
if (structure == null) {
println(
"Cursor is not in a valid structure token. Put cursor on a decompiler structure->function name.");
return null;
}
println("Structure containing cursor token: " + structure.getDisplayName());
// check that the struct is all function pointers
if (!isVftableStructure(structure)) {
println("This script only works for structures that contain all function pointers");
return null;
}
List<Address> appliedData = getAppliedData(structure);
if (appliedData.size() == 0) {
println("No applied structure " + structure.getDisplayName() +
" so cannot find associated function.");
return null;
}
if (appliedData.size() > 1) {
println("Multiple applied structures " + structure.getDisplayName() +
" so cannot find associated function");
return null;
}
Address singleAppliedStructAddress = appliedData.get(0);
println("Single " + structure.getDisplayName() + " found at " +
singleAppliedStructAddress.toString());
String functionName = tokenAtCursor.toString();
Integer offsetOfFunction = getOrdinalOfFunction(structure, functionName);
if (offsetOfFunction == null) {
println("No function named " + functionName + " in structure " +
structure.getPathName() + " so cannot find associated function");
return null;
}
int defaultPointerSize = currentProgram.getDefaultPointerSize();
Address referencedAddress = getReferencedAddress(currentProgram,
singleAppliedStructAddress.add(defaultPointerSize * offsetOfFunction), true);
if (referencedAddress == null) {
println("No referenced address at offset " + offsetOfFunction);
return null;
}
Function function = currentProgram.getListing().getFunctionAt(referencedAddress);
if (function == null) {
println("No function at: " + referencedAddress.toString());
return null;
}
if (function.isThunk()) {
function = function.getThunkedFunction(true);
}
return function;
}
return null;
}
private Address getAssociatedAddress() {
if (currentLocation instanceof DecompilerLocation dloc) {
// get the Varnode under the cursor
ClangToken token = dloc.getToken();
// return the closest single associated address in the listing
return DecompilerUtils.getClosestAddress(currentProgram, token);
}
return null;
}
private Structure getStructure(ClangToken token) {
DataType dataType = DecompilerUtils.getDataType(token);
if (!(dataType instanceof Structure)) {
return null;
}
return (Structure) dataType;
}
private List<Address> getAppliedData(DataType dataType) throws CancelledException {
List<Address> appliedData = new ArrayList<>();
Listing listing = currentProgram.getListing();
DataIterator definedData = listing.getDefinedData(true);
while (definedData.hasNext()) {
monitor.checkCancelled();
Data nextData = definedData.next();
if (nextData.getDataType().equals(dataType)) {
appliedData.add(nextData.getAddress());
}
}
return appliedData;
}
private Integer getOrdinalOfFunction(Structure struct, String name) {
for (DataTypeComponent component : struct.getComponents()) {
DataType dataType = component.getDataType();
if (dataType instanceof Pointer ptr) {
dataType = ptr.getDataType();
}
if (dataType.getName().equals(name)) {
return component.getOrdinal();
}
}
return null;
}
// validate that structure contains only functionDef*'s
private boolean isVftableStructure(Structure struct) {
for (DataTypeComponent component : struct.getComponents()) {
DataType dataType = component.getDataType();
if (!(dataType instanceof Pointer ptr)) {
return false;
}
dataType = ptr.getDataType();
if (!(dataType instanceof FunctionDefinition)) {
return false;
}
}
return true;
}
private DecompInterface setUpDecompiler(Program program) {
DecompInterface decompInterface = new DecompInterface();
// call it to get results
if (!decompInterface.openProgram(currentProgram)) {
println("Decompile Error: " + decompInterface.getLastMessage());
return null;
}
DecompileOptions options = DecompilerUtils.getDecompileOptions(state.getTool(), program);
decompInterface.setOptions(options);
decompInterface.toggleCCode(true);
decompInterface.toggleSyntaxTree(true);
decompInterface.setSimplificationStyle("decompile");
return decompInterface;
}
/**
* Method to return the referenced address at the given address in the given program
* Note: this will work whether there is a created reference or not
* @param program the given program
* @param addr the address to look for a referenced address at
* @param getIboIf64bit if true, get the address corresponding to the image base offset instead
* of the full reference address
* @return the first referenced address from the given address
*/
public static Address getReferencedAddress(Program program, Address addr,
boolean getIboIf64bit) {
int addressSize = addr.getSize();
if (addressSize == 64 && getIboIf64bit) {
IBO32DataType ibo32 = new IBO32DataType(program.getDataTypeManager());
int length = ibo32.getLength();
DumbMemBufferImpl compMemBuffer = new DumbMemBufferImpl(program.getMemory(), addr);
Object value = ibo32.getValue(compMemBuffer, ibo32.getDefaultSettings(), length);
if (value instanceof Address iboAddress) {
return iboAddress;
}
return null;
}
long offset;
try {
if (addressSize == 32) {
Integer offset32 = program.getMemory().getInt(addr);
offset = Integer.toUnsignedLong(offset32);
}
else if (addressSize == 64) {
offset = program.getMemory().getLong(addr);
}
else {
return null;
}
Address newAddr = addr.getNewAddress(offset);
if (program.getMemory().contains(newAddr)) {
return newAddr;
}
return null;
}
catch (MemoryAccessException e) {
return null;
}
}
}