[Buildroot] [PATCH 1/6] check-shlibs-deps: new script to check shared library dependencies

Jérôme Pouiller jezz at sysmic.org
Mon Nov 14 13:22:33 UTC 2016


Add a script that show or check binary dependencies based on linked shared
libraries.

To do that, it scan NEEDED entries in ELF header and get corresponding package
from packages-file-list.txt.

It have 3 modes:

    check-shlibs-deps -b OUTDIR
        Scan $TARGET_DIR and display found dependencies for all ELF files

    check-shlibs-deps -b OUTDIR -p PACKAGE
        Display found dependencies for PACKAGE

    check-shlibs-deps -b OUTDIR -p PACKAGE -d DEP1,DEP2,...
        Display missing dependencies for PACKAGE

Unfortunately, `packages-file-list.txt' is not properly filled when Top Level
Parallelization is enabled. Therefore, check-shlibs-deps does not (yet) work
with it.

Signed-off-by: Jérôme Pouiller <jezz at sysmic.org>
---
 support/scripts/check-shlibs-deps | 172 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 172 insertions(+)
 create mode 100755 support/scripts/check-shlibs-deps

diff --git a/support/scripts/check-shlibs-deps b/support/scripts/check-shlibs-deps
new file mode 100755
index 0000000..5ce024c
--- /dev/null
+++ b/support/scripts/check-shlibs-deps
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+# Copyright (C) 2016 by Jerome Pouiller <jerome.pouiller at sysmic.org>
+# Inspired from size-stats and check-host-rpath scripts
+
+import os
+import re
+import subprocess
+import argparse
+
+ignored_deps = [ "unknown", "glibc", "uclibc", "musl" ]
+
+# Add an entry in dictionnaries pkgsdict and filesdict
+def add_file(pkgsdict, filesdict, abspath, pkg):
+    if not os.path.exists(abspath):
+        return
+    #if abspath in filesdict and filesdict[abspath] != pkg:
+    #    print("WARNING: %s is owned by %s, but overwritten by %s" %
+    #       (abspath, filesdict[abspath], pkg))
+    filesdict[abspath] = pkg
+    if not pkg in pkgsdict:
+        pkgsdict[pkg] = set()
+    pkgsdict[pkg].add(abspath)
+
+# Build dictionnaries from "build/packages-file-list.txt"
+def build_dicts(builddir):
+    pkgsdict = {}
+    filesdict = {}
+    with open(os.path.join(builddir, "build", "packages-file-list.txt")) as filelistf:
+        for line in filelistf.readlines():
+            pkg, fpath = line.split(",", 1)
+            fpath = fpath.strip()
+            fpath = os.path.join(builddir, "target", fpath)
+            fpath = os.path.normpath(os.path.relpath(fpath))
+            add_file(pkgsdict, filesdict, fpath, pkg)
+    return filesdict, pkgsdict
+
+# Return package associated to a file
+def get_package(filesdict, fpath):
+    if not fpath in filesdict:
+        #print("WARNING: %s is not part of any package" % fpath)
+        # Do not flood user with warning messages. Especially host-gcc-final
+        # does not declare its files and produce many warnings.
+        filesdict[fpath] = "unknown"
+    return filesdict[fpath]
+
+# Return list of libraries linked with a binary
+def get_shared_libs(builddir, binary):
+    libs = set()
+    # Host readelf seems able to read foreign binaries (tested with arm/glibc and arm/uclibc)
+    pipe = subprocess.Popen([ "readelf", "-d", binary ], stdout=subprocess.PIPE)
+    for line in pipe.stdout:
+        match = re.match("^.* \(NEEDED\) .*Shared library: \[(.+)\]$", line)
+        if match:
+            libname = match.group(1)
+            # Support case where "/lib" s a symlink to "/usr/lib"
+            lpaths = set()
+            for dir in [ "usr/lib", "lib" ]:
+                lpaths.add(os.path.relpath(os.path.realpath(os.path.join(builddir, "target", dir, libname))))
+            found = 0
+            for file in lpaths:
+                if os.path.exists(file):
+                    found += 1
+                    libs.add(file)
+            #if found == 0:
+            #    # FIXME: Also take into account RPATH in order to find missed libraries
+            #    print("WARNING: %s depends on %s but it was not found on filesystem" % (binary, libname))
+            if found > 1:
+                print("BUG: %s depends on %s but it was found multiple time on filesystem" % (binary, libname))
+    return libs
+
+# Return a list a dependencies for a list of ELF files
+def build_package_deps(builddir, filesdict, bins):
+    pkgdeps = { }
+    for binary in bins:
+        shlibs = get_shared_libs(builddir, binary)
+        for sh in shlibs:
+            pkg = get_package(filesdict, binary)
+            if not pkg in pkgdeps:
+                pkgdeps[pkg] = set()
+            dep = get_package(filesdict, sh)
+            if not dep in ignored_deps and dep != pkg:
+                pkgdeps[pkg].add(dep)
+    return pkgdeps
+
+# Filter ELF files from a list of files
+def filter_elf(builddir, files):
+    bins = set()
+    pipe = subprocess.Popen([ "file" ] + list(files), stdout=subprocess.PIPE)
+    for line in pipe.stdout:
+        match = re.match("^([^:]+): +ELF ", line)
+        if match:
+            bins.add(match.group(1));
+    return bins
+
+# Return files found in "target/"
+def build_file_list(builddir):
+    files = set()
+    for dirpath, _, filelist in os.walk(os.path.join(builddir, "target")):
+        for f in filelist:
+            file = os.path.join(dirpath, f)
+            file = os.path.relpath(os.path.realpath(file))
+            if not os.path.islink(file):
+                files.add(file)
+    return files
+
+def main(builddir, package, deps):
+    filesdict, pkgsdict = build_dicts(builddir)
+    if package and not package in pkgsdict:
+        print("'%s' is an unkown package" % package)
+        exit(0)
+
+    if package:
+        file_list = pkgsdict[package]
+    else:
+        file_list = build_file_list(builddir)
+    # print("List of files to check:\n  %s" % "\n  ".join(sorted(file_list)))
+    bins = filter_elf(builddir, file_list)
+    # print("List of binaries to check:\n  %s" % "\n  ".join(sorted(bins)))
+    pkgdeps = build_package_deps(builddir, filesdict, sorted(bins))
+    error = 0
+    for p, pdeps in sorted(pkgdeps.items()):
+        if not deps == None:
+            for d in pdeps:
+                if not d in sorted(deps):
+                    print("%s: missed dependency to %s" % (p, d))
+                    error += 1
+        else:
+            print("%s: %s" % (p, " ".join(sorted(pdeps))))
+    return error
+
+
+parser = argparse.ArgumentParser(description='Show or check binary dependencies based on linked shared libraries')
+
+parser.add_argument("--builddir", '-b', metavar="BUILDDIR", required=True,
+        help="Buildroot output directory")
+parser.add_argument("--package", '-p', metavar="PACKAGE",
+        help="Check only PACKAGE (else, show dpendencies of all binairies)")
+parser.add_argument("--deps", '-d', metavar="DEP1,DEP2,...", nargs='?', default="",
+        help="Report errors if found dependencies are not a subset of '--deps'. '-p' is mandatory with this option")
+parser.add_argument('-w', action='store_true',
+        help="Do not return non zero when dependency is missing")
+args = parser.parse_args()
+if not args.package and args.deps:
+    print("ERROR: cannot use --deps wihout --package")
+    exit(1)
+
+if args.deps == "":
+    deps = None
+elif args.deps == None:
+    deps = []
+else:
+    deps = args.deps.split(",")
+
+ret = main(args.builddir, args.package, deps)
+if not args.w:
+    exit(ret)
+exit(0)
-- 
2.9.3




More information about the buildroot mailing list