Skip to main content
Glama
FunctionCallGraphService.java6.2 kB
package com.ghidramcp.services; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Function; import ghidra.program.model.listing.Program; import ghidra.program.model.symbol.Reference; import ghidra.program.model.symbol.ReferenceIterator; import ghidra.program.model.symbol.RefType; import java.util.*; /** * Service for building and analyzing function call graphs */ public class FunctionCallGraphService { private final FunctionNavigator navigator; public FunctionCallGraphService(FunctionNavigator navigator) { this.navigator = navigator; } /** * Get a hierarchical tree of functions called by the given function * @param addressStr Starting function address * @param depth Maximum depth to traverse (default 1) * @return Formatted tree string */ public String getFunctionCallees(String addressStr, int depth) { Program program = navigator.getCurrentProgram(); if (program == null) return "No program loaded"; if (addressStr == null || addressStr.isEmpty()) return "Address is required"; if (depth < 1) return "Depth must be at least 1"; try { Address addr = program.getAddressFactory().getAddress(addressStr); Function func = program.getFunctionManager().getFunctionAt(addr); if (func == null) { func = program.getFunctionManager().getFunctionContaining(addr); } if (func == null) { return "No function found at address " + addressStr; } StringBuilder result = new StringBuilder(); Set<Address> visited = new HashSet<>(); // Build the tree starting from the root function buildCallTree(program, func, 0, depth, visited, result, "", true); return result.toString().trim(); } catch (Exception e) { return "Error building call graph: " + e.getMessage(); } } /** * Recursively build the call tree * @param program The program being analyzed * @param func The current function * @param currentDepth Current depth in the tree * @param maxDepth Maximum depth to traverse * @param visited Set of visited function addresses to detect cycles * @param result StringBuilder to accumulate the tree output * @param prefix Current line prefix for tree formatting * @param isLast Whether this is the last child at this level */ private void buildCallTree(Program program, Function func, int currentDepth, int maxDepth, Set<Address> visited, StringBuilder result, String prefix, boolean isLast) { Address entryPoint = func.getEntryPoint(); // Format the current function node if (currentDepth == 0) { // Root node result.append(String.format("%s (%s)\n", func.getName(), entryPoint)); } else { // Child node with tree characters String connector = isLast ? "└─" : "├─"; result.append(String.format("%s%s %s (%s)\n", prefix, connector, func.getName(), entryPoint)); } // Check if we've reached max depth or if we've already visited this function if (currentDepth >= maxDepth) { return; } // Mark as visited for this branch (but allow revisiting in other branches at same depth) if (visited.contains(entryPoint)) { // Show cycle detection String childPrefix = prefix + (isLast ? " " : "│ "); result.append(childPrefix).append("└─ [circular reference detected]\n"); return; } visited.add(entryPoint); // Get all call references from this function List<Function> callees = getCallees(program, func); // Process each callee for (int i = 0; i < callees.size(); i++) { Function callee = callees.get(i); boolean isLastCallee = (i == callees.size() - 1); // Calculate the prefix for the next level String childPrefix = prefix + (isLast ? " " : "│ "); // Recursively build the tree for this callee Set<Address> childVisited = new HashSet<>(visited); buildCallTree(program, callee, currentDepth + 1, maxDepth, childVisited, result, childPrefix, isLastCallee); } visited.remove(entryPoint); } /** * Get all functions called by the given function * @param program The program * @param func The function to analyze * @return List of called functions */ private List<Function> getCallees(Program program, Function func) { List<Function> callees = new ArrayList<>(); Set<Address> seenAddresses = new HashSet<>(); // Iterate through all addresses in the function body Address minAddr = func.getBody().getMinAddress(); Address maxAddr = func.getBody().getMaxAddress(); ReferenceIterator refIter = program.getReferenceManager().getReferenceIterator(minAddr); while (refIter.hasNext()) { Reference ref = refIter.next(); Address fromAddr = ref.getFromAddress(); // Only process references within this function's body if (fromAddr.compareTo(minAddr) < 0 || fromAddr.compareTo(maxAddr) > 0) { break; } RefType refType = ref.getReferenceType(); // Only process call-type references if (refType.isCall() || refType.isJump()) { Address toAddr = ref.getToAddress(); // Avoid duplicates if (seenAddresses.contains(toAddr)) { continue; } seenAddresses.add(toAddr); Function callee = program.getFunctionManager().getFunctionAt(toAddr); if (callee != null) { callees.add(callee); } } } // Sort by address for consistent output callees.sort(Comparator.comparing(f -> f.getEntryPoint())); return callees; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/HK47196/GhidraMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server