Skip to main content
The FunctionManager plugin manages all functions discovered in a binary, providing access to function metadata, call graphs, and function relationships.

FunctionManager Class

class FunctionManager(KnowledgeBasePlugin, collections.abc.Mapping[K, Function]):
    """Manages functions with optional LRU caching and LMDB spilling."""

Constructor

kb
KnowledgeBase
required
The knowledge base instance this manager belongs to
cache_limit
int | None
default:"None"
Maximum number of functions to keep in memory. None means unlimited (no eviction). When set, uses SpillingFunctionDict with LRU caching.
# Unlimited functions in memory
fm = FunctionManager(kb)

# Limit to 1000 functions in memory
fm = FunctionManager(kb, cache_limit=1000)

Properties

callgraph
networkx.MultiDiGraph[int]
The call graph of all functions. Nodes are function addresses, edges represent calls between functions.
function_address_types
type
The valid types for function addresses (e.g., int or SootMethodDescriptor)
address_types
type
The valid types for addresses in this architecture

Accessing Functions

Dictionary-Style Access

FunctionManager implements the Mapping protocol, so you can use it like a dictionary:
# Access by address
func = kb.functions[0x401000]

# Access by name
func = kb.functions['main']

# Check if function exists
if 0x401000 in kb.functions:
    print("Function exists")

# Get number of functions
num_funcs = len(kb.functions)

# Iterate over all functions
for addr in kb.functions:
    func = kb.functions[addr]
    print(f"{hex(addr)}: {func.name}")

# Iterate over address, function pairs
for addr, func in kb.functions.items():
    print(f"{hex(addr)}: {func.name}")

function

kb.functions.function(
    addr: int | None = None,
    name: str | None = None,
    check_previous_names: bool = False,
    create: bool = False,
    syscall: bool = False,
    plt: bool | None = None
) -> Function | None
Get a function by address or name.
addr
int
default:"None"
Address of the function
name
str
default:"None"
Name of the function. Supports special syntax like sub_401000 for addresses.
check_previous_names
bool
default:"False"
Also search in previous names (for renamed functions)
create
bool
default:"False"
Create the function if it doesn’t exist
syscall
bool
default:"False"
Mark as syscall if creating a new function
plt
bool | None
default:"None"
Filter by PLT status: True for PLT stubs, False for non-PLT, None for any
Returns: Function instance or None if not found
# Get function by address
func = kb.functions.function(addr=0x401000)

# Get or create function
func = kb.functions.function(addr=0x401000, create=True)

# Get function by name
main = kb.functions.function(name='main')

# Filter PLT functions
plt_func = kb.functions.function(addr=0x401000, plt=True)

get_by_addr

kb.functions.get_by_addr(addr: int, meta_only: bool = False) -> Function
Get a function by address.
addr
int
required
Address of the function
meta_only
bool
default:"False"
If True, return a lightweight meta-only Function (when using spilling). Meta-only functions contain basic information but not full graph data.
Returns: Function instance Raises: KeyError if function doesn’t exist

get_by_name

kb.functions.get_by_name(
    name: str,
    check_previous_names: bool = False
) -> Generator[Function]
Get all functions with the given name (yields multiple functions if name is not unique).
name
str
required
Name of the function to search for
check_previous_names
bool
default:"False"
Also search previous names of renamed functions
Returns: Generator yielding Function instances
# Get all functions named 'helper'
for func in kb.functions.get_by_name('helper'):
    print(f"Found helper at {hex(func.addr)}")

# Include previous names in search
for func in kb.functions.get_by_name('old_name', check_previous_names=True):
    print(f"Function was previously named 'old_name': {func.name}")

get_addrs_by_name

kb.functions.get_addrs_by_name(
    name: str,
    check_previous_names: bool = False
) -> set[int]
Get addresses of all functions with the given name.
name
str
required
Name of the function
check_previous_names
bool
default:"False"
Include previous names in search
Returns: Set of function addresses

Querying Functions

contains_addr

kb.functions.contains_addr(addr: int) -> bool
Check if an address is managed by the function manager.

query

kb.functions.query(query: str, check_previous_names: bool = False) -> Function | None
Query for a function using selectors. Supported formats:
  • ::<name> - Function in the main object
  • ::<addr>::<name> - Function at specific address
  • ::<obj>::<name> - Function in specific object
query
str
required
Query string with selector syntax
check_previous_names
bool
default:"False"
Search previous names
Returns: Function instance or None
# Query by name in main object
func = kb.functions.query('::main')

# Query by address and name
func = kb.functions.query('::0x401000::main')

# Query by object and name
func = kb.functions.query('::libc.so::malloc')

