Source code for pypcode

"""
Pythonic interface to SLEIGH
"""

from __future__ import annotations
import os.path
import xml.etree.ElementTree as ET
from enum import IntEnum
from typing import cast
from collections.abc import Generator, Sequence, Mapping

from .__version__ import __version__

from .pypcode_native import __version__ as __pypcode_native_version__  # pylint:disable=no-name-in-module

assert __version__ == __pypcode_native_version__

from .pypcode_native import (  # pylint:disable=no-name-in-module
    Address,
    AddrSpace,
    BadDataError,
    DecoderError,
    Disassembly,
    Instruction,
    LowlevelError,
    OpCode,
    PcodeOp,
    TRANSLATE_FLAGS_BB_TERMINATING,
    Translation,
    UnimplError,
    Varnode,
)
from .pypcode_native import Context as _Context  # pylint:disable=no-name-in-module

from .printing import (
    OpFormat,
    OpFormatBinary,
    OpFormatFunc,
    OpFormatSpecial,
    OpFormatUnary,
    PcodePrettyPrinter,
)

__all__ = [
    "Address",
    "AddrSpace",
    "Arch",
    "ArchLanguage",
    "BadDataError",
    "Context",
    "DecoderError",
    "Disassembly",
    "Instruction",
    "LowlevelError",
    "OpCode",
    "OpFormat",
    "OpFormatBinary",
    "OpFormatFunc",
    "OpFormatSpecial",
    "OpFormatUnary",
    "PcodeOp",
    "PcodePrettyPrinter",
    "TranslateFlags",
    "Translation",
    "UnimplError",
    "Varnode",
]


[docs] class TranslateFlags(IntEnum): """ Flags that can be passed to Context::translate """ BB_TERMINATING = TRANSLATE_FLAGS_BB_TERMINATING
PKG_SRC_DIR = os.path.abspath(os.path.dirname(__file__)) SPECFILES_DIR = os.path.join(PKG_SRC_DIR, "processors")
[docs] class ArchLanguage: """ A specific language for an architecture. Provides access to language, pspec, and cspecs. """ __slots__ = ( "archdir", "ldef", "_pspec", "_cspecs", ) archdir: str ldef: ET.Element def __init__(self, archdir: str, ldef: ET.Element): self.archdir = archdir self.ldef = ldef self._pspec: ET.Element | None = None self._cspecs: dict[tuple[str, str], ET.Element] | None = None @property def pspec_path(self) -> str: return os.path.join(self.archdir, self.processorspec) @property def slafile_path(self) -> str: return os.path.join(self.archdir, self.slafile) @property def description(self) -> str: elem = self.ldef.find("description") if elem is not None: return elem.text or "" return "" def __getattr__(self, key): if key in self.ldef.attrib: return self.ldef.attrib[key] raise AttributeError(key) @property def pspec(self) -> ET.Element | None: if self._pspec is None: self._pspec = ET.parse(self.pspec_path).getroot() return self._pspec @property def cspecs(self) -> Mapping[tuple[str, str], ET.Element]: if self._cspecs is None: self._cspecs = {} for e in self.ldef.findall("compiler"): path = os.path.join(self.archdir, e.attrib["spec"]) cspec = ET.parse(path).getroot() self._cspecs[(e.attrib["id"], e.attrib["name"])] = cspec return self._cspecs
[docs] def init_context_from_pspec(self, ctx: Context) -> None: if self.pspec is None: return cd = self.pspec.find("context_data") if cd is None: return cs = cd.find("context_set") if cs is None: return for e in cs: assert e.tag == "set" ctx.setVariableDefault(e.attrib["name"], int(e.attrib["val"]))
[docs] @classmethod def from_id(cls, langid: str) -> ArchLanguage | None: """ Return language with given id, or None if the language could not be found. """ for arch in Arch.enumerate(): for lang in arch.languages: if lang.id == langid: return lang return None
[docs] class Arch: """ Main class representing an architecture describing available languages. """ __slots__ = ( "archpath", "archname", "ldefpath", "ldef", "languages", ) archpath: str archname: str ldefpath: str ldef: ET.ElementTree[ET.Element[str]] languages: Sequence[ArchLanguage] def __init__(self, name: str, ldefpath: str): """ Initialize the Arch. :param name: The name of the architecture :param ldefpath: Path to language definition files (.ldefs) """ self.archpath = os.path.dirname(ldefpath) self.archname = name self.ldefpath = ldefpath self.ldef = ET.parse(ldefpath) self.languages = [ArchLanguage(self.archpath, e) for e in self.ldef.getroot()]
[docs] @classmethod def enumerate(cls) -> Generator[Arch]: """ Enumerate all available architectures and languages. Language definitions are sourced from definitions shipped with pypcode and can be found in processors/<architecture>/data/languages/<variant>.ldefs """ for archname in os.listdir(SPECFILES_DIR): langdir = os.path.join(SPECFILES_DIR, archname, "data", "languages") if not (os.path.exists(langdir) and os.path.isdir(langdir)): continue for langname in os.listdir(langdir): if not langname.endswith(".ldefs"): continue ldefpath = os.path.join(langdir, langname) yield Arch(archname, ldefpath)
[docs] class Context(_Context): """ Context for translation. """ __slots__ = ( "language", "registers", ) language: ArchLanguage registers: dict[str, Varnode] def __init__(self, language: ArchLanguage | str): """ Initialize a context for translation or disassembly. :param language: The ``ArchLanguage`` to initialize the context with, or a language id ``str`` """ if isinstance(language, ArchLanguage): self.language = language elif isinstance(language, str): _l = ArchLanguage.from_id(cast(str, language)) assert _l is not None self.language = _l else: raise TypeError("Context must be initialized with a language or language id") super().__init__(f"<sleigh>{self.language.slafile_path}</sleigh>") self.language.init_context_from_pspec(self) self.registers = {n: v for v, n in self.getAllRegisters().items()}