xref: /aosp_15_r20/external/executorch/scripts/check_binary_dependencies.py (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
1*523fa7a6SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*523fa7a6SAndroid Build Coastguard Worker# Copyright (c) Meta Platforms, Inc. and affiliates.
3*523fa7a6SAndroid Build Coastguard Worker# All rights reserved.
4*523fa7a6SAndroid Build Coastguard Worker#
5*523fa7a6SAndroid Build Coastguard Worker# This source code is licensed under the BSD-style license found in the
6*523fa7a6SAndroid Build Coastguard Worker# LICENSE file in the root directory of this source tree.
7*523fa7a6SAndroid Build Coastguard Worker
8*523fa7a6SAndroid Build Coastguard Worker# pyre-strict
9*523fa7a6SAndroid Build Coastguard Worker
10*523fa7a6SAndroid Build Coastguard Worker"""
11*523fa7a6SAndroid Build Coastguard WorkerA script to help check binary dependencies and disallowed symbols in intermediate build files.
12*523fa7a6SAndroid Build Coastguard Worker"""
13*523fa7a6SAndroid Build Coastguard Worker
14*523fa7a6SAndroid Build Coastguard Workerimport argparse
15*523fa7a6SAndroid Build Coastguard Workerimport os
16*523fa7a6SAndroid Build Coastguard Workerimport re
17*523fa7a6SAndroid Build Coastguard Workerimport subprocess
18*523fa7a6SAndroid Build Coastguard Workerimport sys
19*523fa7a6SAndroid Build Coastguard Workerfrom dataclasses import dataclass
20*523fa7a6SAndroid Build Coastguard Workerfrom pathlib import Path
21*523fa7a6SAndroid Build Coastguard Workerfrom typing import Dict, Iterable, List, NoReturn, Optional, Tuple
22*523fa7a6SAndroid Build Coastguard Worker
23*523fa7a6SAndroid Build Coastguard Worker# Script output statuses.
24*523fa7a6SAndroid Build Coastguard WorkerSTATUS_OK = 0
25*523fa7a6SAndroid Build Coastguard WorkerSTATUS_SCRIPT_ERROR = 1
26*523fa7a6SAndroid Build Coastguard WorkerSTATUS_ERROR = 2
27*523fa7a6SAndroid Build Coastguard WorkerSTATUS_WARNING = 3
28*523fa7a6SAndroid Build Coastguard Worker
29*523fa7a6SAndroid Build Coastguard Worker# Object file suffix.
30*523fa7a6SAndroid Build Coastguard WorkerOBJECT_SUFFIX = ".o"
31*523fa7a6SAndroid Build Coastguard Worker
32*523fa7a6SAndroid Build Coastguard Worker# Project root, assuming this script is in `<root>/scripts/`
33*523fa7a6SAndroid Build Coastguard WorkerPROJECT_ROOT = Path(__file__).parent.parent.resolve()
34*523fa7a6SAndroid Build Coastguard Worker
35*523fa7a6SAndroid Build Coastguard Worker# Regex to strip info from nm and readelf.
36*523fa7a6SAndroid Build Coastguard WorkerNM_REGEX = re.compile(r"\d*\s+(?P<status>\S)\s+(?P<symbol>.*)")
37*523fa7a6SAndroid Build Coastguard WorkerREADELF_DEP_REGEX = re.compile(r".*\(NEEDED\)\s+(?P<so>.*)")
38*523fa7a6SAndroid Build Coastguard WorkerREADELF_DYN_SYM_REGEX = re.compile(r"(UND|\d+)\s+(?P<symbol>[^@\s:]+)(@.*)?$")
39*523fa7a6SAndroid Build Coastguard Worker
40*523fa7a6SAndroid Build Coastguard Worker# Disallow list of prefixes for standard library symbols.
41*523fa7a6SAndroid Build Coastguard WorkerDISALLOW_LIST = [
42*523fa7a6SAndroid Build Coastguard Worker    "operator new",
43*523fa7a6SAndroid Build Coastguard Worker    "operator delete",
44*523fa7a6SAndroid Build Coastguard Worker    "std::__cxx11::basic_string",
45*523fa7a6SAndroid Build Coastguard Worker    "std::__throw",
46*523fa7a6SAndroid Build Coastguard Worker    "std::deque",
47*523fa7a6SAndroid Build Coastguard Worker    "std::exception",
48*523fa7a6SAndroid Build Coastguard Worker    "std::forward_list",
49*523fa7a6SAndroid Build Coastguard Worker    "std::list",
50*523fa7a6SAndroid Build Coastguard Worker    "std::map",
51*523fa7a6SAndroid Build Coastguard Worker    "std::multimap",
52*523fa7a6SAndroid Build Coastguard Worker    "std::multiset",
53*523fa7a6SAndroid Build Coastguard Worker    "std::priority_queue",
54*523fa7a6SAndroid Build Coastguard Worker    "std::queue",
55*523fa7a6SAndroid Build Coastguard Worker    "std::set",
56*523fa7a6SAndroid Build Coastguard Worker    "std::stack",
57*523fa7a6SAndroid Build Coastguard Worker    "std::unordered_map",
58*523fa7a6SAndroid Build Coastguard Worker    "std::unordered_multimap",
59*523fa7a6SAndroid Build Coastguard Worker    "std::unordered_multiset",
60*523fa7a6SAndroid Build Coastguard Worker    "std::unordered_set",
61*523fa7a6SAndroid Build Coastguard Worker    "std::vector",
62*523fa7a6SAndroid Build Coastguard Worker]
63*523fa7a6SAndroid Build Coastguard Worker
64*523fa7a6SAndroid Build Coastguard Worker
65*523fa7a6SAndroid Build Coastguard Worker@dataclass
66*523fa7a6SAndroid Build Coastguard Workerclass Symbol:
67*523fa7a6SAndroid Build Coastguard Worker    """Symbol scraped from ELF binary object."""
68*523fa7a6SAndroid Build Coastguard Worker
69*523fa7a6SAndroid Build Coastguard Worker    mangled: str
70*523fa7a6SAndroid Build Coastguard Worker    demangled: str
71*523fa7a6SAndroid Build Coastguard Worker    defined: bool
72*523fa7a6SAndroid Build Coastguard Worker    disallowed: bool
73*523fa7a6SAndroid Build Coastguard Worker    sources: List[Path]
74*523fa7a6SAndroid Build Coastguard Worker
75*523fa7a6SAndroid Build Coastguard Worker
76*523fa7a6SAndroid Build Coastguard Worker# Cached symbols dictionary.
77*523fa7a6SAndroid Build Coastguard Workersymbols_cache: Optional[Dict[str, Symbol]] = None
78*523fa7a6SAndroid Build Coastguard Worker
79*523fa7a6SAndroid Build Coastguard Worker
80*523fa7a6SAndroid Build Coastguard Workerdef error(message: str) -> NoReturn:
81*523fa7a6SAndroid Build Coastguard Worker    """Emit an error message and kill the script."""
82*523fa7a6SAndroid Build Coastguard Worker    print(message)
83*523fa7a6SAndroid Build Coastguard Worker    sys.exit(STATUS_SCRIPT_ERROR)
84*523fa7a6SAndroid Build Coastguard Worker
85*523fa7a6SAndroid Build Coastguard Worker
86*523fa7a6SAndroid Build Coastguard Workerdef get_tool_output(args: List[str]) -> str:
87*523fa7a6SAndroid Build Coastguard Worker    """Execute a command in the shell and return the output."""
88*523fa7a6SAndroid Build Coastguard Worker    result = subprocess.run(args, stdout=subprocess.PIPE)
89*523fa7a6SAndroid Build Coastguard Worker    output = result.stdout.decode("utf-8")
90*523fa7a6SAndroid Build Coastguard Worker    return output
91*523fa7a6SAndroid Build Coastguard Worker
92*523fa7a6SAndroid Build Coastguard Worker
93*523fa7a6SAndroid Build Coastguard Workerdef read_nm(
94*523fa7a6SAndroid Build Coastguard Worker    nm: str, file: Path, exclude: Optional[List[str]] = None
95*523fa7a6SAndroid Build Coastguard Worker) -> List[Tuple[str, str]]:
96*523fa7a6SAndroid Build Coastguard Worker    """Read a set of symbols using the nm tool."""
97*523fa7a6SAndroid Build Coastguard Worker    if exclude is None:
98*523fa7a6SAndroid Build Coastguard Worker        exclude = ["N"]
99*523fa7a6SAndroid Build Coastguard Worker
100*523fa7a6SAndroid Build Coastguard Worker    output = get_tool_output([nm, file])
101*523fa7a6SAndroid Build Coastguard Worker    result = []
102*523fa7a6SAndroid Build Coastguard Worker    for line in output.splitlines():
103*523fa7a6SAndroid Build Coastguard Worker        match = re.search(NM_REGEX, line)
104*523fa7a6SAndroid Build Coastguard Worker        if not match:
105*523fa7a6SAndroid Build Coastguard Worker            continue
106*523fa7a6SAndroid Build Coastguard Worker
107*523fa7a6SAndroid Build Coastguard Worker        status = match.group("status").upper()
108*523fa7a6SAndroid Build Coastguard Worker        if exclude is None or status not in exclude:
109*523fa7a6SAndroid Build Coastguard Worker            result.append((status, match.group("symbol")))
110*523fa7a6SAndroid Build Coastguard Worker    return result
111*523fa7a6SAndroid Build Coastguard Worker
112*523fa7a6SAndroid Build Coastguard Worker
113*523fa7a6SAndroid Build Coastguard Workerdef get_object_symbols(
114*523fa7a6SAndroid Build Coastguard Worker    nm: str, symbols: Dict[str, Symbol], object_file: Path, source_file: Path
115*523fa7a6SAndroid Build Coastguard Worker) -> None:
116*523fa7a6SAndroid Build Coastguard Worker    """Scrape symbols from a binary object."""
117*523fa7a6SAndroid Build Coastguard Worker    symbol_table = read_nm(nm, object_file)
118*523fa7a6SAndroid Build Coastguard Worker    for t, symbol in symbol_table:
119*523fa7a6SAndroid Build Coastguard Worker        if symbol not in symbols:
120*523fa7a6SAndroid Build Coastguard Worker            symbols[symbol] = Symbol(
121*523fa7a6SAndroid Build Coastguard Worker                mangled=symbol,
122*523fa7a6SAndroid Build Coastguard Worker                demangled="",
123*523fa7a6SAndroid Build Coastguard Worker                defined=(t != "U"),
124*523fa7a6SAndroid Build Coastguard Worker                disallowed=False,
125*523fa7a6SAndroid Build Coastguard Worker                sources=[],
126*523fa7a6SAndroid Build Coastguard Worker            )
127*523fa7a6SAndroid Build Coastguard Worker        if source_file in symbols[symbol].sources:
128*523fa7a6SAndroid Build Coastguard Worker            continue
129*523fa7a6SAndroid Build Coastguard Worker        symbols[symbol].sources.append(source_file)
130*523fa7a6SAndroid Build Coastguard Worker
131*523fa7a6SAndroid Build Coastguard Worker
132*523fa7a6SAndroid Build Coastguard Workerdef get_elf_dependencies(readelf: str, binary_file: Path) -> List[str]:
133*523fa7a6SAndroid Build Coastguard Worker    """Get the shared object dependencies of a binary executable."""
134*523fa7a6SAndroid Build Coastguard Worker    shared_objects = []
135*523fa7a6SAndroid Build Coastguard Worker    output = get_tool_output([readelf, "-d", binary_file])
136*523fa7a6SAndroid Build Coastguard Worker    for line in output.splitlines():
137*523fa7a6SAndroid Build Coastguard Worker        match = re.search(READELF_DEP_REGEX, line)
138*523fa7a6SAndroid Build Coastguard Worker        if not match:
139*523fa7a6SAndroid Build Coastguard Worker            continue
140*523fa7a6SAndroid Build Coastguard Worker        shared_objects.append(match.group("so"))
141*523fa7a6SAndroid Build Coastguard Worker
142*523fa7a6SAndroid Build Coastguard Worker    return shared_objects
143*523fa7a6SAndroid Build Coastguard Worker
144*523fa7a6SAndroid Build Coastguard Worker
145*523fa7a6SAndroid Build Coastguard Workerdef get_binary_dynamic_symbols(readelf: str, binary_file: Path) -> List[str]:
146*523fa7a6SAndroid Build Coastguard Worker    """Get the dynamic symbols required by a binary executable."""
147*523fa7a6SAndroid Build Coastguard Worker    dynamic_symbols = []
148*523fa7a6SAndroid Build Coastguard Worker    output = get_tool_output([readelf, "--dyn-syms", "--wide", binary_file])
149*523fa7a6SAndroid Build Coastguard Worker    for line in output.splitlines():
150*523fa7a6SAndroid Build Coastguard Worker        match = re.search(READELF_DYN_SYM_REGEX, line)
151*523fa7a6SAndroid Build Coastguard Worker        if not match:
152*523fa7a6SAndroid Build Coastguard Worker            continue
153*523fa7a6SAndroid Build Coastguard Worker        dynamic_symbols.append(match.group("symbol"))
154*523fa7a6SAndroid Build Coastguard Worker    return list(set(dynamic_symbols))
155*523fa7a6SAndroid Build Coastguard Worker
156*523fa7a6SAndroid Build Coastguard Worker
157*523fa7a6SAndroid Build Coastguard Workerdef demangle_symbols(cxxfilt: str, mangled_symbols: Iterable[Symbol]) -> None:
158*523fa7a6SAndroid Build Coastguard Worker    """Demangle a collection of symbols using the cxxfilt tool."""
159*523fa7a6SAndroid Build Coastguard Worker    output = get_tool_output([cxxfilt] + [symbol.mangled for symbol in mangled_symbols])
160*523fa7a6SAndroid Build Coastguard Worker    for symbol, demangled in zip(mangled_symbols, output.splitlines()):
161*523fa7a6SAndroid Build Coastguard Worker        symbol.demangled = demangled
162*523fa7a6SAndroid Build Coastguard Worker
163*523fa7a6SAndroid Build Coastguard Worker
164*523fa7a6SAndroid Build Coastguard Workerdef check_disallowed_symbols(cxxfilt: str, symbols: Iterable[Symbol]) -> None:
165*523fa7a6SAndroid Build Coastguard Worker    """Check a collection of symbols for disallowed prefixes."""
166*523fa7a6SAndroid Build Coastguard Worker    for symbol in symbols:
167*523fa7a6SAndroid Build Coastguard Worker        assert len(symbol.demangled) > 0
168*523fa7a6SAndroid Build Coastguard Worker        if symbol.demangled.startswith(tuple(DISALLOW_LIST)):
169*523fa7a6SAndroid Build Coastguard Worker            symbol.disallowed = True
170*523fa7a6SAndroid Build Coastguard Worker
171*523fa7a6SAndroid Build Coastguard Worker
172*523fa7a6SAndroid Build Coastguard Workerdef get_cached_symbols(nm: str, build_root: Path) -> Dict[str, Symbol]:
173*523fa7a6SAndroid Build Coastguard Worker    """Return a dictionary of symbols scraped from build files"""
174*523fa7a6SAndroid Build Coastguard Worker    global symbols_cache
175*523fa7a6SAndroid Build Coastguard Worker
176*523fa7a6SAndroid Build Coastguard Worker    if symbols_cache is not None:
177*523fa7a6SAndroid Build Coastguard Worker        return symbols_cache
178*523fa7a6SAndroid Build Coastguard Worker    symbols = {}
179*523fa7a6SAndroid Build Coastguard Worker
180*523fa7a6SAndroid Build Coastguard Worker    if not build_root.is_dir():
181*523fa7a6SAndroid Build Coastguard Worker        error("Specified buck-out is not a directory")
182*523fa7a6SAndroid Build Coastguard Worker
183*523fa7a6SAndroid Build Coastguard Worker    for root, _, files in os.walk(build_root):
184*523fa7a6SAndroid Build Coastguard Worker        root_path = Path(root)
185*523fa7a6SAndroid Build Coastguard Worker        for file_name in files:
186*523fa7a6SAndroid Build Coastguard Worker            file_path = root_path / file_name
187*523fa7a6SAndroid Build Coastguard Worker            if file_path.suffix == OBJECT_SUFFIX:
188*523fa7a6SAndroid Build Coastguard Worker                object_file_path = file_path
189*523fa7a6SAndroid Build Coastguard Worker                source_file_name = object_file_path.name[: -len(OBJECT_SUFFIX)]
190*523fa7a6SAndroid Build Coastguard Worker
191*523fa7a6SAndroid Build Coastguard Worker                object_file_rel = Path(os.path.relpath(object_file_path, build_root))
192*523fa7a6SAndroid Build Coastguard Worker                if "codegen" in str(object_file_path):
193*523fa7a6SAndroid Build Coastguard Worker                    source_file_path = source_file_name + " (generated)"
194*523fa7a6SAndroid Build Coastguard Worker                else:
195*523fa7a6SAndroid Build Coastguard Worker                    source_file_path = (
196*523fa7a6SAndroid Build Coastguard Worker                        PROJECT_ROOT / object_file_rel.parent.parent / source_file_name
197*523fa7a6SAndroid Build Coastguard Worker                    )
198*523fa7a6SAndroid Build Coastguard Worker                get_object_symbols(nm, symbols, object_file_path, source_file_path)
199*523fa7a6SAndroid Build Coastguard Worker
200*523fa7a6SAndroid Build Coastguard Worker    symbols_cache = symbols
201*523fa7a6SAndroid Build Coastguard Worker    return symbols_cache
202*523fa7a6SAndroid Build Coastguard Worker
203*523fa7a6SAndroid Build Coastguard Worker
204*523fa7a6SAndroid Build Coastguard Workerdef check_dependencies(readelf: str, binary_file: Path) -> int:
205*523fa7a6SAndroid Build Coastguard Worker    """Check that there are no shared object dependencies of a binary executable."""
206*523fa7a6SAndroid Build Coastguard Worker    elf_dependencies = get_elf_dependencies(readelf, binary_file)
207*523fa7a6SAndroid Build Coastguard Worker    if len(elf_dependencies) > 0:
208*523fa7a6SAndroid Build Coastguard Worker        print("Found the following shared object dependencies:")
209*523fa7a6SAndroid Build Coastguard Worker        for dependency in elf_dependencies:
210*523fa7a6SAndroid Build Coastguard Worker            print(" *", dependency)
211*523fa7a6SAndroid Build Coastguard Worker        print()
212*523fa7a6SAndroid Build Coastguard Worker        return STATUS_ERROR
213*523fa7a6SAndroid Build Coastguard Worker    return STATUS_OK
214*523fa7a6SAndroid Build Coastguard Worker
215*523fa7a6SAndroid Build Coastguard Worker
216*523fa7a6SAndroid Build Coastguard Workerdef check_disallowed_symbols_build_dir(nm: str, cxxfilt: str, build_root: Path) -> int:
217*523fa7a6SAndroid Build Coastguard Worker    """Check that there are no disallowed symbols used in intermediate build files."""
218*523fa7a6SAndroid Build Coastguard Worker    symbols = get_cached_symbols(nm, build_root)
219*523fa7a6SAndroid Build Coastguard Worker    symbol_list = list(symbols.values())
220*523fa7a6SAndroid Build Coastguard Worker    demangle_symbols(cxxfilt, symbol_list)
221*523fa7a6SAndroid Build Coastguard Worker    check_disallowed_symbols(cxxfilt, symbol_list)
222*523fa7a6SAndroid Build Coastguard Worker    disallowed_symbols = filter(lambda symbol: symbol.disallowed, symbol_list)
223*523fa7a6SAndroid Build Coastguard Worker
224*523fa7a6SAndroid Build Coastguard Worker    disallowed_by_file = {}
225*523fa7a6SAndroid Build Coastguard Worker    for symbol in disallowed_symbols:
226*523fa7a6SAndroid Build Coastguard Worker        for file in symbol.sources:
227*523fa7a6SAndroid Build Coastguard Worker            if file not in disallowed_by_file:
228*523fa7a6SAndroid Build Coastguard Worker                disallowed_by_file[file] = []
229*523fa7a6SAndroid Build Coastguard Worker            disallowed_by_file[file].append(symbol)
230*523fa7a6SAndroid Build Coastguard Worker
231*523fa7a6SAndroid Build Coastguard Worker    for file, symbols in disallowed_by_file.items():
232*523fa7a6SAndroid Build Coastguard Worker        print(f"{file} contains disallowed symbols:")
233*523fa7a6SAndroid Build Coastguard Worker        for symbol in symbols:
234*523fa7a6SAndroid Build Coastguard Worker            print(" *", symbol.demangled)
235*523fa7a6SAndroid Build Coastguard Worker        print()
236*523fa7a6SAndroid Build Coastguard Worker
237*523fa7a6SAndroid Build Coastguard Worker    if len(disallowed_by_file) > 0:
238*523fa7a6SAndroid Build Coastguard Worker        return STATUS_ERROR
239*523fa7a6SAndroid Build Coastguard Worker
240*523fa7a6SAndroid Build Coastguard Worker    return STATUS_OK
241*523fa7a6SAndroid Build Coastguard Worker
242*523fa7a6SAndroid Build Coastguard Worker
243*523fa7a6SAndroid Build Coastguard Workerdef check_dynamic(
244*523fa7a6SAndroid Build Coastguard Worker    nm: str, readelf: str, cxxfilt: str, binary_file: Path, build_root: Optional[Path]
245*523fa7a6SAndroid Build Coastguard Worker) -> int:
246*523fa7a6SAndroid Build Coastguard Worker    """Check for dynamic symbols required by an executable, categorizing them from the
247*523fa7a6SAndroid Build Coastguard Worker    intermediate files that may have included those symbols.
248*523fa7a6SAndroid Build Coastguard Worker    """
249*523fa7a6SAndroid Build Coastguard Worker    symbols = get_cached_symbols(nm, build_root) if build_root is not None else {}
250*523fa7a6SAndroid Build Coastguard Worker
251*523fa7a6SAndroid Build Coastguard Worker    dynamic_symbols = []
252*523fa7a6SAndroid Build Coastguard Worker    binary_dyn_sym = get_binary_dynamic_symbols(readelf, binary_file)
253*523fa7a6SAndroid Build Coastguard Worker    for symbol in binary_dyn_sym:
254*523fa7a6SAndroid Build Coastguard Worker        if symbols is not None and symbol in symbols:
255*523fa7a6SAndroid Build Coastguard Worker            dynamic_symbols.append(symbols[symbol])
256*523fa7a6SAndroid Build Coastguard Worker        else:
257*523fa7a6SAndroid Build Coastguard Worker            dynamic_symbols.append(Symbol(symbol, "", False, False, []))
258*523fa7a6SAndroid Build Coastguard Worker    demangle_symbols(cxxfilt, dynamic_symbols)
259*523fa7a6SAndroid Build Coastguard Worker    check_disallowed_symbols(cxxfilt, dynamic_symbols)
260*523fa7a6SAndroid Build Coastguard Worker
261*523fa7a6SAndroid Build Coastguard Worker    dynamic_by_file = {}
262*523fa7a6SAndroid Build Coastguard Worker    global_dynamic = []
263*523fa7a6SAndroid Build Coastguard Worker    for symbol in dynamic_symbols:
264*523fa7a6SAndroid Build Coastguard Worker        if len(symbol.sources) == 0:
265*523fa7a6SAndroid Build Coastguard Worker            global_dynamic.append(symbol)
266*523fa7a6SAndroid Build Coastguard Worker            continue
267*523fa7a6SAndroid Build Coastguard Worker
268*523fa7a6SAndroid Build Coastguard Worker        for file in symbol.sources:
269*523fa7a6SAndroid Build Coastguard Worker            if file not in dynamic_by_file:
270*523fa7a6SAndroid Build Coastguard Worker                dynamic_by_file[file] = []
271*523fa7a6SAndroid Build Coastguard Worker            dynamic_by_file[file].append(symbol)
272*523fa7a6SAndroid Build Coastguard Worker
273*523fa7a6SAndroid Build Coastguard Worker    print("Executable relies on the following dynamic symbols:")
274*523fa7a6SAndroid Build Coastguard Worker    for file, symbols in dynamic_by_file.items():
275*523fa7a6SAndroid Build Coastguard Worker        print(f"{file} contains dynamic symbols:")
276*523fa7a6SAndroid Build Coastguard Worker        for symbol in symbols:
277*523fa7a6SAndroid Build Coastguard Worker            print(" *", symbol.demangled)
278*523fa7a6SAndroid Build Coastguard Worker        print()
279*523fa7a6SAndroid Build Coastguard Worker
280*523fa7a6SAndroid Build Coastguard Worker    if len(dynamic_by_file) > 0:
281*523fa7a6SAndroid Build Coastguard Worker        return STATUS_ERROR
282*523fa7a6SAndroid Build Coastguard Worker
283*523fa7a6SAndroid Build Coastguard Worker    return STATUS_OK
284*523fa7a6SAndroid Build Coastguard Worker
285*523fa7a6SAndroid Build Coastguard Worker
286*523fa7a6SAndroid Build Coastguard Workerdef bubble_error(program_status, routine_status) -> int:
287*523fa7a6SAndroid Build Coastguard Worker    """Bubble a routine's error status up to the program status."""
288*523fa7a6SAndroid Build Coastguard Worker    # A non-OK error status overrides an OK error status.
289*523fa7a6SAndroid Build Coastguard Worker    if routine_status == STATUS_OK:
290*523fa7a6SAndroid Build Coastguard Worker        return program_status
291*523fa7a6SAndroid Build Coastguard Worker    elif program_status == STATUS_OK:
292*523fa7a6SAndroid Build Coastguard Worker        return routine_status
293*523fa7a6SAndroid Build Coastguard Worker    else:
294*523fa7a6SAndroid Build Coastguard Worker        return min(program_status, routine_status)
295*523fa7a6SAndroid Build Coastguard Worker
296*523fa7a6SAndroid Build Coastguard Worker
297*523fa7a6SAndroid Build Coastguard Workerdef main() -> int:
298*523fa7a6SAndroid Build Coastguard Worker    """Parse command line arguments and execute tool."""
299*523fa7a6SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
300*523fa7a6SAndroid Build Coastguard Worker        description="A tool to help check binary dependencies and statically included symbols."
301*523fa7a6SAndroid Build Coastguard Worker    )
302*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
303*523fa7a6SAndroid Build Coastguard Worker        "--nm",
304*523fa7a6SAndroid Build Coastguard Worker        metavar="executable",
305*523fa7a6SAndroid Build Coastguard Worker        type=str,
306*523fa7a6SAndroid Build Coastguard Worker        help="Path of the nm tool executable",
307*523fa7a6SAndroid Build Coastguard Worker        default="nm",
308*523fa7a6SAndroid Build Coastguard Worker    )
309*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
310*523fa7a6SAndroid Build Coastguard Worker        "--readelf",
311*523fa7a6SAndroid Build Coastguard Worker        metavar="executable",
312*523fa7a6SAndroid Build Coastguard Worker        type=str,
313*523fa7a6SAndroid Build Coastguard Worker        help="Path of the readelf tool executable",
314*523fa7a6SAndroid Build Coastguard Worker        default="readelf",
315*523fa7a6SAndroid Build Coastguard Worker    )
316*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
317*523fa7a6SAndroid Build Coastguard Worker        "--cxxfilt",
318*523fa7a6SAndroid Build Coastguard Worker        metavar="executable",
319*523fa7a6SAndroid Build Coastguard Worker        type=str,
320*523fa7a6SAndroid Build Coastguard Worker        help="Path of the cxxfilt tool executable",
321*523fa7a6SAndroid Build Coastguard Worker        default="c++filt",
322*523fa7a6SAndroid Build Coastguard Worker    )
323*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument("--binary", metavar="binary", type=str, help="Binary to check")
324*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
325*523fa7a6SAndroid Build Coastguard Worker        "--buck-out", metavar="dir", type=str, help="Buck output directory"
326*523fa7a6SAndroid Build Coastguard Worker    )
327*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
328*523fa7a6SAndroid Build Coastguard Worker        "--check-dependencies",
329*523fa7a6SAndroid Build Coastguard Worker        action="store_true",
330*523fa7a6SAndroid Build Coastguard Worker        help="Check shared library dependencies for a binary",
331*523fa7a6SAndroid Build Coastguard Worker    )
332*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
333*523fa7a6SAndroid Build Coastguard Worker        "--check-disallowed-symbols",
334*523fa7a6SAndroid Build Coastguard Worker        action="store_true",
335*523fa7a6SAndroid Build Coastguard Worker        help="Check for usage of disallowed symbols",
336*523fa7a6SAndroid Build Coastguard Worker    )
337*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
338*523fa7a6SAndroid Build Coastguard Worker        "--check-dynamic",
339*523fa7a6SAndroid Build Coastguard Worker        action="store_true",
340*523fa7a6SAndroid Build Coastguard Worker        help="Check for usage of dynamic symbols",
341*523fa7a6SAndroid Build Coastguard Worker    )
342*523fa7a6SAndroid Build Coastguard Worker
343*523fa7a6SAndroid Build Coastguard Worker    args = parser.parse_args()
344*523fa7a6SAndroid Build Coastguard Worker
345*523fa7a6SAndroid Build Coastguard Worker    exit_status = STATUS_OK
346*523fa7a6SAndroid Build Coastguard Worker
347*523fa7a6SAndroid Build Coastguard Worker    if args.check_dependencies:
348*523fa7a6SAndroid Build Coastguard Worker        if args.binary is None:
349*523fa7a6SAndroid Build Coastguard Worker            error("--binary flag must be specified when checking dependencies")
350*523fa7a6SAndroid Build Coastguard Worker        status = check_dependencies(args.readelf, Path(args.binary))
351*523fa7a6SAndroid Build Coastguard Worker        exit_status = bubble_error(exit_status, status)
352*523fa7a6SAndroid Build Coastguard Worker
353*523fa7a6SAndroid Build Coastguard Worker    if args.check_disallowed_symbols:
354*523fa7a6SAndroid Build Coastguard Worker        if args.buck_out is None:
355*523fa7a6SAndroid Build Coastguard Worker            error("--buck-out flag must be specified when checking disallowed symbols")
356*523fa7a6SAndroid Build Coastguard Worker        status = check_disallowed_symbols_build_dir(
357*523fa7a6SAndroid Build Coastguard Worker            args.nm, args.cxxfilt, Path(args.buck_out)
358*523fa7a6SAndroid Build Coastguard Worker        )
359*523fa7a6SAndroid Build Coastguard Worker        exit_status = bubble_error(exit_status, status)
360*523fa7a6SAndroid Build Coastguard Worker
361*523fa7a6SAndroid Build Coastguard Worker    if args.check_dynamic:
362*523fa7a6SAndroid Build Coastguard Worker        if args.binary is None:
363*523fa7a6SAndroid Build Coastguard Worker            error("--binary flag must be specified when checking dynamic symbol usage")
364*523fa7a6SAndroid Build Coastguard Worker        status = check_dynamic(
365*523fa7a6SAndroid Build Coastguard Worker            args.nm,
366*523fa7a6SAndroid Build Coastguard Worker            args.readelf,
367*523fa7a6SAndroid Build Coastguard Worker            args.cxxfilt,
368*523fa7a6SAndroid Build Coastguard Worker            Path(args.binary),
369*523fa7a6SAndroid Build Coastguard Worker            Path(args.buck_out) if args.buck_out is not None else None,
370*523fa7a6SAndroid Build Coastguard Worker        )
371*523fa7a6SAndroid Build Coastguard Worker        exit_status = bubble_error(exit_status, status)
372*523fa7a6SAndroid Build Coastguard Worker
373*523fa7a6SAndroid Build Coastguard Worker    return exit_status
374*523fa7a6SAndroid Build Coastguard Worker
375*523fa7a6SAndroid Build Coastguard Worker
376*523fa7a6SAndroid Build Coastguard Workerif __name__ == "__main__":
377*523fa7a6SAndroid Build Coastguard Worker    sys.exit(main())
378