xref: /llvm-project/lldb/scripts/msvc_extract_private_symbols.py (revision dc1a2cb9718966e9050e32962dc86377088f2d7e)
1"""A tool for extracting a list of private lldb symbols to export for MSVC.
2
3When exporting symbols from a dll or exe we either need to mark the symbols in
4the source code as __declspec(dllexport) or supply a list of symbols to the
5linker. Private symbols in LLDB don't explicitly specific dllexport, so we
6automate that by examining the symbol table.
7"""
8
9import argparse
10import os
11import re
12import subprocess
13import sys
14
15
16def extract_symbols(nm_path: str, lib: str):
17    """Extract all of the private lldb symbols from the given path to llvm-nm and
18    library to extract from."""
19
20    # Matches mangled symbols containing 'lldb_private'.
21    lldb_sym_re = r"[0-9a-zA-Z]* [BT] (?P<symbol>[?]+[^?].*lldb_private.*)"
22
23    # '-g' means we only get global symbols.
24    # '-p' do not waste time sorting the symbols.
25    process = subprocess.Popen(
26        [nm_path, "-g", "-p", lib],
27        bufsize=1,
28        stdout=subprocess.PIPE,
29        stdin=subprocess.PIPE,
30        universal_newlines=True,
31    )
32    process.stdin.close()
33
34    lldb_symbols = set()
35    for line in process.stdout:
36        match = re.match(lldb_sym_re, line)
37        if match:
38            symbol = match.group("symbol")
39            assert (
40                symbol.count(" ") == 0
41            ), "Regex matched too much, probably got undecorated name as well"
42            # Deleting destructors start with ?_G or ?_E and can be discarded
43            # because link.exe gives you a warning telling you they can't be
44            # exported if you don't.
45            if symbol.startswith("??_G") or symbol.startswith("??_E"):
46                continue
47            lldb_symbols.add(symbol)
48
49    return lldb_symbols
50
51
52def main():
53    parser = argparse.ArgumentParser(description="Generate LLDB dll exports")
54    parser.add_argument(
55        "-o", metavar="file", type=str, help="The name of the resultant export file."
56    )
57    parser.add_argument("--nm", help="Path to the llvm-nm executable.")
58    parser.add_argument(
59        "libs",
60        metavar="lib",
61        type=str,
62        nargs="+",
63        help="The libraries to extract symbols from.",
64    )
65    args = parser.parse_args()
66
67    # Get the list of libraries to extract symbols from
68    libs = list()
69    for lib in args.libs:
70        # When invoked by cmake the arguments are the cmake target names of the
71        # libraries, so we need to add .lib/.a to the end and maybe lib to the
72        # start to get the filename. Also allow objects.
73        suffixes = [".lib", ".a", ".obj", ".o"]
74        if not any([lib.endswith(s) for s in suffixes]):
75            for suffix in suffixes:
76                if os.path.exists(lib + suffix):
77                    lib = lib + suffix
78                    break
79                if os.path.exists("lib" + lib + suffix):
80                    lib = "lib" + lib + suffix
81                    break
82        if not any([lib.endswith(s) for s in suffixes]):
83            print(
84                "Unknown extension type for library argument: " + lib, file=sys.stderr
85            )
86            exit(1)
87        libs.append(lib)
88
89    # Extract symbols from the input libraries.
90    symbols = set()
91    for lib in libs:
92        for sym in list(extract_symbols(args.nm, lib)):
93            symbols.add(sym)
94
95    # Write out the symbols to the output file.
96    with open(args.o, "w", newline="") as f:
97        for s in sorted(symbols):
98            f.write(f"{s}\n")
99
100
101if __name__ == "__main__":
102    main()
103