waf: a better way to detect duplicated symbols
authorAndrew Tridgell <tridge@samba.org>
Wed, 6 Apr 2011 03:35:49 +0000 (13:35 +1000)
committerAndrew Tridgell <tridge@samba.org>
Wed, 6 Apr 2011 04:44:14 +0000 (06:44 +0200)
this detects when we have the same symbol linked in twice in any
binary by using ldd and nm on the binary and its associated libraries.

Some of these duplicates are caused by a subsystem being linked twice,
and some are caused by two versions of the same function name being
linked into a binary

Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org>

Autobuild-User: Andrew Tridgell <tridge@samba.org>
Autobuild-Date: Wed Apr  6 06:44:14 CEST 2011 on sn-devel-104

buildtools/wafsamba/symbols.py

index 0e862cb..5059c60 100644 (file)
@@ -17,6 +17,7 @@ from samba_utils import *
 #
 # bld.env.syslib_symbols: dictionary mapping system library name to set of symbols
 #                         for that library
+# bld.env.library_dict  : dictionary mapping built library paths to subsystem names
 #
 # LOCAL_CACHE(bld, 'TARGET_TYPE') : dictionary mapping subsystem name to target type
 
@@ -30,11 +31,11 @@ def symbols_extract(objfiles, dynamic=False):
     if dynamic:
         # needed for some .so files
         cmd.append("-D")
-    cmd.extend(objfiles)
+    cmd.extend(list(objfiles))
 
     nmpipe = subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
     if len(objfiles) == 1:
-        filename = objfiles[0]
+        filename = list(objfiles)[0]
         ret[filename] = { "PUBLIC": set(), "UNDEFINED" : set()}
 
     for line in nmpipe:
@@ -68,6 +69,35 @@ def real_name(name):
     return name
 
 
+def get_ldd_libs(bld, binname):
+    '''find the list of linked libraries for any binary or library
+    binname is the path to the binary/library on disk
+    '''
+    ret = set()
+    lddpipe = subprocess.Popen(['ldd', binname], stdout=subprocess.PIPE).stdout
+    for line in lddpipe:
+        line = line.strip()
+        cols = line.split(" ")
+        if len(cols) < 3 or cols[1] != "=>" or cols[2] == '':
+            continue
+        ret.add(os.path.realpath(cols[2]))
+    return ret
+
+
+def get_ldd_libs_recursive(bld, binname, seen=set()):
+    '''find the recursive list of linked libraries for any binary or library
+    binname is the path to the binary/library on disk. seen is a set used
+    to prevent loops
+    '''
+    if binname in seen:
+        return set()
+    ret = get_ldd_libs(bld, binname)
+    seen.add(binname)
+    for lib in ret:
+        ret = ret.union(get_ldd_libs_recursive(bld, lib, seen))
+    return ret
+
+
 def find_syslib_path(bld, libname, deps):
     '''find the path to the syslib we will link against'''
     # the strategy is to use the targets that depend on the library, and run ldd
@@ -160,6 +190,20 @@ def build_symbol_sets(bld, tgt_list):
                 bld.env.used_symbols[name] = bld.env.used_symbols[name].union(t2.used_symbols)
 
 
+def build_library_dict(bld, tgt_list):
+    '''build the library_dict dictionary'''
+
+    if bld.env.library_dict:
+        return
+
+    bld.env.library_dict = {}
+
+    for t in tgt_list:
+        if t.samba_type in [ 'LIBRARY', 'PYTHON' ]:
+            linkpath = os.path.realpath(t.link_task.outputs[0].abspath(bld.env))
+            bld.env.library_dict[linkpath] = t.sname
+
+
 def build_syslib_sets(bld, tgt_list):
     '''build the public_symbols for all syslibs'''
 
@@ -444,6 +488,37 @@ def symbols_whyneeded(task):
         Logs.info("target '%s' uses symbols %s from '%s'" % (target, overlap, subsystem))
 
 
+def report_duplicate(bld, binname, sym, libs):
+    '''report duplicated symbols'''
+    if sym in ['_init', '_fini']:
+        return
+    libnames = []
+    for lib in libs:
+        if lib in bld.env.library_dict:
+            libnames.append(bld.env.library_dict[lib])
+        else:
+            libnames.append(lib)
+    print("%s: Symbol %s linked in multiple libraries %s" % (binname, sym, libnames))
+
+
+def symbols_dupcheck_binary(bld, binname):
+    '''check for duplicated symbols in one binary'''
+    libs = get_ldd_libs_recursive(bld, binname)
+
+    symlist = symbols_extract(libs, dynamic=True)
+
+    symmap = {}
+    for libpath in symlist:
+        for sym in symlist[libpath]['PUBLIC']:
+            if not sym in symmap:
+                symmap[sym] = set()
+            symmap[sym].add(libpath)
+    for sym in symmap:
+        if len(symmap[sym]) > 1:
+            for libpath in symmap[sym]:
+                if libpath in bld.env.library_dict:
+                    report_duplicate(bld, binname, sym, symmap[sym])
+                    break
 
 def symbols_dupcheck(task):
     '''check for symbols defined in two different subsystems'''
@@ -452,25 +527,12 @@ def symbols_dupcheck(task):
 
     targets = LOCAL_CACHE(bld, 'TARGET_TYPE')
 
-    Logs.info("Checking for duplicate symbols")
-    for sym in bld.env.symbol_map:
-        subsystems = set(bld.env.symbol_map[sym])
-        if len(subsystems) == 1:
-            continue
-
-        if sym in ['main', '_init', '_fini', 'init_samba_module', 'samba_init_module', 'ldb_init_module' ]:
-            # these are expected to be in many subsystems
-            continue
+    build_library_dict(bld, tgt_list)
+    for t in tgt_list:
+        if t.samba_type == 'BINARY':
+            binname = os_path_relpath(t.link_task.outputs[0].abspath(bld.env), os.getcwd())
+            symbols_dupcheck_binary(bld, binname)
 
-        # if all of them are in system libraries, we can ignore them. This copes
-        # with the duplication between libc, libpthread and libattr
-        all_syslib = True
-        for s in subsystems:
-            if s != 'c' and (not s in targets or targets[s] != 'SYSLIB'):
-                all_syslib = False
-        if all_syslib:
-            continue
-        Logs.info("symbol %s appears in %s" % (sym, subsystems))
 
 
 def SYMBOL_CHECK(bld):