Floor and Ceiling Functions

kb.functions.floor_addr(addr: int) -> int | None
kb.functions.floor_func(addr: int) -> Function | None
kb.functions.ceiling_addr(addr: int) -> int | None
kb.functions.ceiling_func(addr: int) -> Function | None
Returns the function with the greatest address ≤ addr.
# Find function at or before address
addr = kb.functions.floor_addr(0x401234)
if addr:
    func = kb.functions[addr]
Returns the function with the smallest address ≥ addr.
# Find function at or after address
addr = kb.functions.ceiling_addr(0x401234)
if addr:
    func = kb.functions[addr]

Iterating Functions

items

kb.functions.items(meta_only: bool = False) -> Generator[tuple[int, Function]]
Iterate over (address, function) pairs.
meta_only
bool
default:"False"
Yield meta-only functions (lightweight versions)

values

kb.functions.values(meta_only: bool = False) -> Generator[Function]
Iterate over all functions.
# Iterate all functions
for func in kb.functions.values():
    print(f"{func.name}: {func.size} bytes")

# Iterate meta-only (faster for large binaries)
for func in kb.functions.values(meta_only=True):
    print(func.name)  # Can access basic properties only

Modifying Functions

Adding Functions

# Create new function
kb.functions[0x401000] = Function(kb.functions, 0x401000, name='my_func')

# Or use function() with create=True
func = kb.functions.function(addr=0x401000, create=True)

Deleting Functions

# Delete by address
del kb.functions[0x401000]

function_name_changed

kb.functions.function_name_changed(
    addr: int,
    old_name: str | None,
    new_name: str
) -> None
Notify the manager that a function’s name has changed (internal use).

Call Graph Operations

The callgraph property provides access to the function call graph:
import networkx as nx

# Get all callers of a function
callers = list(kb.functions.callgraph.predecessors(0x401000))

# Get all callees
callees = list(kb.functions.callgraph.successors(0x401000))

# Check if function A calls function B
if kb.functions.callgraph.has_edge(0x401000, 0x402000):
    print("Function 0x401000 calls 0x402000")

# Get edge data
for src, dst, data in kb.functions.callgraph.edges(data=True):
    edge_type = data.get('type', 'call')
    print(f"{hex(src)} -> {hex(dst)} ({edge_type})")

Memory Management (Spilling)

When cache_limit is set, FunctionManager uses SpillingFunctionDict to manage memory:

Cache Properties

if hasattr(kb.functions._function_map, 'cache_limit'):
    fm = kb.functions._function_map
    print(f"Cache limit: {fm.cache_limit}")
    print(f"Cached count: {fm.cached_count}")
    print(f"Spilled count: {fm.spilled_count}")
    print(f"Total count: {fm.total_count}")
cache_limit
int
Maximum functions in memory
cached_count
int
Number of functions currently in memory
spilled_count
int
Number of functions spilled to LMDB
total_count
int
Total functions (cached + spilled)

Manual Cache Control

fm = kb.functions._function_map

# Load all spilled functions
fm.load_all_spilled()

# Evict all cached functions
fm.evict_all_cached()

# Adjust cache limit
fm.cache_limit = 2000

Usage Examples

Finding Functions

# Find all non-returning functions
for func in kb.functions.values():
    if func.returning is False:
        print(f"Non-returning: {func.name} at {hex(func.addr)}")

# Find all PLT functions
for addr, func in kb.functions.items():
    if func.is_plt:
        print(f"PLT: {func.name}")

Call Graph Analysis

import networkx as nx

# Find leaf functions (no callees)
leaves = [n for n in kb.functions.callgraph.nodes() 
          if kb.functions.callgraph.out_degree(n) == 0]

# Find functions with most callers
callers_count = [(n, kb.functions.callgraph.in_degree(n)) 
                 for n in kb.functions.callgraph.nodes()]
most_called = max(callers_count, key=lambda x: x[1])
print(f"Most called: {hex(most_called[0])} ({most_called[1]} callers)")

# Get call chain between two functions
try:
    path = nx.shortest_path(kb.functions.callgraph, 0x401000, 0x402000)
    print(f"Call path: {' -> '.join(hex(a) for a in path)}")
except nx.NetworkXNoPath:
    print("No call path found")

Working with Large Binaries

# Use cache limit for large binaries
kb.functions = FunctionManager(kb, cache_limit=1000)

# Use meta_only for faster iteration
for func in kb.functions.values(meta_only=True):
    # Only access basic properties
    print(f"{hex(func.addr)}: {func.name}")
    # Don't access func.blocks or func.graph (not loaded)

Build docs developers (and LLMs) love