Source code for gtirb.section

import itertools
import typing
from enum import Enum
from uuid import UUID

from intervaltree import IntervalTree

from .block import ByteBlock, CodeBlock, DataBlock
from .byteinterval import ByteInterval, SymbolicExpressionElement
from .node import Node, _NodeMessage
from .proto import Section_pb2
from .util import (
    SetWrapper,
    _address_interval,
    _nodes_at_interval_tree,
    _nodes_on_interval_tree,
)

if typing.TYPE_CHECKING:  # pragma: no cover
    # Ignore flake8 "imported but unused" errors.
    from .ir import IR  # noqa: F401
    from .module import Module  # noqa: F401


[docs]class Section(Node): """Represents a named section of the binary. Does not directly store the contents of the section, which are kept in a :class:`gtirb.ImageByteMap`. :ivar ~.name: The section name (E.g. ".text", ".bss", etc). :ivar ~.byte_intervals: The :class:`ByteInterval`\\s in this section. :ivar ~.flags: The :class:`Section.Flag`\\s this section has. """
[docs] class Flag(Enum): """A flag representing a known property of a section.""" Undefined = Section_pb2.SectionFlag.Value("Section_Undefined") """This value is defined for Protobuf compatibility. Do not use.""" Readable = Section_pb2.SectionFlag.Value("Readable") """This section can be read from at runtime.""" Writable = Section_pb2.SectionFlag.Value("Writable") """This section can be written to at runtime.""" Executable = Section_pb2.SectionFlag.Value("Executable") """This section contains executable code.""" Loaded = Section_pb2.SectionFlag.Value("Loaded") """This section is present in memory at runtime.""" Initialized = Section_pb2.SectionFlag.Value("Initialized") """This section has bytes allocated to it in the binary file.""" ThreadLocal = Section_pb2.SectionFlag.Value("ThreadLocal") """This section is created in memory once per thread."""
class _ByteIntervalSet(SetWrapper[ByteInterval]): def __init__( self, node: "Section", *args: typing.Iterable[ByteInterval] ): self._node = node super().__init__(*args) def add(self, v: ByteInterval) -> None: if v._section is not None: v._section.byte_intervals.discard(v) self._node._index_add(v) v._section = self._node if self._node.ir is not None: v._add_to_uuid_cache(self._node.ir._local_uuid_cache) return super().add(v) def discard(self, v: ByteInterval) -> None: if v not in self: return self._node._index_discard(v) v._section = None if self._node.ir is not None: v._remove_from_uuid_cache(self._node.ir._local_uuid_cache) return super().discard(v)
[docs] def __init__( self, *, name: str = "", byte_intervals: typing.Iterable[ByteInterval] = (), flags: typing.Iterable["Section.Flag"] = set(), uuid: typing.Optional[UUID] = None, module: typing.Optional["Module"] = None, ): """ :param name: The name of this section. :param byte_intervals: The :class:`ByteInterval`\\s in this section. :param flags: The :class:`Section.Flag`\\s this section has. :param uuid: The UUID of this ``Section``, or None if a new UUID needs generated via :func:`uuid.uuid4`. Defaults to None. :param module: The :class:`Module` this section belongs to. """ super().__init__(uuid) self._interval_index: "IntervalTree[int,ByteInterval]" = IntervalTree() self._module: typing.Optional["Module"] = None self.name = name self.byte_intervals = Section._ByteIntervalSet(self, byte_intervals) self.flags = set(flags) # Use the property setter to ensure correct invariants. self.module = module
def _index_add(self, byte_interval: ByteInterval) -> None: address_interval = _address_interval(byte_interval) if address_interval: self._interval_index.add(address_interval) def _index_discard(self, byte_interval: ByteInterval) -> None: address_interval = _address_interval(byte_interval) if address_interval: self._interval_index.discard(address_interval) @classmethod def _decode_protobuf( cls, proto_section: _NodeMessage, uuid: UUID, ir: typing.Optional["IR"], ) -> "Section": assert ir assert isinstance(proto_section, Section_pb2.Section) s = cls( name=proto_section.name, flags=(Section.Flag(f) for f in proto_section.section_flags), uuid=uuid, ) s._add_to_uuid_cache(ir._local_uuid_cache) s.byte_intervals.update( ByteInterval._from_protobuf(bi, ir) for bi in proto_section.byte_intervals ) return s def _to_protobuf(self) -> Section_pb2.Section: """Get a Protobuf representation of ``self``.""" proto_section = Section_pb2.Section() proto_section.uuid = self.uuid.bytes proto_section.name = self.name proto_section.byte_intervals.extend( bi._to_protobuf() for bi in self.byte_intervals ) proto_section.section_flags.extend(f.value for f in self.flags) return proto_section
[docs] def deep_eq(self, other: object) -> bool: # Do not move __eq__. See docstring for Node.deep_eq for more info. if not isinstance(other, Section): return False return ( self.uuid == other.uuid and self.name == other.name and len(self.byte_intervals) == len(other.byte_intervals) and all( self_node.deep_eq(other_node) for self_node, other_node in zip( sorted(self.byte_intervals, key=lambda bi: bi.uuid), sorted(other.byte_intervals, key=lambda bi: bi.uuid), ) ) and self.flags == other.flags )
def __repr__(self) -> str: return ( "Section(" "uuid={uuid!r}, " "name={name!r}, " "byte_intervals={byte_intervals!r}, " "flags=({pretty_flags}), " ")".format( pretty_flags=", ".join( "Section." + str(f) for f in self.flags ), **self.__dict__, ) ) @property def module(self) -> typing.Optional["Module"]: """The :class:`Module` this section belongs to.""" return self._module @module.setter def module(self, value: typing.Optional["Module"]) -> None: if self._module is not None: self._module.sections.discard(self) if value is not None: value.sections.add(self) @property def byte_blocks(self) -> typing.Iterator[ByteBlock]: """The :class:`ByteBlock`\\s in this section.""" return itertools.chain.from_iterable( bi.blocks for bi in self.byte_intervals ) @property def code_blocks(self) -> typing.Iterator[CodeBlock]: """The :class:`CodeBlock`\\s in this section.""" return (b for b in self.byte_blocks if isinstance(b, CodeBlock)) @property def data_blocks(self) -> typing.Iterator[DataBlock]: """The :class:`DataBlock`\\s in this section.""" return (b for b in self.byte_blocks if isinstance(b, DataBlock)) @property def address(self) -> typing.Optional[int]: """Get the address of this section, if known. The address is calculated from the :class:`ByteInterval` objects in this section. More specifically, if the address of all byte intervals in this section are fixed, then it will return the address of the interval lowest in memory. If any one interval does not have an address then this will be ``None``, as the address is not calculable in that case. Note that a section with no intervals in it has no address or size, so it will be ``None`` in that case. """ if 0 < len(self._interval_index) == len(self.byte_intervals): return self._interval_index.begin() return None @property def size(self) -> typing.Optional[int]: """Get the size of this section, if known. The address is calculated from the :class:`ByteInterval` objects in this section. More specifically, if the address of all byte intervals in this section are fixed, then it will return the difference between the lowest and highest address among the intervals. If any one interval does not have an address, then this will be ``None``, as the size is not calculable in that case. Note that a section with no intervals in it has no address or size, so it will be ``None`` in that case. """ if 0 < len(self._interval_index) == len(self.byte_intervals): return self._interval_index.span() - 1 return None
[docs] def byte_intervals_on( self, addrs: typing.Union[int, range] ) -> typing.Iterable[ByteInterval]: """Finds all the byte intervals that overlap an address or range of addresses. :param addrs: Either a ``range`` object or a single address. """ return _nodes_on_interval_tree(self._interval_index, addrs)
[docs] def byte_intervals_at( self, addrs: typing.Union[int, range] ) -> typing.Iterable[ByteInterval]: """Finds all the byte intervals that begin at an address or range of addresses. :param addrs: Either a ``range`` object or a single address. """ return _nodes_at_interval_tree(self._interval_index, addrs)
[docs] def byte_blocks_on( self, addrs: typing.Union[int, range] ) -> typing.Iterable[ByteBlock]: """Finds all the byte blocks that overlap an address or range of addresses. :param addrs: Either a ``range`` object or a single address. """ for interval in self.byte_intervals_on(addrs): yield from interval.byte_blocks_on(addrs)
[docs] def byte_blocks_at( self, addrs: typing.Union[int, range] ) -> typing.Iterable[ByteBlock]: """Finds all the byte blocks that begin at an address or range of addresses. :param addrs: Either a ``range`` object or a single address. """ for interval in self.byte_intervals_on(addrs): yield from interval.byte_blocks_at(addrs)
[docs] def code_blocks_on( self, addrs: typing.Union[int, range] ) -> typing.Iterable[CodeBlock]: """Finds all the code blocks that overlap an address or range of addresses. :param addrs: Either a ``range`` object or a single address. """ return ( block for block in self.byte_blocks_on(addrs) if isinstance(block, CodeBlock) )
[docs] def code_blocks_at( self, addrs: typing.Union[int, range] ) -> typing.Iterable[CodeBlock]: """Finds all the code blocks that begin at an address or range of addresses. :param addrs: Either a ``range`` object or a single address. """ return ( block for block in self.byte_blocks_at(addrs) if isinstance(block, CodeBlock) )
[docs] def data_blocks_on( self, addrs: typing.Union[int, range] ) -> typing.Iterable[DataBlock]: """Finds all the data blocks that overlap an address or range of addresses. :param addrs: Either a ``range`` object or a single address. """ return ( block for block in self.byte_blocks_on(addrs) if isinstance(block, DataBlock) )
[docs] def data_blocks_at( self, addrs: typing.Union[int, range] ) -> typing.Iterable[DataBlock]: """Finds all the data blocks that begin at an address or range of addresses. :param addrs: Either a ``range`` object or a single address. """ return ( block for block in self.byte_blocks_at(addrs) if isinstance(block, DataBlock) )
[docs] def symbolic_expressions_at( self, addrs: typing.Union[int, range] ) -> typing.Iterable[SymbolicExpressionElement]: """Finds all the symbolic expressions that begin at an address or range of addresses. :param addrs: Either a ``range`` object or a single address. :returns: Yields ``(interval, offset, symexpr)`` tuples for every symbolic expression in the range. """ for interval in self.byte_intervals_on(addrs): yield from interval.symbolic_expressions_at(addrs)
def _add_to_uuid_cache(self, cache: typing.Dict[UUID, Node]) -> None: """Update the UUID cache when this node is added.""" cache[self.uuid] = self for bi in self.byte_intervals: bi._add_to_uuid_cache(cache) def _remove_from_uuid_cache(self, cache: typing.Dict[UUID, Node]) -> None: """Update the UUID cache when this node is removed.""" del cache[self.uuid] for bi in self.byte_intervals: bi._remove_from_uuid_cache(cache) @property def ir(self) -> typing.Optional["IR"]: """Get the IR this node ultimately belongs to.""" if self.module is None: return None return self.module.ir