summaryrefslogtreecommitdiff
path: root/Tools/c-analyzer/c_analyzer/symbols/_nm.py
blob: f3a75a6d4ba8241900e94a8c8f52df7327ebc3a8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import os.path
import shutil

from c_analyzer.common import util, info

from .info import Symbol


# XXX need tests:
# * iter_symbols

NM_KINDS = {
        'b': Symbol.KIND.VARIABLE,  # uninitialized
        'd': Symbol.KIND.VARIABLE,  # initialized
        #'g': Symbol.KIND.VARIABLE,  # uninitialized
        #'s': Symbol.KIND.VARIABLE,  # initialized
        't': Symbol.KIND.FUNCTION,
        }

SPECIAL_SYMBOLS = {
        # binary format (e.g. ELF)
        '__bss_start',
        '__data_start',
        '__dso_handle',
        '_DYNAMIC',
        '_edata',
        '_end',
        '__environ@@GLIBC_2.2.5',
        '_GLOBAL_OFFSET_TABLE_',
        '__JCR_END__',
        '__JCR_LIST__',
        '__TMC_END__',
        }


def _is_special_symbol(name):
    if name in SPECIAL_SYMBOLS:
        return True
    if '@@GLIBC' in name:
        return True
    return False


def iter_symbols(binfile, *,
                 nm=None,
                 handle_id=None,
                 _which=shutil.which,
                 _run=util.run_cmd,
                 ):
    """Yield a Symbol for each relevant entry reported by the "nm" command."""
    if nm is None:
        nm = _which('nm')
        if not nm:
            raise NotImplementedError
    if handle_id is None:
        handle_id = info.ID

    argv = [nm,
            '--line-numbers',
            binfile,
            ]
    try:
        output = _run(argv)
    except Exception:
        if nm is None:
            # XXX Use dumpbin.exe /SYMBOLS on Windows.
            raise NotImplementedError
        raise
    for line in output.splitlines():
        (name, kind, external, filename, funcname,
         ) = _parse_nm_line(line)
        if kind != Symbol.KIND.VARIABLE:
            continue
        elif _is_special_symbol(name):
            continue
        yield Symbol(
                id=handle_id(filename, funcname, name),
                kind=kind,
                external=external,
                )


def _parse_nm_line(line):
    _origline = line
    _, _, line = line.partition(' ')  # strip off the address
    line = line.strip()

    kind, _, line = line.partition(' ')
    line = line.strip()
    external = kind.isupper()
    kind = NM_KINDS.get(kind.lower(), Symbol.KIND.OTHER)

    name, _, filename = line.partition('\t')
    name = name.strip()
    if filename:
        filename = os.path.relpath(filename.partition(':')[0])
    else:
        filename = info.UNKNOWN

    name, islocal = _parse_nm_name(name, kind)
    funcname = info.UNKNOWN if islocal else None
    return name, kind, external, filename, funcname


def _parse_nm_name(name, kind):
    if kind != Symbol.KIND.VARIABLE:
        return name, None
    if _is_special_symbol(name):
        return name, None

    actual, sep, digits = name.partition('.')
    if not sep:
        return name, False

    if not digits.isdigit():
        raise Exception(f'got bogus name {name}')
    return actual, True