1 # a waf tool to extract symbols from object files or libraries
2 # using nm, producing a set of exposed defined/undefined symbols
4 import Utils, Build, subprocess, Logs
5 from samba_wildcard import fake_build_environment
6 from samba_utils import *
8 # these are the data structures used in symbols.py:
10 # bld.env.symbol_map : dictionary mapping public symbol names to list of
11 # subsystem names where that symbol exists
13 # t.in_library : list of libraries that t is in
15 # bld.env.public_symbols: set of public symbols for each subsystem
16 # bld.env.used_symbols : set of used symbols for each subsystem
18 # bld.env.syslib_symbols: dictionary mapping system library name to set of symbols
20 # bld.env.library_dict : dictionary mapping built library paths to subsystem names
22 # LOCAL_CACHE(bld, 'TARGET_TYPE') : dictionary mapping subsystem name to target type
24 def symbols_extract(objfiles, dynamic=False):
25 '''extract symbols from objfile, returning a dictionary containing
26 the set of undefined and public symbols for each file'''
32 # needed for some .so files
34 cmd.extend(list(objfiles))
36 nmpipe = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
37 if len(objfiles) == 1:
38 filename = list(objfiles)[0]
39 ret[filename] = { "PUBLIC": set(), "UNDEFINED" : set()}
43 if line.endswith(':'):
45 ret[filename] = { "PUBLIC": set(), "UNDEFINED" : set() }
47 cols = line.split(" ")
50 # see if the line starts with an address
57 if symbol_type in "BDGTRVWSi":
59 ret[filename]["PUBLIC"].add(symbol)
60 elif symbol_type in "U":
61 ret[filename]["UNDEFINED"].add(symbol)
67 if name.find(".objlist") != -1:
72 def get_ldd_libs(bld, binname):
73 '''find the list of linked libraries for any binary or library
74 binname is the path to the binary/library on disk
77 lddpipe = subprocess.Popen(['ldd', binname], stdout=subprocess.PIPE).stdout
80 cols = line.split(" ")
81 if len(cols) < 3 or cols[1] != "=>" or cols[2] == '':
83 ret.add(os.path.realpath(cols[2]))
87 def get_ldd_libs_recursive(bld, binname, seen=set()):
88 '''find the recursive list of linked libraries for any binary or library
89 binname is the path to the binary/library on disk. seen is a set used
94 ret = get_ldd_libs(bld, binname)
97 ret = ret.union(get_ldd_libs_recursive(bld, lib, seen))
101 def find_syslib_path(bld, libname, deps):
102 '''find the path to the syslib we will link against'''
103 # the strategy is to use the targets that depend on the library, and run ldd
104 # on it to find the real location of the library that is used
106 linkpath = deps[0].link_task.outputs[0].abspath(bld.env)
108 if libname == "python":
109 libname += bld.env.PYTHON_VERSION
113 lddpipe = subprocess.Popen(['ldd', linkpath], stdout=subprocess.PIPE).stdout
116 cols = line.split(" ")
117 if len(cols) < 3 or cols[1] != "=>":
119 if cols[0].startswith("lib%s." % libname.lower()):
121 if cols[0].startswith("libc."):
123 bld.env.libc_path = cols[2]
127 def build_symbol_sets(bld, tgt_list):
128 '''build the public_symbols and undefined_symbols attributes for each target'''
130 if bld.env.public_symbols:
133 objlist = [] # list of object file
134 objmap = {} # map from object filename to target (subsystem) name
137 t.public_symbols = set()
138 t.undefined_symbols = set()
139 t.used_symbols = set()
140 for tsk in getattr(t, 'compiled_tasks', []):
141 for output in tsk.outputs:
142 objpath = output.abspath(bld.env)
143 objlist.append(objpath)
146 symbols = symbols_extract(objlist)
149 t.public_symbols = t.public_symbols.union(symbols[obj]["PUBLIC"])
150 t.undefined_symbols = t.undefined_symbols.union(symbols[obj]["UNDEFINED"])
151 t.used_symbols = t.used_symbols.union(symbols[obj]["UNDEFINED"])
153 t.undefined_symbols = t.undefined_symbols.difference(t.public_symbols)
155 # and the reverse map of public symbols to subsystem name
156 bld.env.symbol_map = {}
159 for s in t.public_symbols:
160 if not s in bld.env.symbol_map:
161 bld.env.symbol_map[s] = []
162 bld.env.symbol_map[s].append(real_name(t.sname))
164 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
166 bld.env.public_symbols = {}
168 name = real_name(t.sname)
169 if name in bld.env.public_symbols:
170 bld.env.public_symbols[name] = bld.env.public_symbols[name].union(t.public_symbols)
172 bld.env.public_symbols[name] = t.public_symbols
173 if t.samba_type == 'LIBRARY':
174 for dep in t.add_objects:
175 t2 = bld.name_to_obj(dep, bld.env)
176 bld.ASSERT(t2 is not None, "Library '%s' has unknown dependency '%s'" % (name, dep))
177 bld.env.public_symbols[name] = bld.env.public_symbols[name].union(t2.public_symbols)
179 bld.env.used_symbols = {}
181 name = real_name(t.sname)
182 if name in bld.env.used_symbols:
183 bld.env.used_symbols[name] = bld.env.used_symbols[name].union(t.used_symbols)
185 bld.env.used_symbols[name] = t.used_symbols
186 if t.samba_type == 'LIBRARY':
187 for dep in t.add_objects:
188 t2 = bld.name_to_obj(dep, bld.env)
189 bld.ASSERT(t2 is not None, "Library '%s' has unknown dependency '%s'" % (name, dep))
190 bld.env.used_symbols[name] = bld.env.used_symbols[name].union(t2.used_symbols)
193 def build_library_dict(bld, tgt_list):
194 '''build the library_dict dictionary'''
196 if bld.env.library_dict:
199 bld.env.library_dict = {}
202 if t.samba_type in [ 'LIBRARY', 'PYTHON' ]:
203 linkpath = os.path.realpath(t.link_task.outputs[0].abspath(bld.env))
204 bld.env.library_dict[linkpath] = t.sname
207 def build_syslib_sets(bld, tgt_list):
208 '''build the public_symbols for all syslibs'''
210 if bld.env.syslib_symbols:
213 # work out what syslibs we depend on, and what targets those are used in
217 if getattr(t, 'uselib', []) and t.samba_type in [ 'LIBRARY', 'BINARY', 'PYTHON' ]:
219 if lib in ['PYEMBED', 'PYEXT']:
221 if not lib in syslibs:
223 syslibs[lib].append(t)
225 # work out the paths to each syslib
228 path = find_syslib_path(bld, lib, syslibs[lib])
230 Logs.warn("Unable to find syslib path for %s" % lib)
232 syslib_paths.append(path)
233 objmap[path] = lib.lower()
236 syslib_paths.append(bld.env.libc_path)
237 objmap[bld.env.libc_path] = 'c'
239 symbols = symbols_extract(syslib_paths, dynamic=True)
241 # keep a map of syslib names to public symbols
242 bld.env.syslib_symbols = {}
244 bld.env.syslib_symbols[lib] = symbols[lib]["PUBLIC"]
246 # add to the map of symbols to dependencies
248 for sym in symbols[lib]["PUBLIC"]:
249 if not sym in bld.env.symbol_map:
250 bld.env.symbol_map[sym] = []
251 bld.env.symbol_map[sym].append(objmap[lib])
253 # keep the libc symbols as well, as these are useful for some of the
255 bld.env.libc_symbols = symbols[bld.env.libc_path]["PUBLIC"]
257 # add to the combined map of dependency name to public_symbols
258 for lib in bld.env.syslib_symbols:
259 bld.env.public_symbols[objmap[lib]] = bld.env.syslib_symbols[lib]
262 def build_autodeps(bld, t):
263 '''build the set of dependencies for a target'''
265 name = real_name(t.sname)
267 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
269 for sym in t.undefined_symbols:
270 if sym in t.public_symbols:
272 if sym in bld.env.symbol_map:
273 depname = bld.env.symbol_map[sym]
274 if depname == [ name ]:
275 # self dependencies aren't interesting
277 if t.in_library == depname:
278 # no need to depend on the library we are part of
280 if depname[0] in ['c', 'python']:
281 # these don't go into autodeps
283 if targets[depname[0]] in [ 'SYSLIB' ]:
286 t2 = bld.name_to_obj(depname[0], bld.env)
287 if len(t2.in_library) != 1:
290 if t2.in_library == t.in_library:
291 # if we're part of the same library, we don't need to autodep
293 deps.add(t2.in_library[0])
297 def build_library_names(bld, tgt_list):
298 '''add a in_library attribute to all targets that are part of a library'''
300 if bld.env.done_build_library_names:
307 if t.samba_type in [ 'LIBRARY' ]:
308 for obj in t.samba_deps_extended:
309 t2 = bld.name_to_obj(obj, bld.env)
310 if t2 and t2.samba_type in [ 'SUBSYSTEM', 'ASN1' ]:
311 if not t.sname in t2.in_library:
312 t2.in_library.append(t.sname)
313 bld.env.done_build_library_names = True
316 def check_library_deps(bld, t):
317 '''check that all the autodeps that have mutual dependency of this
318 target are in the same library as the target'''
320 name = real_name(t.sname)
322 if len(t.in_library) > 1:
323 Logs.warn("WARNING: Target '%s' in multiple libraries: %s" % (t.sname, t.in_library))
325 for dep in t.autodeps:
326 t2 = bld.name_to_obj(dep, bld.env)
329 for dep2 in t2.autodeps:
330 if dep2 == name and t.in_library != t2.in_library:
331 Logs.warn("WARNING: mutual dependency %s <=> %s" % (name, real_name(t2.sname)))
332 Logs.warn("Libraries should match. %s != %s" % (t.in_library, t2.in_library))
333 # raise Utils.WafError("illegal mutual dependency")
336 def check_syslib_collisions(bld, tgt_list):
337 '''check if a target has any symbol collisions with a syslib
339 We do not want any code in Samba to use a symbol name from a
340 system library. The chance of that causing problems is just too
341 high. Note that libreplace uses a rep_XX approach of renaming
347 for lib in bld.env.syslib_symbols:
348 common = t.public_symbols.intersection(bld.env.syslib_symbols[lib])
350 Logs.error("ERROR: Target '%s' has symbols '%s' which is also in syslib '%s'" % (t.sname, common, lib))
353 raise Utils.WafError("symbols in common with system libraries")
356 def check_dependencies(bld, t):
357 '''check for depenencies that should be changed'''
359 if bld.name_to_obj(t.sname + ".objlist", bld.env):
362 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
364 remaining = t.undefined_symbols.copy()
365 remaining = remaining.difference(t.public_symbols)
367 sname = real_name(t.sname)
369 deps = set(t.samba_deps)
370 for d in t.samba_deps:
371 if targets[d] in [ 'EMPTY', 'DISABLED', 'SYSLIB' ]:
373 bld.ASSERT(d in bld.env.public_symbols, "Failed to find symbol list for dependency '%s'" % d)
374 diff = remaining.intersection(bld.env.public_symbols[d])
375 if not diff and targets[sname] != 'LIBRARY':
376 Logs.info("Target '%s' has no dependency on %s" % (sname, d))
378 remaining = remaining.difference(diff)
380 t.unsatisfied_symbols = set()
382 for sym in remaining:
383 if sym in bld.env.symbol_map:
384 dep = bld.env.symbol_map[sym]
385 if not dep[0] in needed:
386 needed[dep[0]] = set()
387 needed[dep[0]].add(sym)
389 t.unsatisfied_symbols.add(sym)
392 Logs.info("Target '%s' should add dep '%s' for symbols %s" % (sname, dep, " ".join(needed[dep])))
396 def check_syslib_dependencies(bld, t):
397 '''check for syslib depenencies'''
399 if bld.name_to_obj(t.sname + ".objlist", bld.env):
402 sname = real_name(t.sname)
406 features = TO_LIST(t.features)
407 if 'pyembed' in features or 'pyext' in features:
408 if 'python' in bld.env.public_symbols:
409 t.unsatisfied_symbols = t.unsatisfied_symbols.difference(bld.env.public_symbols['python'])
412 for sym in t.unsatisfied_symbols:
413 if sym in bld.env.symbol_map:
414 dep = bld.env.symbol_map[sym][0]
417 if not dep in needed:
424 Logs.info("Target '%s' should add syslib dep '%s' for symbols %s" % (sname, dep, " ".join(needed[dep])))
427 debug("deps: Target '%s' has unsatisfied symbols: %s" % (sname, " ".join(remaining)))
431 def symbols_symbolcheck(task):
432 '''check the internal dependency lists'''
434 tgt_list = get_tgt_list(bld)
436 build_symbol_sets(bld, tgt_list)
437 build_library_names(bld, tgt_list)
441 if getattr(t, 'source', ''):
442 build_autodeps(bld, t)
445 check_dependencies(bld, t)
448 check_library_deps(bld, t)
450 def symbols_syslibcheck(task):
451 '''check the syslib dependencies'''
453 tgt_list = get_tgt_list(bld)
455 build_syslib_sets(bld, tgt_list)
456 check_syslib_collisions(bld, tgt_list)
459 check_syslib_dependencies(bld, t)
462 def symbols_whyneeded(task):
463 """check why 'target' needs to link to 'subsystem'"""
465 tgt_list = get_tgt_list(bld)
467 why = Options.options.WHYNEEDED.split(":")
469 raise Utils.WafError("usage: WHYNEEDED=TARGET:DEPENDENCY")
473 build_symbol_sets(bld, tgt_list)
474 build_library_names(bld, tgt_list)
475 build_syslib_sets(bld, tgt_list)
477 Logs.info("Checking why %s needs to link to %s" % (target, subsystem))
478 if not target in bld.env.used_symbols:
479 Logs.warn("unable to find target '%s' in used_symbols dict" % target)
481 if not subsystem in bld.env.public_symbols:
482 Logs.warn("unable to find subsystem '%s' in public_symbols dict" % subsystem)
484 overlap = bld.env.used_symbols[target].intersection(bld.env.public_symbols[subsystem])
486 Logs.info("target '%s' doesn't use any public symbols from '%s'" % (target, subsystem))
488 Logs.info("target '%s' uses symbols %s from '%s'" % (target, overlap, subsystem))
491 def report_duplicate(bld, binname, sym, libs):
492 '''report duplicated symbols'''
493 if sym in ['_init', '_fini']:
497 if lib in bld.env.library_dict:
498 libnames.append(bld.env.library_dict[lib])
501 print("%s: Symbol %s linked in multiple libraries %s" % (binname, sym, libnames))
504 def symbols_dupcheck_binary(bld, binname):
505 '''check for duplicated symbols in one binary'''
506 libs = get_ldd_libs_recursive(bld, binname)
508 symlist = symbols_extract(libs, dynamic=True)
511 for libpath in symlist:
512 for sym in symlist[libpath]['PUBLIC']:
513 if not sym in symmap:
515 symmap[sym].add(libpath)
517 if len(symmap[sym]) > 1:
518 for libpath in symmap[sym]:
519 if libpath in bld.env.library_dict:
520 report_duplicate(bld, binname, sym, symmap[sym])
523 def symbols_dupcheck(task):
524 '''check for symbols defined in two different subsystems'''
526 tgt_list = get_tgt_list(bld)
528 targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
530 build_library_dict(bld, tgt_list)
532 if t.samba_type == 'BINARY':
533 binname = os_path_relpath(t.link_task.outputs[0].abspath(bld.env), os.getcwd())
534 symbols_dupcheck_binary(bld, binname)
538 def SYMBOL_CHECK(bld):
539 '''check our dependency lists'''
540 if Options.options.SYMBOLCHECK:
541 bld.SET_BUILD_GROUP('symbolcheck')
542 task = bld(rule=symbols_symbolcheck, always=True, name='symbol checking')
545 bld.SET_BUILD_GROUP('syslibcheck')
546 task = bld(rule=symbols_syslibcheck, always=True, name='syslib checking')
549 bld.SET_BUILD_GROUP('syslibcheck')
550 task = bld(rule=symbols_dupcheck, always=True, name='symbol duplicate checking')
553 if Options.options.WHYNEEDED:
554 bld.SET_BUILD_GROUP('syslibcheck')
555 task = bld(rule=symbols_whyneeded, always=True, name='check why a dependency is needed')
559 Build.BuildContext.SYMBOL_CHECK = SYMBOL_CHECK