1import os.path
2import shutil
3
4from c_analyzer.common import util, info
5
6from .info import Symbol
7
8
9# XXX need tests:
10# * iter_symbols
11
12NM_KINDS = {
13        'b': Symbol.KIND.VARIABLE,  # uninitialized
14        'd': Symbol.KIND.VARIABLE,  # initialized
15        #'g': Symbol.KIND.VARIABLE,  # uninitialized
16        #'s': Symbol.KIND.VARIABLE,  # initialized
17        't': Symbol.KIND.FUNCTION,
18        }
19
20SPECIAL_SYMBOLS = {
21        # binary format (e.g. ELF)
22        '__bss_start',
23        '__data_start',
24        '__dso_handle',
25        '_DYNAMIC',
26        '_edata',
27        '_end',
28        '__environ@@GLIBC_2.2.5',
29        '_GLOBAL_OFFSET_TABLE_',
30        '__JCR_END__',
31        '__JCR_LIST__',
32        '__TMC_END__',
33        }
34
35
36def _is_special_symbol(name):
37    if name in SPECIAL_SYMBOLS:
38        return True
39    if '@@GLIBC' in name:
40        return True
41    return False
42
43
44def iter_symbols(binfile, *,
45                 nm=None,
46                 handle_id=None,
47                 _which=shutil.which,
48                 _run=util.run_cmd,
49                 ):
50    """Yield a Symbol for each relevant entry reported by the "nm" command."""
51    if nm is None:
52        nm = _which('nm')
53        if not nm:
54            raise NotImplementedError
55    if handle_id is None:
56        handle_id = info.ID
57
58    argv = [nm,
59            '--line-numbers',
60            binfile,
61            ]
62    try:
63        output = _run(argv)
64    except Exception:
65        if nm is None:
66            # XXX Use dumpbin.exe /SYMBOLS on Windows.
67            raise NotImplementedError
68        raise
69    for line in output.splitlines():
70        (name, kind, external, filename, funcname,
71         ) = _parse_nm_line(line)
72        if kind != Symbol.KIND.VARIABLE:
73            continue
74        elif _is_special_symbol(name):
75            continue
76        yield Symbol(
77                id=handle_id(filename, funcname, name),
78                kind=kind,
79                external=external,
80                )
81
82
83def _parse_nm_line(line):
84    _origline = line
85    _, _, line = line.partition(' ')  # strip off the address
86    line = line.strip()
87
88    kind, _, line = line.partition(' ')
89    line = line.strip()
90    external = kind.isupper()
91    kind = NM_KINDS.get(kind.lower(), Symbol.KIND.OTHER)
92
93    name, _, filename = line.partition('\t')
94    name = name.strip()
95    if filename:
96        filename = os.path.relpath(filename.partition(':')[0])
97    else:
98        filename = info.UNKNOWN
99
100    name, islocal = _parse_nm_name(name, kind)
101    funcname = info.UNKNOWN if islocal else None
102    return name, kind, external, filename, funcname
103
104
105def _parse_nm_name(name, kind):
106    if kind != Symbol.KIND.VARIABLE:
107        return name, None
108    if _is_special_symbol(name):
109        return name, None
110
111    actual, sep, digits = name.partition('.')
112    if not sep:
113        return name, False
114
115    if not digits.isdigit():
116        raise Exception(f'got bogus name {name}')
117    return actual, True
118