• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os
2import os.path
3import shutil
4
5from ..common import files
6from ..common.info import UNKNOWN, ID
7from ..parser import find as p_find
8
9from . import _nm
10from .info import Symbol
11
12# XXX need tests:
13# * get_resolver()
14# * get_resolver_from_dirs()
15# * symbol()
16# * symbols()
17# * variables()
18
19
20def _resolve_known(symbol, knownvars):
21    for varid in knownvars:
22        if symbol.match(varid):
23            break
24    else:
25        return None
26    return knownvars.pop(varid)
27
28
29def get_resolver(filenames=None, known=None, *,
30                 handle_var,
31                 check_filename=None,
32                 perfilecache=None,
33                 preprocessed=False,
34                 _from_source=p_find.variable_from_id,
35                 ):
36    """Return a "resolver" func for the given known vars/types and filenames.
37
38    "handle_var" is a callable that takes (ID, decl) and returns a
39    Variable.  Variable.from_id is a suitable callable.
40
41    The returned func takes a single Symbol and returns a corresponding
42    Variable.  If the symbol was located then the variable will be
43    valid, populated with the corresponding information.  Otherwise None
44    is returned.
45    """
46    knownvars = (known or {}).get('variables')
47    if knownvars:
48        knownvars = dict(knownvars)  # a copy
49        if filenames:
50            if check_filename is None:
51                filenames = list(filenames)
52                def check_filename(filename):
53                    return filename in filenames
54            def resolve(symbol):
55                # XXX Check "found" instead?
56                if not check_filename(symbol.filename):
57                    return None
58                found = _resolve_known(symbol, knownvars)
59                if found is None:
60                    #return None
61                    varid, decl = _from_source(symbol, filenames,
62                                               perfilecache=perfilecache,
63                                               preprocessed=preprocessed,
64                                               )
65                    found = handle_var(varid, decl)
66                return found
67        else:
68            def resolve(symbol):
69                return _resolve_known(symbol, knownvars)
70    elif filenames:
71        def resolve(symbol):
72            varid, decl = _from_source(symbol, filenames,
73                                       perfilecache=perfilecache,
74                                       preprocessed=preprocessed,
75                                       )
76            return handle_var(varid, decl)
77    else:
78        def resolve(symbol):
79            return None
80    return resolve
81
82
83def get_resolver_from_dirs(dirnames, known=None, *,
84                           handle_var,
85                           suffixes=('.c',),
86                           perfilecache=None,
87                           preprocessed=False,
88                           _iter_files=files.iter_files_by_suffix,
89                           _get_resolver=get_resolver,
90                           ):
91    """Return a "resolver" func for the given known vars/types and filenames.
92
93    "dirnames" should be absolute paths.  If not then they will be
94    resolved relative to CWD.
95
96    See get_resolver().
97    """
98    dirnames = [d if d.endswith(os.path.sep) else d + os.path.sep
99                for d in dirnames]
100    filenames = _iter_files(dirnames, suffixes)
101    def check_filename(filename):
102        for dirname in dirnames:
103            if filename.startswith(dirname):
104                return True
105        else:
106            return False
107    return _get_resolver(filenames, known,
108                         handle_var=handle_var,
109                         check_filename=check_filename,
110                         perfilecache=perfilecache,
111                         preprocessed=preprocessed,
112                         )
113
114
115def symbol(symbol, filenames, known=None, *,
116           perfilecache=None,
117           preprocessed=False,
118           handle_id=None,
119           _get_resolver=get_resolver,
120           ):
121    """Return a Variable for the one matching the given symbol.
122
123    "symbol" can be one of several objects:
124
125    * Symbol - use the contained info
126    * name (str) - look for a global variable with that name
127    * (filename, name) - look for named global in file
128    * (filename, funcname, name) - look for named local in file
129
130    A name is always required.  If the filename is None, "", or
131    "UNKNOWN" then all files will be searched.  If the funcname is
132    "" or "UNKNOWN" then only local variables will be searched for.
133    """
134    resolve = _get_resolver(known, filenames,
135                            handle_id=handle_id,
136                            perfilecache=perfilecache,
137                            preprocessed=preprocessed,
138                            )
139    return resolve(symbol)
140
141
142def _get_platform_tool():
143    if os.name == 'nt':
144        # XXX Support this.
145        raise NotImplementedError
146    elif nm := shutil.which('nm'):
147        return lambda b, hi: _nm.iter_symbols(b, nm=nm, handle_id=hi)
148    else:
149        raise NotImplementedError
150
151
152def symbols(binfile, *,
153            handle_id=None,
154            _file_exists=os.path.exists,
155            _get_platform_tool=_get_platform_tool,
156            ):
157    """Yield a Symbol for each one found in the binary."""
158    if not _file_exists(binfile):
159        raise Exception('executable missing (need to build it first?)')
160
161    _iter_symbols = _get_platform_tool()
162    yield from _iter_symbols(binfile, handle_id)
163
164
165def variables(binfile, *,
166              resolve,
167              handle_id=None,
168              _iter_symbols=symbols,
169              ):
170    """Yield (Variable, Symbol) for each found symbol."""
171    for symbol in _iter_symbols(binfile, handle_id=handle_id):
172        if symbol.kind != Symbol.KIND.VARIABLE:
173            continue
174        var = resolve(symbol) or None
175        yield var, symbol
176