|
| 1 | +#!/usr/bin/env python3 |
| 2 | +"""Check that a libsecp256k1 shared library exports only expected symbols. |
| 3 | +
|
| 4 | +Usage examples: |
| 5 | + - When building with Autotools: |
| 6 | + ./tools/symbol-check.py .libs/libsecp256k1.so |
| 7 | + ./tools/symbol-check.py .libs/libsecp256k1-<V>.dll |
| 8 | + ./tools/symbol-check.py .libs/libsecp256k1.dylib |
| 9 | +
|
| 10 | + - When building with CMake: |
| 11 | + ./tools/symbol-check.py build/lib/libsecp256k1.so |
| 12 | + ./tools/symbol-check.py build/bin/libsecp256k1-<V>.dll |
| 13 | + ./tools/symbol-check.py build/lib/libsecp256k1.dylib""" |
| 14 | + |
| 15 | +import re |
| 16 | +import sys |
| 17 | +import subprocess |
| 18 | + |
| 19 | +import lief |
| 20 | + |
| 21 | + |
| 22 | +class UnexpectedExport(RuntimeError): |
| 23 | + pass |
| 24 | + |
| 25 | + |
| 26 | +def get_exported_exports(library) -> list[str]: |
| 27 | + """Adapter function to get exported symbols based on the library format.""" |
| 28 | + if library.format == lief.Binary.FORMATS.ELF: |
| 29 | + return [symbol.name for symbol in library.exported_symbols] |
| 30 | + elif library.format == lief.Binary.FORMATS.PE: |
| 31 | + return [entry.name for entry in library.get_export().entries] |
| 32 | + elif library.format == lief.Binary.FORMATS.MACHO: |
| 33 | + return [symbol.name[1:] for symbol in library.exported_symbols] |
| 34 | + raise NotImplementedError(f"Unsupported format: {library.format}") |
| 35 | + |
| 36 | + |
| 37 | +def grep_expected_symbols() -> list[str]: |
| 38 | + """Guess the list of expected exported symbols from the source code.""" |
| 39 | + grep_output = subprocess.check_output( |
| 40 | + ["git", "grep", r"^\s*SECP256K1_API", "--", "include"], |
| 41 | + universal_newlines=True, |
| 42 | + encoding="utf-8" |
| 43 | + ) |
| 44 | + lines = grep_output.split("\n") |
| 45 | + pattern = re.compile(r'\bsecp256k1_\w+') |
| 46 | + exported: list[str] = [pattern.findall(line)[-1] for line in lines if line.strip()] |
| 47 | + return exported |
| 48 | + |
| 49 | + |
| 50 | +def check_symbols(library, expected_exports) -> None: |
| 51 | + """Check that the library exports only the expected symbols.""" |
| 52 | + actual_exports = get_exported_exports(library) |
| 53 | + unexpected_exports = set(actual_exports) - set(expected_exports) |
| 54 | + if unexpected_exports != set(): |
| 55 | + raise UnexpectedExport(f"Unexpected exported symbols: {unexpected_exports}") |
| 56 | + |
| 57 | +def main(): |
| 58 | + if len(sys.argv) != 2: |
| 59 | + print(__doc__) |
| 60 | + return 1 |
| 61 | + library = lief.parse(sys.argv[1]) |
| 62 | + expected_exports = grep_expected_symbols() |
| 63 | + try: |
| 64 | + check_symbols(library, expected_exports) |
| 65 | + except UnexpectedExport as e: |
| 66 | + print(f"{sys.argv[0]}: In {sys.argv[1]}: {e}") |
| 67 | + return 1 |
| 68 | + return 0 |
| 69 | + |
| 70 | + |
| 71 | +if __name__ == "__main__": |
| 72 | + sys.exit(main()) |
0 commit comments