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