From 45ef7d002625b0b76059cf54a9eede91a46bac14 Mon Sep 17 00:00:00 2001 From: Jan-Erik Rediger Date: Sat, 9 Dec 2023 00:05:23 +0100 Subject: [PATCH] symbols, sections and exports in sql --- .gitignore | 1 + crates/oelf/oelf/__init__.py | 0 crates/oelf/oelf/cli.py | 195 +++++++++++++++++++++++++++++++++++ crates/oelf/pyproject.toml | 17 +++ 4 files changed, 213 insertions(+) create mode 100644 crates/oelf/oelf/__init__.py create mode 100644 crates/oelf/oelf/cli.py create mode 100644 crates/oelf/pyproject.toml diff --git a/.gitignore b/.gitignore index 65332ff..a76039a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.so *.dylib **/target/ +*.egg-info diff --git a/crates/oelf/oelf/__init__.py b/crates/oelf/oelf/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/crates/oelf/oelf/cli.py b/crates/oelf/oelf/cli.py new file mode 100644 index 0000000..87ed0a4 --- /dev/null +++ b/crates/oelf/oelf/cli.py @@ -0,0 +1,195 @@ +import sys + +import apsw +import apsw.shell +import goblin + +from dataclasses import dataclass +from enum import Flag, auto +from typing import Any, Callable, Iterator, Sequence, Tuple, cast + +connection = apsw.Connection(":memory:") + + +@dataclass +class Generator: + """A generator for the virtual table SQLite module. + + This class is needed because apsw wants to assign columns and + column_access to the generator function itself.""" + + columns: Sequence[str] + column_access: apsw.ext.VTColumnAccess + callable: Callable[[], Iterator[dict[str, Any]]] + + def __call__(self) -> Iterator[dict[str, Any]]: + """Call the generator should return an iterator of dictionaries. + + The dictionaries should have keys that match the column names.""" + return self.callable() + + @staticmethod + def make_generator( + columns: list[str], generator: Callable[[], Iterator[dict[str, Any]]] + ): + """Create a generator from a callable that returns + an iterator of dictionaries.""" + return Generator(columns, apsw.ext.VTColumnAccess.By_Name, generator) + + +class CacheFlag(Flag): + NONE = 0 + DYNAMIC_ENTRIES = auto() + HEADERS = auto() + INSTRUCTIONS = auto() + SECTIONS = auto() + EXPORTS = auto() + SYMBOLS = auto() + STRINGS = auto() + VERSION_REQUIREMENTS = auto() + VERSION_DEFINITIONS = auto() + DWARF_DIE = auto() + DWARF_DIE_CALL_GRAPH = auto() + + @classmethod + def from_string(cls, str: str): + """Convert a string to a CacheFlag. + + This also specially handles 'ALL' which returns all the flags.""" + if str == "ALL": + return cls.ALL() + try: + return cls[str] + except KeyError: + raise ValueError(f"{str} is not a valid CacheFlag") + + @classmethod + def ALL(cls): + retval = cls.NONE + for member in cls.__members__.values(): + retval |= member + return retval + + +def register_generator( + connection: apsw.Connection, + generator: Generator, + table_name: str, + generator_flag: CacheFlag, + cache_flags: CacheFlag, +) -> None: + """Register a virtual table generator. + + This method does a bit of duplicate work which checks if we need to cache + the given generator. + + If so we rename the table with a prefix 'raw' and then create a temp table""" + original_table_name = table_name + if generator_flag in cache_flags: + table_name = f"raw_{table_name}" + + apsw.ext.make_virtual_module(connection, table_name, generator) + + if generator_flag in cache_flags: + connection.execute( + f"""CREATE TABLE {original_table_name} + AS SELECT * FROM {table_name};""" + ) + + +def register_symbols( + gob: goblin.Object, connection: apsw.Connection, cache_flags: CacheFlag +) -> None: + def dynamic_entries_generator() -> Iterator[dict[str, Any]]: + for sym in gob.symbols(): + yield { + "name": sym.name, + "type": sym.typ, + "global": sym.is_global, + "weak": sym.weak, + "undefined": sym.undefined, + "stab": sym.stab, + } + + generator = Generator.make_generator( + ["name", "type", "global", "weak", "undefined", "stab"], + dynamic_entries_generator, + ) + + register_generator( + connection, + generator, + "macho_symbols", + CacheFlag.SYMBOLS, + cache_flags, + ) + +def register_sections( + gob: goblin.Object, connection: apsw.Connection, cache_flags: CacheFlag +) -> None: + def dynamic_entries_generator() -> Iterator[dict[str, Any]]: + for sect in gob.sections(): + yield { + "name": sect.name, + "segment": sect.segment, + "addr": sect.addr, + "size": sect.size, + "offset": sect.offset, + "align": sect.align, + "reloff": sect.reloff, + "nreloc": sect.nreloc, + "flags": sect.flags, + } + + generator = Generator.make_generator( + ["name", "segment", "addr", "size", "offset", "align", "reloff", "nreloc", "flags"], + dynamic_entries_generator, + ) + + register_generator( + connection, + generator, + "macho_sections", + CacheFlag.SECTIONS, + cache_flags, + ) + + +def register_exports( + gob: goblin.Object, connection: apsw.Connection, cache_flags: CacheFlag +) -> None: + def dynamic_entries_generator() -> Iterator[dict[str, Any]]: + for exp in gob.exports(): + yield { + "name": exp.name, + "size": exp.size, + "offset": exp.offset, + "type": str(exp.info.typ), + "address": exp.info.address, + "flags": exp.info.flags, + "lib": exp.info.lib, + "lib_symbol_name": exp.info.lib_symbol_name, + } + + generator = Generator.make_generator( + ["name", "size", "offset", "type", "address", "flags", "lib", "lib_symbol_name"], + dynamic_entries_generator, + ) + + register_generator( + connection, + generator, + "macho_exports", + CacheFlag.EXPORTS, + cache_flags, + ) + + +g = goblin.Object("../mylib.dylib") +register_symbols(g, connection, CacheFlag.SYMBOLS) +register_sections(g, connection, CacheFlag.SECTIONS) +register_exports(g, connection, CacheFlag.EXPORTS) + +shell = apsw.shell.Shell(db=connection, stdin=sys.stdin) +shell.command_prompt(["ölf> "]) +shell.cmdloop() diff --git a/crates/oelf/pyproject.toml b/crates/oelf/pyproject.toml new file mode 100644 index 0000000..089ca8e --- /dev/null +++ b/crates/oelf/pyproject.toml @@ -0,0 +1,17 @@ +[project] +name = "oelf" +version = "0.1" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + +dependencies = [ + "apsw", +] + +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta"