lib_reloc_defined_prefixes.py (3531B)
1 #!/usr/bin/env python3 2 """Print disallowed global definitions after a relocatable archive link. 3 4 The check extracts every object from libkit.a, links them into one 5 relocatable object, and reports any remaining externally visible definition 6 whose C symbol name is not in libkit's public prefix set. 7 """ 8 9 import argparse 10 import os 11 import pathlib 12 import subprocess 13 import sys 14 import tempfile 15 16 17 ALLOWED_PREFIXES = ("Kit", "kit_", "KIT") 18 19 20 def run(args: list[str], **kwargs) -> subprocess.CompletedProcess[str]: 21 return subprocess.run(args, check=True, text=True, **kwargs) 22 23 24 def nm_lines(path: pathlib.Path, nm: str) -> list[str]: 25 return run([nm, str(path)], capture_output=True).stdout.splitlines() 26 27 28 def parse_nm_defined_globals(lines: list[str]) -> set[str]: 29 syms: set[str] = set() 30 for line in lines: 31 line = line.rstrip() 32 if not line or line.endswith(":"): 33 continue 34 parts = line.split(None, 2) 35 if len(parts) != 3: 36 continue 37 _, type_, name = parts 38 if type_ != "U" and type_.isupper(): 39 syms.add(name) 40 return syms 41 42 43 def public_c_name(sym: str) -> str: 44 if sym.startswith("_"): 45 stripped = sym[1:] 46 if stripped.startswith(ALLOWED_PREFIXES): 47 return stripped 48 return sym 49 50 51 def is_allowed(sym: str) -> bool: 52 return public_c_name(sym).startswith(ALLOWED_PREFIXES) 53 54 55 def link_relocatable( 56 archive: pathlib.Path, output: pathlib.Path, ar: str, cc: str 57 ) -> None: 58 archive = archive.resolve() 59 output = output.resolve() 60 with tempfile.TemporaryDirectory(prefix="kit-lib-reloc.") as td: 61 work = pathlib.Path(td) 62 run([ar, "x", str(archive)], cwd=work) 63 objects = sorted(work.glob("*.o")) 64 if not objects: 65 raise RuntimeError(f"{archive} did not contain any .o members") 66 67 args = [cc, "-r", "-nostdlib", "-o", str(output)] 68 args.extend(str(obj) for obj in objects) 69 output.parent.mkdir(parents=True, exist_ok=True) 70 run(args, capture_output=True) 71 72 73 def disallowed_reloc_defs( 74 archive: pathlib.Path, output: pathlib.Path, ar: str, cc: str, nm: str 75 ) -> list[str]: 76 link_relocatable(archive, output, ar, cc) 77 return sorted( 78 public_c_name(sym) 79 for sym in parse_nm_defined_globals(nm_lines(output, nm)) 80 if not is_allowed(sym) 81 ) 82 83 84 def main() -> int: 85 ap = argparse.ArgumentParser(description=__doc__) 86 ap.add_argument("archive", help="Path to libkit.a") 87 ap.add_argument( 88 "--output", 89 default="build/libkit.reloc.o", 90 help="Path for the temporary relocatable-link output", 91 ) 92 ap.add_argument("--ar", default=os.environ.get("AR", "ar"), help="ar binary") 93 ap.add_argument("--cc", default=os.environ.get("CC", "cc"), help="C compiler driver") 94 ap.add_argument("--nm", default=os.environ.get("NM", "nm"), help="nm binary") 95 args = ap.parse_args() 96 97 try: 98 bad = disallowed_reloc_defs( 99 pathlib.Path(args.archive), 100 pathlib.Path(args.output), 101 args.ar, 102 args.cc, 103 args.nm, 104 ) 105 except (FileNotFoundError, RuntimeError, subprocess.CalledProcessError) as e: 106 print(f"libkit relocatable symbol check failed: {e}", file=sys.stderr) 107 if isinstance(e, subprocess.CalledProcessError) and e.stderr: 108 print(e.stderr, file=sys.stderr, end="") 109 return 2 110 111 for sym in bad: 112 print(sym) 113 return 0 114 115 116 if __name__ == "__main__": 117 sys.exit(main())