Source code for qbindiff.loader.function
# Copyright 2023 Quarkslab
#
# 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.
"""Function
"""
from __future__ import annotations
from collections.abc import Mapping
from typing import TYPE_CHECKING
from qbindiff.abstract import GenericNode
from qbindiff.loader import BasicBlock
from qbindiff.loader.types import FunctionType
if TYPE_CHECKING:
import networkx
from collections.abc import Generator, Iterator
from qbindiff.loader.backend.abstract import AbstractFunctionBackend
from qbindiff.types import Addr
[docs]
class Function(Mapping, GenericNode):
"""
Representation of a binary function.
This class is a non-mutable mapping between basic block's address and the basic block itself.
It lazily loads all the basic blocks when iterating through them or even accessing
one of them and it unloads all of them after the iteration has ended.
To keep a reference to the basic blocks the **with** statement can be used, for example:
.. code-block:: python
:linenos:
# func: Function
with func: # Loading all the basic blocks
for bb_addr, bb in func.items(): # Blocks are already loaded
pass
# The blocks are still loaded
for bb_addr, bb in func.items():
pass
# here the blocks have been unloaded
"""
def __init__(self, backend: AbstractFunctionBackend):
super().__init__()
# The basic blocks are lazily loaded
self._basic_blocks: dict[Addr, BasicBlock] | None = None
self._enable_unloading = True
self._backend = backend # Load directly from instanciated backend
[docs]
@staticmethod
def from_backend(backend: AbstractFunctionBackend) -> Function:
"""
Load the Function from an instanciated function backend object
"""
return Function(backend)
def __hash__(self):
return hash(self.addr)
def __enter__(self) -> None:
"""
Preload basic blocks and don't deallocate them until __exit__ is called
"""
self._enable_unloading = False
self._preload()
def __exit__(self, exc_type, exc_value, traceback) -> None:
"""
Deallocate all the basic blocks
"""
self._enable_unloading = True
self._unload()
def __getitem__(self, key: Addr) -> BasicBlock:
if self._basic_blocks is not None:
return self._basic_blocks[key]
self._preload()
bb = self._basic_blocks[key] # type: ignore # already preloaded
self._unload()
return bb
def __iter__(self) -> Generator[BasicBlock, None, None]:
"""
Iterate over basic blocks, not addresses
"""
if self._basic_blocks is not None:
yield from self._basic_blocks.values()
else:
self._preload()
yield from self._basic_blocks.values() # type: ignore # already preloaded
self._unload()
def __len__(self) -> int:
if self._basic_blocks is not None:
return len(self._basic_blocks)
self._preload()
size = len(self._basic_blocks) # type: ignore # already preloaded
self._unload()
return size
[docs]
def items(self) -> Iterator[tuple[Addr, BasicBlock]]: # type: ignore[override]
"""
Returns a generator of tuples with addresses of basic blocks and the corresponding basic blocks objects
:returns: generator (addr, basicblock)
"""
if self._basic_blocks is not None:
yield from self._basic_blocks.items()
else:
self._preload()
yield from self._basic_blocks.items() # type: ignore # already preloaded
self._unload()
def _preload(self) -> None:
"""Load in memory all the basic blocks"""
self._basic_blocks = {}
for bb in map(BasicBlock.from_backend, self._backend.basic_blocks):
self._basic_blocks[bb.addr] = bb
def _unload(self) -> None:
"""Unload from memory all the basic blocks"""
if self._enable_unloading:
self._basic_blocks = None
self._backend.unload_blocks()
[docs]
def get_label(self) -> Addr:
"""
Get the address associated to this function
:returns: The address associated with the function
"""
return self.addr
@property
def edges(self) -> list[tuple[Addr, Addr]]:
"""
Edges of the function flowgraph as a list of tuples with basic block addresses
"""
return list(self.flowgraph.edges)
@property
def addr(self) -> Addr:
"""
Address of the function
"""
return self._backend.addr
@property
def flowgraph(self) -> networkx.DiGraph:
"""
The networkx DiGraph of the function. This is used to perform networkx
based algorithm.
"""
return self._backend.graph
@property
def parents(self) -> set[Addr]:
"""
Set of function parents in the call graph.
Thus functions that calls this function
"""
return self._backend.parents
@property
def children(self) -> set[Addr]:
"""
Set of functions called by this function in the call graph.
"""
return self._backend.children
@property
def type(self) -> FunctionType:
"""
Returns the type of the instruction (as defined by IDA)
"""
return self._backend.type
[docs]
def is_library(self) -> bool:
"""
Returns whether or not this function is a library function.
A library function is either a thunk function or it has been identified as part
of an external library. It is not an imported function.
:return: bool
"""
return self.type == FunctionType.library
[docs]
def is_import(self) -> bool:
"""
Returns whether this function is an import function.
(Thus not having content)
:return: bool
"""
return self.type in (FunctionType.imported, FunctionType.extern)
[docs]
def is_thunk(self) -> bool:
"""
Returns whether this function is a thunk function.
:return: bool
"""
return self.type == FunctionType.thunk
[docs]
def is_alone(self) -> bool:
"""
Returns whether the function have neither caller nor callee.
:return: bool
"""
return not (self.children or self.parents)
def __repr__(self) -> str:
return "<Function: 0x%x>" % self.addr
@property
def name(self) -> str:
"""
Name of the function
"""
return self._backend.name
@name.setter
def name(self, name):
self._backend.name = name