ShlibVisibilityChecker is a small tool which locates internal symbols that are unnecessarily exported from shared libraries. Such symbols are undesirable because they cause
- slower startup time (due to slower relocation processing by dynamic linker, see a real-world example for Linux kernel)
- performance slowdown (due to indirect function calls, compiler's inability
to optimize exportable functions e.g. inline them, effective turnoff of
--gc-sections
) - leak of implementation details (if some clients start to use private functions instead of regular APIs)
- bugs due to runtime symbol clashing
- crash in Apache due to symbol clash with libasn1
- more real-world examples in Flameeyes blog
ShlibVisibilityChecker compares APIs declared in public headers
against APIs exported from shared libraries and warns about discrepancies.
In majority of cases such symbols are internal library symbols which should be hidden
(in rare cases these are internal symbols which are used by other libraries or executables
in the same package and shlibvischeck-debian
tries hard to not report such cases).
Such discrepancies should then be fixed by recompiling package
with -fvisibility=hidden
(see here for details).
A typical fix, for a typical Autoconf project can be found
here.
ShlibVisibilityChecker not meant to be 100% precise but rather provide assistance in locating packages which may benefit the most from visibility annotations (and to understand how bad the situation with visibility is in modern distros).
To check a raw package, i.e. a bunch of headers and shared libs, collect source and binary interfaces and compare them:
$ bin/read_header_api --only-args /usr/include/xcb/* > api.txt
$ ./read_binary_api --permissive /usr/lib/x86_64-linux-gnu/libxcb*.so > abi.txt
$ vimdiff api.txt abi.txt # Or `comm -13 api.txt abi.txt'
Another useful scenario is locating symbols that are exported from
Debian package's shared libraries but are not declared in it's headers.
The main tool for this is a shlibvischeck-debian
script.
To apply it to a package, run
$ shlibvischeck-debian libacl1
The following exported symbols in package 'libacl1' are private:
__acl_extended_file
__acl_from_xattr
__acl_to_xattr
__bss_start
_edata
_end
_fini
_init
closed
head
high_water_alloc
next_line
num_dir_handles
walk_tree
To skip autogenerated symbols like _init
or _edata
(caused by ld linker scripts and libgcc startup files) add --permissive
.
You can also check visibility issues in arbitrary set of headers and libraries:
$ shlibvischeck-common --permissive --cflags="-I/usr/include -I$AUDIT_INSTALL/include -I/usr/lib/llvm-5.0/lib/clang/5.0.0/include" $AUDIT_INSTALL/include/*.h $AUDIT_INSTALL/lib/*.so*
Build-time prerequisites are python3
(setuptools
module), clang
,
llvm
, libclang-dev
, g++
and make
.
Run-time dependencies are python3
(python-magic
module), pkg-config
and aptitude
.
To install everything on Ubuntu, run
$ sudo apt-get install python3 clang llvm libclang-dev g++ make pkg-config aptitude
$ sudo python3 -mensurepip
$ sudo pip3 install setuptools python-magic
(you could also use script scripts/install-deps.sh
).
You also need to enable access to Ubuntu source packages via
$ sudo sed -Ei 's/^# *deb-src /deb-src /' /etc/apt/sources.list
$ sudo apt-get update
Python and binary components are built separately:
$ make clean all && make install
$ ./setup.py build && pip3 install .
During analysis shlibvischeck-debian
installs new Debian packages so it's recommended to run it under chroot or in VM.
There are many instructions on setting up chroot e.g. this one.
A list of packages for analysis can be obtained from Debian rating:
$ curl https://popcon.debian.org/by_vote | awk '/^[0-9]+ +lib/{print $2}' > by_vote
$ shlibvischeck-debian $(head -500 by_vote | tr '\n' ' ')
Once you found a problematic package, you can fix it by restricting visibility of internal symbols. The best way to control symbol visibility in a package is to
- hide all symbols by default by adding
-fvisibility=hidden
toCFLAGS
in project buildscripts (Makefile.in
orCMakeLists.txt
) - explicitly annotate publicly visible functions with
__attribute__((visibility("default")))
See fix in libcaca for example.
At the moment tool works only on Debian-based systems (e.g. Ubuntu). This should be fine as buildscripts are the same across all distros so detecting issues on Ubuntu would serve everyone else too.
An important design issue is that the tool can not detect symbols which are used indirectly
i.e. not through an API but through dlsym
or explicit out-of-header prototype declaration
in source file. This happens in plugins or tightly interconnected shlibs within the same project.
Such cases should hopefully be rare.
ShlibVisibilityChecker is a heuristic tool so it will not be able to analyze all packages. Current success rate is around 60%. Major reasons for errors are
- badly-structured headers i.e. the ones which do not #include all their dependencies
(e.g.
libatasmart
fails to includestddef.h
andtdb
fails to includesys/types.h
). - internal headers which should not be #included directly (e.g.
lzma/container.h
) - experimental headers which require custom macro definitions (not listed in
pkgconfig) (e.g.
dpkg/macros.h
requiresLIBDPKG_VOLATILE_API
) - missing dependencies (e.g.
libverto-dev
uses Glib headers but does not declare this)
Other issues:
- TODOs are scattered all over the codebase
- would be interesting to go over dependent packages and check if they use invalid symbols
The tool found huge number of packages that lacked visibility annotations (in practice every second package has spurious exports). Here are some which I tried to fix:
- Bzip2: Hide unused symbols in libbz2
- Expat: Private symbols exported from shared library (fixed)
- Libaudit: Exported private symbols in audit-userspace (partially fixed)
- Gdbm: sr #347: Add visibility annotations to hide private symbols
- Libnfnetfilter: [RFC][PATCH] Hide private symbols in libnfnetlink (fixed)
- Libarchive: Hide private symbols in libarchive.so (fixed)
- Libcaca: Hide private symbols in libcaca (fixed)
- Libgmp: Building gmp with -fvisibility=hidden
- Vorbis: Remove private symbols from Vorbis shared libs
More perspective packages (from Debian top-100): libpopt1, libgpg-error0, libxml2, libwrap0, libpcre3, libkeyutils1, libedit2, liblcms2-2.