diff --git a/.gitignore b/.gitignore
index 5a9de428f2129b954e3a775c66a961f3b5887cac..1cd499a266527b9cf79d45409f483b8031b3129e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,9 +5,6 @@
 /build/
 /builddir/
 /helpers/_generated_*/
-/libcapsule/
-/libcapsule_*.dsc
-/libcapsule_*.tar.[gx]z
 /obj-*/
 /pressure-vessel-[0-9]*.tar.gz
 /relocatable-install/
diff --git a/debian/control b/debian/control
index 71b4a67a178133663d2b21094e4ea8b49126f7a6..d1040304be953a407b42c81b4934cfe2118607f1 100644
--- a/debian/control
+++ b/debian/control
@@ -9,7 +9,6 @@ Build-Depends:
  gcc (>= 4:4.8) | gcc-4.8,
  glslang-tools,
  gtk-doc-tools <!nodoc>,
- libcapsule-tools | libcapsule-tools-relocatable,
  libelf-dev,
  libgl1-mesa-dev | libgl-dev,
  libglib2.0-dev,
@@ -89,7 +88,6 @@ Section: misc
 Depends:
  ${misc:Depends},
  ${shlibs:Depends},
- libcapsule-tools-relocatable,
  waffle-utils-multiarch,
 Description:
  The Steam Runtime is the library stack used to run the Steam client
@@ -139,7 +137,6 @@ Architecture: amd64 i386
 Multi-Arch: foreign
 Depends:
  bubblewrap,
- libcapsule-tools-relocatable (>= 0.20201120.0),
  python3 (>= 3.5) | python3.5,
  steam-runtime-tools-bin,
  ${misc:Depends},
@@ -157,7 +154,6 @@ Section: libs
 Architecture: amd64
 Multi-Arch: foreign
 Depends:
- libcapsule-tools-relocatable (>= 0.20201120.0),
  libsteam-runtime-tools-0-helpers,
  waffle-utils-multiarch,
  ${misc:Depends},
@@ -172,7 +168,6 @@ Section: libs
 Architecture: i386
 Multi-Arch: foreign
 Depends:
- libcapsule-tools-relocatable (>= 0.20201120.0),
  libsteam-runtime-tools-0-helpers,
  waffle-utils-multiarch,
  ${misc:Depends},
diff --git a/debian/copyright b/debian/copyright
index 9de68c824762a3cf30ffdd74dfd191366fcf0390..ba2b08c22821d26b9e9ffb85edd91a76eb8d33ea 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -12,6 +12,7 @@ Copyright:
  1995-1997 Josh MacDonald
  1997-2000 GLib team
  1997-2020 Sam Lantinga
+ 1999-2017 Free Software Foundation, Inc.
  2000-2021 Red Hat, Inc
  2010-2011 Lennart Poettering
  2012-2017 Colin Walters
@@ -34,12 +35,28 @@ Copyright:
  2019-2021 Collabora Ltd.
 License: Apache-2.0
 
+Files:
+ subprojects/libcapsule/tests/CapsuleTestDpkg.pm
+Copyright:
+ © 2007 Raphaël Hertzog
+ © 2009-2010 Modestas Vainius
+License: GPL-2+
+
 Files:
  build-aux/git-version-gen
+ subprojects/libcapsule/build-aux/git-version-gen
 Copyright:
  2007-2018 Free Software Foundation, Inc.
 License: GPL-3+
 
+Files:
+ subprojects/libcapsule/m4/ax_is_release.m4
+ subprojects/libcapsule/m4/capsule_matching_abi.m4
+Copyright:
+ © 2015 Philip Withnall
+ © 2016-2019 Collabora Ltd.
+License: Autoconf-permissive
+
 License: Apache-2.0
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
@@ -55,6 +72,11 @@ License: Apache-2.0
 Comment:
  SPDX-License-Identifier: Apache-2.0
 
+License: Autoconf-permissive
+ Copying and distribution of this file, with or without modification, are
+ permitted in any medium without royalty provided the copyright notice
+ and this notice are preserved.
+
 License: Expat
  Permission is hereby granted, free of charge, to any person obtaining
  a copy of this software and associated documentation files (the
@@ -113,6 +135,23 @@ License: LGPL-2+
 Comment:
  On Debian systems see /usr/share/common-licenses/LGPL-2
 
+License: GPL-2+
+ 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, see <https://www.gnu.org/licenses/>.
+Comment:
+ On Debian systems, the full text of the GNU General Public License
+ version 2 can be found in the file '/usr/share/common-licenses/GPL-2'.
+
 License: GPL-3+
  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
diff --git a/debian/gitlab-ci.yml b/debian/gitlab-ci.yml
index f835edd43064af79d13f9a9908045eac72ab5afd..478e1fea8e0e09265a7091c866ba731c65ca1485 100644
--- a/debian/gitlab-ci.yml
+++ b/debian/gitlab-ci.yml
@@ -61,7 +61,7 @@ package:
     variables:
         STEAM_CI_DEB_BUILD: full    # debuild --build=full, aka -F
         STEAM_CI_INSTALL_SCRIPT: |
-            apt-get -y install libcapsule-tools pkg-create-dbgsym
+            apt-get -y install pkg-create-dbgsym
 
 package:i386:
     extends: .build_package
@@ -69,7 +69,7 @@ package:i386:
         BUILD_IMAGE: registry.gitlab.steamos.cloud/steamrt/scout/sdk/i386:beta
         STEAM_CI_DEB_BUILD: any     # debuild --build=any, aka -B
         STEAM_CI_INSTALL_SCRIPT: |
-            apt-get -y install libcapsule-tools pkg-create-dbgsym
+            apt-get -y install pkg-create-dbgsym
 
 build:devel:
     stage: build
@@ -142,10 +142,6 @@ build:devel:
         # be able to get its source code
         apt-get -y dist-upgrade
 
-        apt-get -y --no-install-recommends install \
-        libcapsule-tools \
-        ${NULL+}
-
         # g++ 4.6 is too old (see also debian/rules)
         export CC=gcc-4.8
         export CXX=g++-4.8
@@ -217,10 +213,6 @@ relocatable-install:
         - |
             apt-get -y --no-install-recommends install \
             bubblewrap \
-            libcapsule0 \
-            libcapsule-tools \
-            libcapsule-tools-relocatable:amd64 \
-            libcapsule-tools-relocatable:i386 \
             libglib2.0-dev \
             libxau-dev \
             meson \
diff --git a/debian/rules b/debian/rules
index c716248f1099b4e2699e5cc7982c9a129917861f..161a276c9ada4577cfad667f60c65be7152d3dd8 100755
--- a/debian/rules
+++ b/debian/rules
@@ -83,8 +83,6 @@ override_dh_auto_install:
 		$(DESTDIR)/usr/$(pkglibexecdir)/
 	ln -s ../../bin/$(DEB_HOST_MULTIARCH)-wflinfo \
 		$(DESTDIR)/usr/$(pkglibexecdir)/
-	ln -s ../../lib/libcapsule/relocatable/$(DEB_HOST_MULTIARCH)-capsule-capture-libs \
-		$(DESTDIR)/usr/$(pkglibexecdir)/
 	set -e; for x in $(DESTDIR)/usr/libexec/installed-tests/steam-runtime-tools-0/*; do \
 		if [ -f "$$x" ] \
 		&& cmp -s "$$x" \
@@ -110,9 +108,8 @@ override_dh_link:
 	:
 	install -d $(relocatable_pkglibdir)
 	ln -fnsv ../libsteam-runtime-tools-0.so.0 $(relocatable_pkglibdir)
-	export PATH=/usr/lib/libcapsule/relocatable:$$PATH; \
 	LD_LIBRARY_PATH='$(DESTDIR)/usr/$(libdir)' \
-	$(DEB_HOST_MULTIARCH)-capsule-capture-libs \
+	$(DESTDIR)/usr/$(pkglibexecdir)/$(DEB_HOST_MULTIARCH)-capsule-capture-libs \
 		--container / \
 		--dest $(relocatable_pkglibdir) \
 		--link-target / \
@@ -120,6 +117,7 @@ override_dh_link:
 		--provider / \
 		only-dependencies:path:$(DESTDIR)/usr/bin/steam-runtime-check-requirements \
 		only-dependencies:path:$(DESTDIR)/usr/bin/steam-runtime-system-info \
+		only-dependencies:path:$(DESTDIR)/usr/$(pkglibexecdir)/$(DEB_HOST_MULTIARCH)-capsule-capture-libs \
 		only-dependencies:path:$(DESTDIR)/usr/$(pkglibexecdir)/$(DEB_HOST_MULTIARCH)-check-locale \
 		$(NULL)
 	:
diff --git a/debian/tests/relocatable-install b/debian/tests/relocatable-install
index 4adf0eb6143c86f1c07cabd370953d78b2fbe967..5e41d9bba19fa4b1d76410a6a7f45d3d90410a52 100755
--- a/debian/tests/relocatable-install
+++ b/debian/tests/relocatable-install
@@ -6,8 +6,6 @@ dpkg --add-architecture amd64
 dpkg --add-architecture i386
 apt-get -y update
 apt-get -y install \
-    libcapsule-tools-relocatable:amd64 \
-    libcapsule-tools-relocatable:i386 \
     libsteam-runtime-tools-0-0:amd64 \
     libsteam-runtime-tools-0-0:i386 \
     libsteam-runtime-tools-0-helpers:amd64 \
diff --git a/helpers/capture-libs.c b/helpers/capture-libs.c
new file mode 100644
index 0000000000000000000000000000000000000000..c3f2a5f9d5eaadd2816c320ea6355cbd99acde58
--- /dev/null
+++ b/helpers/capture-libs.c
@@ -0,0 +1,34 @@
+// Copyright © 2021 Collabora Ltd
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#ifdef __GNUC__
+// ELF code taken from glibc does a lot of pointer arithmetic assuming
+// that (void * + integer) behaves like (char * + integer), which it does
+// in gcc/clang
+#pragma GCC diagnostic ignored "-Wpointer-arith"
+// For fallback implementation of static_assert() */
+#pragma GCC diagnostic ignored "-Wredundant-decls"
+#elif defined(__clang__)
+#pragma clang diagnostic ignored "-Wpointer-arith"
+#pragma clang diagnostic ignored "-Wredundant-decls"
+#endif
+
+#include "subprojects/libcapsule/utils/ld-cache.c"
+#include "subprojects/libcapsule/utils/ld-libs.c"
+#include "subprojects/libcapsule/utils/library-cmp.c"
+#include "subprojects/libcapsule/utils/tools.c"
+#include "subprojects/libcapsule/utils/utils.c"
+
+#include "subprojects/libcapsule/utils/capture-libs.c"
diff --git a/helpers/meson.build b/helpers/meson.build
index 16f43f1f4407b6a58233332fc15459b2706b06d3..645615ca43cff8a211eb78c2c8afc14681d80e1a 100644
--- a/helpers/meson.build
+++ b/helpers/meson.build
@@ -215,4 +215,33 @@ elif multiarch == 'x86_64-linux-gnu'
   subdir('xeon_phi')
 endif
 
+if multiarch == 'i386-linux-gnu' or multiarch == 'x86_64-linux-gnu'
+  if meson.version().version_compare('>=0.58.0')
+    # replaces deprecated source_root()
+    libcapsule_srcdir = meson.global_source_root() / 'subprojects' / 'libcapsule'
+  else
+    libcapsule_srcdir = meson.source_root() / 'subprojects' / 'libcapsule'
+  endif
+
+  # libcapsule doesn't currently have a Meson build system, so instead
+  # of building it as a subproject, we build our own source file that
+  # #includes everything we need from it.
+  executable(
+    multiarch + '-capsule-capture-libs',
+    'capture-libs.c',
+    c_args : [
+      '-DHOST_PREFIX="' + multiarch + '-"',
+      '-DPACKAGE_TARNAME="' + meson.project_name() + '"',
+      '-DPACKAGE_VERSION="' + meson.project_version() + '"',
+    ],
+    dependencies: [libdl, libelf],
+    include_directories : project_include_dirs,
+    install : true,
+    install_dir : pkglibexecdir,
+    # Use libelf from the adjacent libdir, ignoring LD_LIBRARY_PATH
+    build_rpath : pkglibexec_rpath,
+    install_rpath : pkglibexec_rpath,
+  )
+endif
+
 # vim:set sw=2 sts=2 et:
diff --git a/pressure-vessel/README.md b/pressure-vessel/README.md
index fae7e54f7c8ad506961cdb8815505a232cf359c7..3475fc00d82111fc1cbd569036ac4f01a0e6ec88 100644
--- a/pressure-vessel/README.md
+++ b/pressure-vessel/README.md
@@ -115,49 +115,11 @@ that is likely to depend on libraries from your host system, which is
 good for quick turnaround and debugging, but not suitable for deployment
 to the public. For that, you'll want a relocatable installation: see below.
 
-If you are developing pressure-vessel, you might well want to alter
-the bundled libcapsule tools. You can do this by building your own
-copy of libcapsule. This requires 32- and 64-bit copies of libelf (the
-version from elfutils) and its development files.
-
-    git clone https://gitlab.collabora.com/vivek/libcapsule.git
-    cd libcapsule
-    prefix="${XDG_DATA_HOME:-"$HOME/.local/share"}/Steam/steamapps/common/SteamLinuxRuntime/pressure-vessel"
-    libdir="lib/x86_64-linux-gnu"
-    NOCONFIGURE=1 ./autogen.sh
-    mkdir _build _build/i386 _build/x86_64
-    ( cd _build/x86_64; ../../configure
-    --host=x86_64-linux-gnu --enable-host-prefix=x86_64-linux-gnu- \
-    --prefix="$prefix" --libdir="\${prefix}/$libdir" \
-    --disable-gtk-doc --disable-shared --without-glib )
-    ( cd _build/i386; ../../configure
-    --host=i686-linux-gnu --enable-host-prefix=i386-linux-gnu- \
-    --prefix="$prefix" --libdir="\${prefix}/$libdir" \
-    --disable-gtk-doc --disable-shared --without-glib )
-    make -C _build/x86_64
-    make -C _build/i386
-    make -C _build/x86_64 install
-    make -C _build/i386 install
-
-Use `--host=i586-linux-gnu` instead of `--host=i686-linux-gnu` if your
-distribution uses that tuple for its 32-bit compiler. For the i386 build,
-if you don't have an `i?86-linux-gnu-gcc` you might have to add
-`CC="gcc -m32"` to the i386 configure command line.
-
-Again, this will give you a non-production version of libcapsule that is
-likely to depend on libraries from your host system.
-
 Building a relocatable install for deployment
 ---------------------------------------------
 
 To make the built version compatible with older systems, you will need
-a SteamRT 1 'scout' environment.
-
-For this, you need prebuilt versions of libcapsule-tools-relocatable:amd64
-and libcapsule-tools-relocatable:i386 either in your build environment
-or available via apt-get. For example, you could do the build in a
-SteamRT 1 'scout' SDK Docker image that has those packages already.
-Then you can do:
+a SteamRT 1 'scout' environment. Then you can do:
 
     meson --prefix="$(pwd)/_build/prefix" -Dsrcdir=src _build
     ninja -C _build
diff --git a/pressure-vessel/THIRD-PARTY.md b/pressure-vessel/THIRD-PARTY.md
index 7170b9592c08683e557e3ac86323b357b78f44d8..54d79303c9c36432ee5c6eff2d09909143662b83 100644
--- a/pressure-vessel/THIRD-PARTY.md
+++ b/pressure-vessel/THIRD-PARTY.md
@@ -2,10 +2,9 @@ Third-party modules
 ===================
 
 pressure-vessel includes the capsule-capture-libs utility from libcapsule,
-which is licensed under [LGPL-2.1-or-later][]. See
-libcapsule.txt. Complete source code can be obtained from the
-same place as pressure-vessel, or from
-<https://gitlab.collabora.com/vivek/libcapsule/>.
+which is licensed under [LGPL-2.1-or-later][]. Complete source code can
+be obtained from the same place as pressure-vessel, or from
+<https://gitlab.steamos.cloud/steamrt/steam-runtime-tools/>.
 
 pressure-vessel incorporates code from GNOME libglnx, variously licensed
 under [LGPL-2.0-or-later][] and [LGPL-2.1-or-later][].
diff --git a/pressure-vessel/build-relocatable-install.py b/pressure-vessel/build-relocatable-install.py
index 2ea606352da4f28f07088ec4d21305ac92720be4..aa19dcdbc56b634129fdb7f887bfbef5a9099f8c 100755
--- a/pressure-vessel/build-relocatable-install.py
+++ b/pressure-vessel/build-relocatable-install.py
@@ -77,7 +77,6 @@ ARCHS = [
 ]
 # package to install from => source package for copyright information
 DEPENDENCIES = {
-    'libcapsule-tools-relocatable': 'libcapsule',
     'libelf1': 'elfutils',
     'waffle-utils-multiarch': 'waffle',
     'zlib1g': 'zlib',
@@ -114,10 +113,6 @@ EXECUTABLES = [
     'pressure-vessel-wrap',
     'steam-runtime-system-info',
 ]
-LIBCAPSULE_TOOLS = [
-    'capsule-capture-libs',
-    'capsule-symbols',
-]
 
 
 def install(src, dst, mode=0o644):
@@ -295,38 +290,13 @@ def main():
             0o644,
         )
 
-        for arch in ARCHS:
-            path = '/usr/lib/libcapsule/relocatable/{}-{}'.format(
-                arch.multiarch,
-                LIBCAPSULE_TOOLS[0],
-            )
-
-            if not os.path.exists(path):
-                package = 'libcapsule-tools-relocatable'
-                v_check_call([
-                    'apt-get',
-                    'download',
-                    package + ':' + arch.name,
-                ], cwd=tmpdir)
-                v_check_call(
-                    'dpkg-deb -X {}_*_{}.deb build-relocatable'.format(
-                        quote(package),
-                        quote(arch.name),
-                    ),
-                    cwd=tmpdir,
-                    shell=True,
-                )
-                path = '{}/build-relocatable/{}'.format(tmpdir, path)
-
-            for tool in LIBCAPSULE_TOOLS:
-                install_exe(
-                    os.path.join(
-                        os.path.dirname(path),
-                        '{}-{}'.format(arch.multiarch, tool),
-                    ),
-                    os.path.join(installation, 'bin'),
-                )
+        inst_pkglibexecdir = os.path.join(
+            installation,
+            'libexec',
+            'steam-runtime-tools-0',
+        )
 
+        for arch in ARCHS:
             path = os.path.join(
                 args.prefix, 'libexec', 'steam-runtime-tools-0',
             )
@@ -354,29 +324,21 @@ def main():
             for tool in glob.glob(os.path.join(path, arch.multiarch + '-*')):
                 install_exe(
                     tool,
-                    os.path.join(
-                        installation,
-                        'libexec',
-                        'steam-runtime-tools-0',
-                        os.path.basename(tool),
-                    ),
+                    os.path.join(inst_pkglibexecdir, os.path.basename(tool)),
                 )
 
             for shader in glob.glob(os.path.join(path, 'shaders', '*.spv')):
                 install(
                     shader,
                     os.path.join(
-                        installation, 'libexec', 'steam-runtime-tools-0',
+                        inst_pkglibexecdir,
                         'shaders', os.path.basename(shader),
                     )
                 )
 
             shutil.copytree(
                 os.path.join(path, arch.multiarch),
-                os.path.join(
-                    installation, 'libexec', 'steam-runtime-tools-0',
-                    arch.multiarch,
-                ),
+                os.path.join(inst_pkglibexecdir, arch.multiarch),
             )
 
         primary_architecture = subprocess.check_output([
@@ -390,8 +352,8 @@ def main():
             )
 
             v_check_call([
-                '{}/bin/{}-capsule-capture-libs'.format(
-                    installation,
+                '{}/{}-capsule-capture-libs'.format(
+                    inst_pkglibexecdir,
                     arch.multiarch,
                 ),
                 '--dest={}/build-relocatable/{}/lib'.format(
@@ -415,8 +377,8 @@ def main():
                     libsrt_pattern = 'soname:libsteam-runtime-tools-0.so.0'
 
                 v_check_call([
-                    '{}/bin/{}-capsule-capture-libs'.format(
-                        installation,
+                    '{}/{}-capsule-capture-libs'.format(
+                        inst_pkglibexecdir,
                         arch.multiarch,
                     ),
                     '--dest={}/build-relocatable/{}/lib'.format(
@@ -484,12 +446,7 @@ def main():
 
                 install_exe(
                     path,
-                    os.path.join(
-                        installation,
-                        'libexec',
-                        'steam-runtime-tools-0',
-                        exe,
-                    )
+                    os.path.join(inst_pkglibexecdir, exe)
                 )
 
         # For bwrap (and possibly other programs in future) we don't have
diff --git a/pressure-vessel/runtime.c b/pressure-vessel/runtime.c
index ccec4d2f0b2929e88a68bb07005c827cbd4b8b39..c2949184710ae59224699cd42ef40ef11a94a051 100644
--- a/pressure-vessel/runtime.c
+++ b/pressure-vessel/runtime.c
@@ -330,7 +330,7 @@ runtime_architecture_init (RuntimeArchitecture *self,
 
   self->capsule_capture_libs_basename = g_strdup_printf ("%s-capsule-capture-libs",
                                                          self->details->tuple);
-  self->capsule_capture_libs = g_build_filename (runtime->pv_prefix, "bin",
+  self->capsule_capture_libs = g_build_filename (runtime->helpers_path,
                                                  self->capsule_capture_libs_basename,
                                                  NULL);
   self->libdir_in_current_namespace = g_build_filename (runtime->overrides, "lib",
diff --git a/subprojects/libcapsule/.gitignore b/subprojects/libcapsule/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..9a9dad7d0c94acd774a578830e83a784e3c306f2
--- /dev/null
+++ b/subprojects/libcapsule/.gitignore
@@ -0,0 +1,68 @@
+.deps
+/.version
+/INSTALL
+/build-aux/*
+/data/*-linux-gnu*-capsule-mkstublib
+/m4/*
+/test-suite.log
+/tests/*.log
+/tests/*.t
+/tests/*.test
+/tests/*.trs
+/tests/notgl-dlopener
+/tests/notgl-helper-user
+/tests/notgl-user
+/tests/shim/libnotgl.so.0.c
+/tests/shim/libnotgles.so.1.c
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.status
+config.log
+configure
+libtool
+update
+Makefile
+.*stamp
+.libs
+*.la
+*.lo
+*.o
+shim/*.so.c
+shim/*.so.symbols
+shim/*.so.map
+/capsule-capture-libs
+/capsule-elf-dump
+print-libstubs
+capsule-symbols
+capsule-version
+TAGS
+tags
+*~
+data/*.pc
+data/dirconf.txt
+*.swp
+*.tmp
+install.log
+*.1
+*.3
+*-docs.xml
+*.types
+gtk-doc.make
+*.tar.*
+*.tgz
+*.tar
+tmpl/
+*.stamp
+html/
+xml/
+libcapsule-*.txt
+*.args
+*.hierarchy
+*.interfaces
+*.prerequisites
+*.signals
+
+!/build-aux/git-version-gen
+!/m4/ax_is_release.m4
+!/m4/capsule_*.m4
diff --git a/subprojects/libcapsule/AUTHORS b/subprojects/libcapsule/AUTHORS
new file mode 100644
index 0000000000000000000000000000000000000000..be5806f634a07d413e5de15db2dbc172451545bf
--- /dev/null
+++ b/subprojects/libcapsule/AUTHORS
@@ -0,0 +1 @@
+Vivek Das Mohapatra <vivek@collabora.com>; <vivek@etla.org>
diff --git a/subprojects/libcapsule/COPYING b/subprojects/libcapsule/COPYING
new file mode 100644
index 0000000000000000000000000000000000000000..4362b49151d7b34ef83b3067a8f9c9f877d72a0e
--- /dev/null
+++ b/subprojects/libcapsule/COPYING
@@ -0,0 +1,502 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                  GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                            NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/subprojects/libcapsule/Makefile.am b/subprojects/libcapsule/Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..736f26923bf4c48a1f9b85389a6d3fa1cea38673
--- /dev/null
+++ b/subprojects/libcapsule/Makefile.am
@@ -0,0 +1,591 @@
+# Copyright © 2017 Collabora Ltd
+
+# This file is part of libcapsule.
+
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+
+# libcapsule 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 Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+AUTOMAKE_OPTIONS        = subdir-objects
+ACLOCAL_AMFLAGS         = -I m4 --install
+AM_CPPFLAGS             = -DHOST_PREFIX=\"$(HOST_PREFIX)\"
+common_cflags           = -fvisibility=hidden \
+                          --std=c99 \
+                          $(WARN_CFLAGS) \
+                          $(LIBELF_CFLAGS) \
+                          $(GLIB_CFLAGS)
+# Used for production code and tests, but not for test fixtures
+AM_CFLAGS               = $(common_cflags) $(SANITIZE_CFLAGS)
+# Used for test fixtures only
+test_fixture_cflags     = $(common_cflags)
+AM_LDFLAGS              = $(WARN_LDFLAGS)
+static_docs             = Building Debugging Limitations Quick-Start Capsules
+
+# installable items:
+bin_PROGRAMS            = capsule-capture-libs capsule-symbols capsule-version
+if ENABLE_LIBRARY
+dist_bin_SCRIPTS        = data/capsule-init-project
+lib_LTLIBRARIES         = libcapsule.la
+pkgconfigdir            = $(libdir)/pkgconfig
+pkgconfig_DATA          = data/libcapsule.pc data/libcapsule-tools.pc
+nobase_include_HEADERS  = capsule/capsule.h
+dist_doc_DATA           = $(foreach doc,$(static_docs),doc/$(doc).txt)
+endif
+
+if ENABLE_TOOLS_RPATH
+# Use -Wl to hide it from libtool
+tools_ldflags           = -Wl,-rpath,$(enable_tools_rpath)
+endif
+
+if ENABLE_HOST_PREFIX
+if ENABLE_LIBRARY
+bin_SCRIPTS             = data/$(HOST_PREFIX)capsule-mkstublib
+
+# Write out a version of capsule-mkstublib with different defaults
+# for each architecture
+data/$(HOST_PREFIX)capsule-mkstublib: data/capsule-mkstublib Makefile
+	$(AM_V_GEN)set -e; \
+	( \
+		sed -e 1q $<; \
+		echo '# Inserted by Makefile to make this architecture-specific'; \
+		echo ': "$${CAPSULE_SYMBOLS_TOOL:="$(bindir)/$(HOST_PREFIX)capsule-symbols"}"';\
+		sed -e 1d $<; \
+	) > $@.tmp; chmod +x $@.tmp; mv $@.tmp $@
+endif
+
+install-exec-hook: install-exec-hook-host-prefix
+install-exec-hook-host-prefix:
+	cd $(DESTDIR)$(bindir) && ln -f capsule-symbols $(HOST_PREFIX)capsule-symbols
+	cd $(DESTDIR)$(bindir) && ln -f capsule-version $(HOST_PREFIX)capsule-version
+	cd $(DESTDIR)$(bindir) && ln -f capsule-elf-dump $(HOST_PREFIX)capsule-elf-dump
+	cd $(DESTDIR)$(bindir) && ln -f capsule-capture-libs $(HOST_PREFIX)capsule-capture-libs
+
+uninstall-hook: uninstall-hook-host-prefix
+uninstall-hook-host-prefix:
+	cd $(DESTDIR)$(bindir) && rm -f $(HOST_PREFIX)capsule-capture-libs
+	cd $(DESTDIR)$(bindir) && rm -f $(HOST_PREFIX)capsule-elf-dump
+	cd $(DESTDIR)$(bindir) && rm -f $(HOST_PREFIX)capsule-symbols
+	cd $(DESTDIR)$(bindir) && rm -f $(HOST_PREFIX)capsule-version
+else
+if ENABLE_LIBRARY
+dist_bin_SCRIPTS            += data/capsule-mkstublib
+endif
+endif
+
+if ENABLE_LIBRARY
+# Headers to be included by shim libraries
+nobase_include_HEADERS += capsule/capsule-shim.h \
+                          capsule/_int_dlopen.h
+endif
+
+noinst_LTLIBRARIES = utils/libutils.la \
+                     utils/libld.la
+
+utils_libutils_la_SOURCES  = utils/utils.c utils/utils.h
+utils_libld_la_SOURCES     = utils/ld-cache.c utils/ld-cache.h
+utils_libld_la_SOURCES    += utils/ld-libs.c utils/ld-libs.h
+utils_libld_la_LIBADD      = utils/libutils.la -ldl $(LIBELF_LIBS)
+
+# Mostly just a demo program, but keep this hanging around as it's a useful PoC
+bin_PROGRAMS           += capsule-elf-dump
+
+capsule_elf_dump_SOURCES        = utils/elf-dump.c utils/dump.c
+capsule_elf_dump_SOURCES       += utils/tools.c utils/tools.h
+capsule_elf_dump_LDADD          = utils/libutils.la -ldl
+# This looks redundant, but it forces Automake to build separate
+# instantiations of dump.o for elf-dump and libcapsule
+capsule_elf_dump_CFLAGS         = $(AM_CFLAGS)
+capsule_elf_dump_LDFLAGS        = $(AM_LDFLAGS) $(tools_ldflags)
+
+capsule_capture_libs_SOURCES = utils/capture-libs.c \
+                               utils/library-cmp.c  \
+                               utils/library-cmp.h  \
+                               utils/tools.c        \
+                               utils/tools.h
+capsule_capture_libs_LDADD   = utils/libld.la
+capsule_capture_libs_LDFLAGS = $(AM_LDFLAGS) $(tools_ldflags)
+
+# now the rules for building our installables:
+capsule_symbols_SOURCES = utils/print-libstubs.c utils/tools.c utils/tools.h
+capsule_symbols_LDADD   = utils/libld.la
+capsule_symbols_LDFLAGS = $(AM_LDFLAGS) $(tools_ldflags)
+
+capsule_version_SOURCES = utils/print-version.c utils/tools.c utils/tools.h
+capsule_version_LDADD   = utils/libld.la
+capsule_version_LDFLAGS = $(AM_LDFLAGS) $(tools_ldflags)
+
+if ENABLE_LIBRARY
+libcapsule_la_SOURCES   = capsule/capsule-dlmopen.c  \
+                          capsule/capsule-relocate.c \
+                          capsule/capsule-init.c     \
+                          capsule/capsule-private.h  \
+                          capsule/capsule-malloc.h   \
+                          capsule/capsule-wrappers.c \
+                          utils/dump.c               \
+                          utils/process-pt-dynamic.c \
+                          utils/mmap-info.c
+libcapsule_la_LDFLAGS   = -version-number 0:1 -Wl,-z,nodelete
+libcapsule_la_LIBADD    = utils/libld.la
+libcapsule_la_SOURCES  += utils/debug.h              \
+                          utils/dump.h               \
+                          utils/process-pt-dynamic.h \
+                          utils/mmap-info.h
+
+dist_pkgdata_DATA       = data/capsule-shim.mk    \
+                          data/disabled.mk        \
+                          data/shim-.gitignore    \
+                          data/shim-Makefile.am   \
+                          data/shim-README.in     \
+                          data/shim-configure.ac.in \
+                          data/shim-target-Makefile.am
+endif
+
+BUILT_SOURCES           = $(top_srcdir)/.version
+
+$(top_srcdir)/.version:
+	$(AM_V_GEN)echo $(VERSION) > $@-t && mv $@-t $@
+
+include $(top_srcdir)/data/disabled.mk
+
+# ============================================================================
+# GNOME-style installed-tests
+
+insttestsdir                         = $(libexecdir)/installed-tests/$(PACKAGE_TARNAME)
+nobase_insttests_LTLIBRARIES         =
+
+if ENABLE_LIBRARY
+# /!\ Order is important here: the helper library must come first
+nobase_insttests_LTLIBRARIES        += tests/red/libhelper.la                  \
+                                       tests/green/libhelper.la                \
+                                       tests/helper/libhelper.la               \
+                                       tests/red/libnotgl.la                   \
+                                       tests/red/libnotgles.la                 \
+                                       tests/green/libnotgl.la                 \
+                                       tests/green/libnotgles.la               \
+                                       tests/lib/libnotgl.la                   \
+                                       tests/lib/libnotgles.la                 \
+                                       tests/shim/libnotgl.la                  \
+                                       tests/shim/libnotgles.la
+nobase_dist_insttests_DATA           = tests/CapsuleTest.pm                    \
+                                       tests/CapsuleTestDpkg.pm                \
+                                       tests/notgl.h                           \
+                                       tests/notgl-green.c                     \
+                                       tests/notgl-red.c                       \
+                                       tests/notgl-ref.c                       \
+                                       tests/notgl-helper.h                    \
+                                       tests/notgl-helper-green.c              \
+                                       tests/notgl-helper-red.c                \
+                                       tests/notgl-helper-ref.c                \
+                                       tests/notgles.h                         \
+                                       tests/notgles-green.c                   \
+                                       tests/notgles-red.c                     \
+                                       tests/notgles-ref.c                     \
+                                       tests/shim/libnotgl.so.0.excluded       \
+                                       tests/shim/libnotgl.so.0.shared         \
+                                       tests/shim/libnotgl.so.0.symbols        \
+                                       tests/shim/libnotgles.so.1.excluded     \
+                                       tests/shim/libnotgles.so.1.shared       \
+                                       tests/shim/libnotgles.so.1.symbols
+# Install the source code so we can rebuild it inside a container if we want to
+gl_shims =                             libEGL.so.1                             \
+                                       libGL.so.1                              \
+                                       libGLESv2.so.2                          \
+                                       libGLX.so.0                             \
+                                       libOpenGL.so.0                          \
+                                       libX11.so.6                             \
+                                       libXext.so.6                            \
+                                       libXi.so.6                              \
+                                       libgbm.so.1                             \
+                                       libxcb-dri2.so.0                        \
+                                       libxcb-glx.so.0                         \
+                                       libxcb-present.so.0                     \
+                                       libxcb-sync.so.1                        \
+                                       libxcb.so.1
+# We don't do this via nobase_dist_insttests_DATA because patsubst doesn't
+# work for make dist, so we would have to write it out longhand
+install-data-local-shim-sources:
+	$(AM_V_GEN)set -e; \
+	$(mkinstalldirs) $(DESTDIR)$(insttestsdir)/examples/shim; \
+	for lib in ${gl_shims}; do \
+		$(INSTALL_DATA) \
+			$(srcdir)/examples/shim/$$lib.excluded \
+			$(srcdir)/examples/shim/$$lib.shared \
+			$(srcdir)/examples/shim/$$lib.symbols \
+			$(srcdir)/examples/shim/$$lib.symbols.updated-for \
+			$(DESTDIR)$(insttestsdir)/examples/shim; \
+	done
+uninstall-hook: uninstall-hook-shim-sources
+uninstall-hook-shim-sources:
+	$(AM_V_GEN)set -e; \
+	for lib in ${gl_shims}; do \
+		rm -fr \
+			$(DESTDIR)$(insttestsdir)/examples/shim/$$lib.excluded \
+			$(DESTDIR)$(insttestsdir)/examples/shim/$$lib.shared \
+			$(DESTDIR)$(insttestsdir)/examples/shim/$$lib.symbols \
+			$(DESTDIR)$(insttestsdir)/examples/shim/$$lib.symbols.updated-for \
+		; \
+	done
+dist-hook-shim-sources:
+	$(AM_V_GEN)set -e; \
+	$(MKDIR_P) $(distdir)/examples/shim; \
+	for lib in ${gl_shims}; do \
+		cp \
+			$(srcdir)/examples/shim/$$lib.excluded \
+			$(srcdir)/examples/shim/$$lib.shared \
+			$(srcdir)/examples/shim/$$lib.symbols \
+			$(srcdir)/examples/shim/$$lib.symbols.updated-for \
+			$(distdir)/examples/shim; \
+	done
+endif
+
+maintainer-update-gl-shims:
+	@if test -z "${GL_SHIMS_SOURCE_MACHINE}"; then \
+		echo "usage: make -C $(CURDIR) maintainer-update-gl-shims GL_SHIMS_SOURCE_MACHINE=some-machine">&2; \
+		exit 2; \
+	fi
+	set -e; for lib in ${gl_shims}; do \
+		ssh "${GL_SHIMS_SOURCE_MACHINE}" \
+		x86_64-linux-gnu-capsule-symbols "$$lib" \
+		> $(abs_top_srcdir)/examples/shim/"$$lib.symbols.tmp"; \
+		LC_ALL=C sort -u \
+		< $(abs_top_srcdir)/examples/shim/"$$lib.symbols.tmp" \
+		> $(abs_top_srcdir)/examples/shim/"$$lib.symbols"; \
+		rm -f $(abs_top_srcdir)/examples/shim/"$$lib.symbols.tmp"; \
+		: > $(abs_top_srcdir)/examples/shim/"$$lib.excluded"; \
+		: > $(abs_top_srcdir)/examples/shim/"$$lib.shared"; \
+		: > $(abs_top_srcdir)/examples/shim/"$$lib.symbols.updated-for"; \
+	done
+
+if ENABLE_LIBRARY
+tests_lib_libnotgl_la_SOURCES        = tests/notgl-ref.c                       \
+                                       tests/notgl.h                           \
+                                       tests/notgl-helper.h
+tests_lib_libnotgl_la_CFLAGS         = $(test_fixture_cflags)
+tests_lib_libnotgl_la_LDFLAGS        = -shared -version-number 0:42:23
+tests_lib_libnotgl_la_LIBADD         = tests/helper/libhelper.la
+
+tests_lib_libnotgles_la_SOURCES      = tests/notgles-ref.c                     \
+                                       tests/notgles.h                         \
+                                       tests/notgl-helper.h
+tests_lib_libnotgles_la_CFLAGS       = $(test_fixture_cflags)
+tests_lib_libnotgles_la_LDFLAGS      = -shared -version-number 1:42:23
+tests_lib_libnotgles_la_LIBADD       = tests/helper/libhelper.la
+
+tests_red_libnotgl_la_SOURCES        = tests/notgl-red.c                       \
+                                       tests/notgl.h                           \
+                                       tests/notgl-helper.h
+tests_red_libnotgl_la_CFLAGS         = $(test_fixture_cflags)
+# Give it a gratuitously higher version number
+tests_red_libnotgl_la_LDFLAGS        = -shared -version-number 0:123:456
+# We want the RUNPATH or RPATH to point to tests/helper here, so we can
+# demonstrate the functionality of loading two different libraries from the
+# same path on host and container
+tests_red_libnotgl_la_LIBADD         = tests/helper/libhelper.la
+
+tests_red_libnotgles_la_SOURCES      = tests/notgles-red.c                     \
+                                       tests/notgles.h                         \
+                                       tests/notgl-helper.h
+tests_red_libnotgles_la_CFLAGS       = $(test_fixture_cflags)
+tests_red_libnotgles_la_LDFLAGS      = -shared -version-number 1:123:456
+tests_red_libnotgles_la_LIBADD       = tests/helper/libhelper.la
+
+tests_green_libnotgl_la_SOURCES      = tests/notgl-green.c                     \
+                                       tests/notgl.h                           \
+                                       tests/notgl-helper.h
+tests_green_libnotgl_la_CFLAGS       = $(test_fixture_cflags)
+# Give this one a gratuitously *lower* version number
+tests_green_libnotgl_la_LDFLAGS      = -shared -version-number 0:23:42
+# Again, we want the RUNPATH or RPATH to point to tests/helper here
+tests_green_libnotgl_la_LIBADD       = tests/helper/libhelper.la
+
+tests_green_libnotgles_la_SOURCES    = tests/notgles-green.c                   \
+                                       tests/notgles.h                         \
+                                       tests/notgl-helper.h
+tests_green_libnotgles_la_CFLAGS     = $(test_fixture_cflags)
+tests_green_libnotgles_la_LDFLAGS    = -shared -version-number 1:23:42
+tests_green_libnotgles_la_LIBADD     = tests/helper/libhelper.la
+
+tests_helper_libhelper_la_SOURCES    = tests/notgl-helper-ref.c                \
+                                       tests/notgl-helper.h
+tests_helper_libhelper_la_CFLAGS     = $(test_fixture_cflags)
+tests_helper_libhelper_la_LDFLAGS    = -shared -version-number 0:42:23
+
+tests_red_libhelper_la_SOURCES       = tests/notgl-helper-red.c                \
+                                       tests/notgl-helper.h
+tests_red_libhelper_la_CFLAGS        = $(test_fixture_cflags)
+tests_red_libhelper_la_LDFLAGS       = -shared -version-number 0:123:456
+
+tests_green_libhelper_la_SOURCES     = tests/notgl-helper-green.c              \
+                                       tests/notgl-helper.h
+tests_green_libhelper_la_CFLAGS      = $(test_fixture_cflags)
+tests_green_libhelper_la_LDFLAGS     = -shared -version-number 0:23:42
+
+nodist_tests_shim_libnotgl_la_SOURCES = tests/shim/libnotgl.so.0.c
+tests_shim_libnotgl_la_LIBADD        = libcapsule.la
+tests_shim_libnotgl_la_CFLAGS        = $(test_fixture_cflags)
+tests_shim_libnotgl_la_LDFLAGS       = -shared -version-number 0:0:0
+
+nodist_tests_shim_libnotgles_la_SOURCES = tests/shim/libnotgles.so.1.c
+tests_shim_libnotgles_la_LIBADD        = libcapsule.la
+tests_shim_libnotgles_la_CFLAGS        = $(test_fixture_cflags)
+tests_shim_libnotgles_la_LDFLAGS       = -shared -version-number 1:0:0
+
+tests/shim/%.c: tests/shim/%.excluded tests/shim/%.shared tests/shim/%.symbols data/capsule-mkstublib Makefile
+	$(AM_V_GEN)\
+	PKG_CONFIG_PATH=$(abs_builddir)/data \
+	V=$V \
+	$(top_srcdir)/data/capsule-mkstublib \
+		--symbols-from=$(srcdir)/tests/shim/$*.symbols \
+		--no-update-symbols \
+		--runtime-tree=/host \
+		$* \
+		$(srcdir)/tests/shim/$*.excluded \
+		$(srcdir)/tests/shim/$*.shared \
+		$@
+endif
+
+if ENABLE_SHARED
+AM_CPPFLAGS                         += -DENABLE_SHARED
+nobase_insttests_LTLIBRARIES        += tests/version1/libunversionedabibreak.la \
+                                       tests/version2/libunversionedabibreak.la \
+                                       tests/version1/libunversionednumber.la \
+                                       tests/version2/libunversionednumber.la \
+                                       tests/version1/libunversionedsymbols.la \
+                                       tests/version2/libunversionedsymbols.la \
+                                       tests/version1/libversionedabibreak.la \
+                                       tests/version2/libversionedabibreak.la \
+                                       tests/version1/libversionedlikedbus.la \
+                                       tests/version2/libversionedlikedbus.la \
+                                       tests/version1/libversionedlikeglibc.la \
+                                       tests/version2/libversionedlikeglibc.la \
+                                       tests/version1/libversionednumber.la \
+                                       tests/version2/libversionednumber.la \
+                                       tests/version1/libversionedprivatenothidden.la \
+                                       tests/version2/libversionedprivatenothidden.la \
+                                       tests/version1/libversionedsymbols.la \
+                                       tests/version2/libversionedsymbols.la \
+                                       tests/version1/libversionedupgrade.la \
+                                       tests/version2/libversionedupgrade.la
+tests_version1_libunversionedabibreak_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libunversionedabibreak_la_LDFLAGS = -shared -version-number 1:0:0
+tests_version2_libunversionedabibreak_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libunversionedabibreak_la_LDFLAGS = -shared -version-number 1:0:0
+tests_version1_libunversionednumber_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libunversionednumber_la_LDFLAGS = -shared -version-number 1:0:0
+tests_version2_libunversionednumber_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libunversionednumber_la_LDFLAGS = -shared -version-number 1:2:3
+tests_version1_libunversionedsymbols_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libunversionedsymbols_la_LDFLAGS = -shared -version-number 1:0:0
+tests_version2_libunversionedsymbols_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libunversionedsymbols_la_LDFLAGS = -shared -version-number 1:0:0
+tests_version1_libversionedabibreak_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libversionedabibreak_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                 -Wl,--version-script=$(srcdir)/tests/version1/libversionedabibreak.map
+tests_version2_libversionedabibreak_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libversionedabibreak_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                 -Wl,--version-script=$(srcdir)/tests/version2/libversionedabibreak.map
+tests_version1_libversionedlikedbus_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libversionedlikedbus_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                 -Wl,--version-script=$(srcdir)/tests/version1/libversionedlikedbus.map
+tests_version2_libversionedlikedbus_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libversionedlikedbus_la_LDFLAGS = -shared -version-number 1:2:0 \
+                                                 -Wl,--version-script=$(srcdir)/tests/version2/libversionedlikedbus.map
+tests_version1_libversionedlikeglibc_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libversionedlikeglibc_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                  -Wl,--version-script=$(srcdir)/tests/version1/libversionedlikeglibc.map
+tests_version2_libversionedlikeglibc_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libversionedlikeglibc_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                  -Wl,--version-script=$(srcdir)/tests/version2/libversionedlikeglibc.map
+tests_version1_libversionednumber_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libversionednumber_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                               -Wl,--version-script=$(srcdir)/tests/libversioned.map
+tests_version2_libversionednumber_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libversionednumber_la_LDFLAGS = -shared -version-number 1:2:3 \
+                                               -Wl,--version-script=$(srcdir)/tests/libversioned.map
+tests_version1_libversionedprivatenothidden_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libversionedprivatenothidden_la_LDFLAGS = -shared -version-number 1:1:1 \
+                                                         -Wl,--version-script=$(srcdir)/tests/version1/libversionedprivatenothidden.map
+tests_version2_libversionedprivatenothidden_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libversionedprivatenothidden_la_LDFLAGS = -shared -version-number 1:1:1 \
+                                                         -Wl,--version-script=$(srcdir)/tests/version2/libversionedprivatenothidden.map
+tests_version1_libversionedsymbols_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libversionedsymbols_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                -Wl,--version-script=$(srcdir)/tests/libversioned.map
+tests_version2_libversionedsymbols_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libversionedsymbols_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                -Wl,--version-script=$(srcdir)/tests/libversioned.map
+tests_version1_libversionedupgrade_la_CFLAGS  = $(test_fixture_cflags)
+tests_version1_libversionedupgrade_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                -Wl,--version-script=$(srcdir)/tests/version1/libversionedupgrade.map
+tests_version2_libversionedupgrade_la_CFLAGS  = $(test_fixture_cflags)
+tests_version2_libversionedupgrade_la_LDFLAGS = -shared -version-number 1:0:0 \
+                                                -Wl,--version-script=$(srcdir)/tests/version2/libversionedupgrade.map
+endif
+
+LOG_DRIVER                           = env AM_TAP_AWK='$(AWK)'                 \
+                                       $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh
+LOG_COMPILER                         =
+TEST_EXTENSIONS                      = .pl .t
+PL_LOG_DRIVER                        = $(LOG_DRIVER)
+PL_LOG_COMPILER                      = $(PERL)
+T_LOG_DRIVER                         = $(LOG_DRIVER)
+T_LOG_COMPILER                       = $(SHELL) $(top_srcdir)/tests/glib-tap.sh
+AM_TESTS_ENVIRONMENT                 = export CAPSULE_CAPTURE_LIBS_TOOL="$(abs_builddir)/capsule-capture-libs"; \
+                                       export CAPSULE_CFLAGS="-I$(abs_top_srcdir) -I$(abs_top_builddir)"; \
+                                       export CAPSULE_LIBS="$(abs_top_builddir)/libcapsule.la"; \
+                                       export CAPSULE_SYMBOLS_TOOL="$(abs_builddir)/capsule-symbols";\
+                                       export CAPSULE_TESTS_GNU_BUILD="${build}"; \
+                                       export CAPSULE_TESTS_GNU_HOST="${host}"; \
+                                       export CAPSULE_TESTS_UNINSTALLED=1;     \
+                                       export CAPSULE_VERSION_TOOL="$(abs_builddir)/capsule-version";\
+                                       export CC="${CC}";                      \
+                                       export G_TEST_SRCDIR="$(abs_srcdir)";   \
+                                       export G_TEST_BUILDDIR="$(abs_builddir)";\
+                                       export G_DEBUG=gc-friendly;             \
+                                       export MALLOC_CHECK_=2;                 \
+                                       export NM="$(NM)";                      \
+                                       export PKG_CONFIG_PATH="$(abs_builddir)/data";
+if ENABLE_LIBRARY
+test_extra_programs                  = tests/notgl-user                        \
+                                       tests/notgl-helper-user                 \
+                                       tests/notgl-dlopener
+tests_notgl_user_LDADD               = tests/lib/libnotgl.la
+tests_notgl_helper_user_LDADD        = tests/lib/libnotgl.la                   \
+                                       tests/lib/libnotgles.la                 \
+                                       tests/helper/libhelper.la
+tests_notgl_dlopener_LDADD           = -ldl
+endif
+test_extra_scripts                   = tests/manual/gl.pl
+
+test_programs                        =
+if HAVE_GLIB
+test_programs                       += tests/utils.t
+endif
+
+tests_utils_t_SOURCES                = tests/utils.c        \
+                                       tests/test-helpers.c \
+                                       tests/test-helpers.h \
+                                       utils/library-cmp.c  \
+                                       utils/library-cmp.h
+tests_utils_t_LDADD                  = utils/libutils.la $(GLIB_LIBS) $(LIBELF_LIBS)
+
+test_scripts                         = tests/capture-libs.pl                   \
+                                       tests/symbols.pl                        \
+                                       tests/version.pl
+
+if ENABLE_LIBRARY
+test_scripts                        += tests/gl-shim.pl                        \
+                                       tests/init-project.pl                   \
+                                       tests/notgl.pl
+endif
+
+nobase_insttests_PROGRAMS            = $(test_programs)                        \
+                                       $(test_extra_programs)
+nobase_dist_insttests_SCRIPTS        = $(test_scripts)                         \
+                                       $(test_extra_scripts)
+TESTS                                = $(test_programs)                        \
+                                       $(test_scripts)
+testmetadir                          = $(datadir)/installed-tests/$(PACKAGE_TARNAME)
+testmeta_DATA                        = $(patsubst %,%.test,$(test_programs) $(test_scripts))
+$(testmeta_DATA): tests/%.test: tests/% Makefile
+	@$(MKDIR_P) $(dir $@)
+	$(AM_V_GEN)set -e; \
+	maybe_tap=; \
+	case "$*" in \
+		(*.t) \
+			maybe_tap=" --tap"; \
+			;; \
+	esac; \
+	(\
+		echo "[Test]"; \
+		echo "Type=session"; \
+		echo "Output=TAP"; \
+		echo "Exec=env CAPSULE_TESTS_GNU_HOST=$(host) $(insttestsdir)/tests/$*$$maybe_tap"; \
+	) > $@.tmp && mv $@.tmp $@
+
+if ENABLE_LIBRARY
+# ============================================================================
+# gtk-doc configuration: see /usr/share/doc/gtk-doc-tools/examples/Makefile.am
+DOC_MODULE           = libcapsule
+DOC_MAIN_SGML_FILE   = $(DOC_MODULE)-docs.xml
+DOC_SOURCE_DIR       = $(top_srcdir)/capsule
+SCAN_OPTIONS         = --rebuild-sections --rebuild-types
+MKDB_OPTIONS         = --xml-mode --output-format=xml
+MKTMPL_OPTIONS       =
+MKHTML_OPTIONS       =
+FIXXREF_OPTIONS      =
+HFILE_GLOB           = $(top_srcdir)/capsule/capsule.h
+CFILE_GLOB           = $(top_srcdir)/capsule/*.c
+EXTRA_HFILES         =
+IGNORE_HFILES        =
+HTML_IMAGES          =
+content_files        =
+expand_content_files =
+GTKDOC_CFLAGS        =
+GTKDOC_LIBS          =
+
+include $(top_srcdir)/gtk-doc.make
+# standalone man pages
+include  $(top_srcdir)/documentation.mk
+
+# Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=786174
+$(DOC_MAIN_SGML_FILE): sgml-build.stamp
+	@true
+else
+CLEANFILES              =
+EXTRA_DIST              =
+endif
+
+# These need to come after we include gtk-doc.make, which unconditionally
+# defines these variables and targets
+dist-hook: dist-hook-git-version-gen
+.PHONY: dist-hook-git-version-gen
+
+dist-hook-git-version-gen:
+	$(AM_V_GEN)echo $(VERSION) > $(distdir)/.tarball-version
+
+dist-hook: dist-hook-shim-sources
+.PHONY: dist-hook-shim-sources
+
+install-data-local: install-data-local-shim-sources
+.PHONY: install-data-local-shim-sources
+
+CLEANFILES             += $(testmeta_DATA)
+if ENABLE_HOST_PREFIX
+CLEANFILES             += data/$(HOST_PREFIX)capsule-mkstublib
+endif
+CLEANFILES             += $(nodist_tests_shim_libnotgl_la_SOURCES)
+CLEANFILES             += $(nodist_tests_shim_libnotgles_la_SOURCES)
+
+EXTRA_DIST             += $(top_srcdir)/.version
+EXTRA_DIST             += autogen.sh
+EXTRA_DIST             += build-aux/git-version-gen
+EXTRA_DIST             += ci/Jenkinsfile
+EXTRA_DIST             += ci/gitlab-ci.yml
+EXTRA_DIST             += data/capsule-mkstublib
+EXTRA_DIST             += doc/Building-libcapsule.txt
+EXTRA_DIST             += doc/Strategy.txt
+EXTRA_DIST             += doc/capsule-init-project.xml
+EXTRA_DIST             += doc/capsule-mkstublib.xml
+EXTRA_DIST             += doc/devhelp2man.xslt
+EXTRA_DIST             += tests/glib-tap.sh
+EXTRA_DIST             += tests/libversioned.map
+EXTRA_DIST             += tests/version1/libversionedabibreak.map
+EXTRA_DIST             += tests/version1/libversionedlikedbus.map
+EXTRA_DIST             += tests/version1/libversionedlikeglibc.map
+EXTRA_DIST             += tests/version1/libversionedprivatenothidden.map
+EXTRA_DIST             += tests/version1/libversionedupgrade.map
+EXTRA_DIST             += tests/version2/libversionedabibreak.map
+EXTRA_DIST             += tests/version2/libversionedlikedbus.map
+EXTRA_DIST             += tests/version2/libversionedlikeglibc.map
+EXTRA_DIST             += tests/version2/libversionedprivatenothidden.map
+EXTRA_DIST             += tests/version2/libversionedupgrade.map
diff --git a/subprojects/libcapsule/NEWS b/subprojects/libcapsule/NEWS
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/autogen.sh b/subprojects/libcapsule/autogen.sh
new file mode 100755
index 0000000000000000000000000000000000000000..7ed39708c90ec464ba5638767265886426e896b3
--- /dev/null
+++ b/subprojects/libcapsule/autogen.sh
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+set -e
+
+# Run this to generate all the initial makefiles, etc.
+
+[ -n "$srcdir" ] || srcdir=$(dirname "$0");
+[ -n "$srcdir" ] || srcdir=.;
+
+( cd $srcdir
+
+  if [ "$#" = 0 -a "x$NOCONFIGURE" = "x" ];
+  then
+      (echo "*** WARNING: Running 'configure' with no arguments.";
+       echo "*** configure arguments should be passed as:"       ;
+       echo "*** $0 ARGS..."                                     ;
+       echo ""                                                   ) >&2;
+  fi;
+  aclocal -I m4 --install || exit $?;
+  gtkdocize               || exit $?;
+  autoreconf -ivf         || exit $?; );
+
+if [ "$NOCONFIGURE" = "" ];
+then
+    $srcdir/configure "$@" || exit $?;
+
+    if [ "$1" = "--help" ]; then exit 0; fi;
+    echo "Now type 'make' to compile";
+else
+    echo "Skipping configure process.";
+fi
diff --git a/subprojects/libcapsule/build-aux/git-version-gen b/subprojects/libcapsule/build-aux/git-version-gen
new file mode 100755
index 0000000000000000000000000000000000000000..0e254f4bef10ae712755cbb0bee8211b720b1b1e
--- /dev/null
+++ b/subprojects/libcapsule/build-aux/git-version-gen
@@ -0,0 +1,227 @@
+#!/bin/sh
+# Print a version string.
+scriptversion=2017-09-13.06; # UTC
+
+# Copyright (C) 2007-2017 Free Software Foundation, Inc.
+#
+# 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 3 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, see <https://www.gnu.org/licenses/>.
+
+# This script is derived from GIT-VERSION-GEN from GIT: https://git-scm.com/.
+# It may be run two ways:
+# - from a git repository in which the "git describe" command below
+#   produces useful output (thus requiring at least one signed tag)
+# - from a non-git-repo directory containing a .tarball-version file, which
+#   presumes this script is invoked like "./git-version-gen .tarball-version".
+
+# In order to use intra-version strings in your project, you will need two
+# separate generated version string files:
+#
+# .tarball-version - present only in a distribution tarball, and not in
+#   a checked-out repository.  Created with contents that were learned at
+#   the last time autoconf was run, and used by git-version-gen.  Must not
+#   be present in either $(srcdir) or $(builddir) for git-version-gen to
+#   give accurate answers during normal development with a checked out tree,
+#   but must be present in a tarball when there is no version control system.
+#   Therefore, it cannot be used in any dependencies.  GNUmakefile has
+#   hooks to force a reconfigure at distribution time to get the value
+#   correct, without penalizing normal development with extra reconfigures.
+#
+# .version - present in a checked-out repository and in a distribution
+#   tarball.  Usable in dependencies, particularly for files that don't
+#   want to depend on config.h but do want to track version changes.
+#   Delete this file prior to any autoconf run where you want to rebuild
+#   files to pick up a version string change; and leave it stale to
+#   minimize rebuild time after unrelated changes to configure sources.
+#
+# As with any generated file in a VC'd directory, you should add
+# /.version to .gitignore, so that you don't accidentally commit it.
+# .tarball-version is never generated in a VC'd directory, so needn't
+# be listed there.
+#
+# Use the following line in your configure.ac, so that $(VERSION) will
+# automatically be up-to-date each time configure is run (and note that
+# since configure.ac no longer includes a version string, Makefile rules
+# should not depend on configure.ac for version updates).
+#
+# AC_INIT([GNU project],
+#         m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+#         [bug-project@example])
+#
+# Then use the following lines in your Makefile.am, so that .version
+# will be present for dependencies, and so that .version and
+# .tarball-version will exist in distribution tarballs.
+#
+# EXTRA_DIST = $(top_srcdir)/.version
+# BUILT_SOURCES = $(top_srcdir)/.version
+# $(top_srcdir)/.version:
+#	echo $(VERSION) > $@-t && mv $@-t $@
+# dist-hook:
+#	echo $(VERSION) > $(distdir)/.tarball-version
+
+
+me=$0
+
+version="git-version-gen $scriptversion
+
+Copyright 2011 Free Software Foundation, Inc.
+There is NO warranty.  You may redistribute this software
+under the terms of the GNU General Public License.
+For more information about these matters, see the files named COPYING."
+
+usage="\
+Usage: $me [OPTION]... \$srcdir/.tarball-version [TAG-NORMALIZATION-SED-SCRIPT]
+Print a version string.
+
+Options:
+
+   --prefix PREFIX    prefix of git tags (default 'v')
+   --fallback VERSION
+                      fallback version to use if \"git --version\" fails
+
+   --help             display this help and exit
+   --version          output version information and exit
+
+Running without arguments will suffice in most cases."
+
+prefix=v
+fallback=
+
+while test $# -gt 0; do
+  case $1 in
+    --help) echo "$usage"; exit 0;;
+    --version) echo "$version"; exit 0;;
+    --prefix) shift; prefix=${1?};;
+    --fallback) shift; fallback=${1?};;
+    -*)
+      echo "$0: Unknown option '$1'." >&2
+      echo "$0: Try '--help' for more information." >&2
+      exit 1;;
+    *)
+      if test "x$tarball_version_file" = x; then
+        tarball_version_file="$1"
+      elif test "x$tag_sed_script" = x; then
+        tag_sed_script="$1"
+      else
+        echo "$0: extra non-option argument '$1'." >&2
+        exit 1
+      fi;;
+  esac
+  shift
+done
+
+if test "x$tarball_version_file" = x; then
+    echo "$usage"
+    exit 1
+fi
+
+tag_sed_script="${tag_sed_script:-s/x/x/}"
+
+nl='
+'
+
+# Avoid meddling by environment variable of the same name.
+v=
+v_from_git=
+
+# First see if there is a tarball-only version file.
+# then try "git describe", then default.
+if test -f $tarball_version_file
+then
+    v=`cat $tarball_version_file` || v=
+    case $v in
+        *$nl*) v= ;; # reject multi-line output
+        [0-9]*) ;;
+        *) v= ;;
+    esac
+    test "x$v" = x \
+        && echo "$0: WARNING: $tarball_version_file is missing or damaged" 1>&2
+fi
+
+if test "x$v" != x
+then
+    : # use $v
+# Otherwise, if there is at least one git commit involving the working
+# directory, and "git describe" output looks sensible, use that to
+# derive a version string.
+elif test "`git log -1 --pretty=format:x . 2>&1`" = x \
+    && v=`git describe --abbrev=4 --match="$prefix*" HEAD 2>/dev/null \
+          || git describe --abbrev=4 HEAD 2>/dev/null` \
+    && v=`printf '%s\n' "$v" | sed "$tag_sed_script"` \
+    && case $v in
+         $prefix[0-9]*) ;;
+         *) (exit 1) ;;
+       esac
+then
+    # Is this a new git that lists number of commits since the last
+    # tag or the previous older version that did not?
+    #   Newer: v6.10-77-g0f8faeb
+    #   Older: v6.10-g0f8faeb
+    vprefix=`expr "X$v" : 'X\(.*\)-g[^-]*$'` || vprefix=$v
+    case $vprefix in
+        *-*) : git describe is probably okay three part flavor ;;
+        *)
+            : git describe is older two part flavor
+            # Recreate the number of commits and rewrite such that the
+            # result is the same as if we were using the newer version
+            # of git describe.
+            vtag=`echo "$v" | sed 's/-.*//'`
+            commit_list=`git rev-list "$vtag"..HEAD 2>/dev/null` \
+                || { commit_list=failed;
+                     echo "$0: WARNING: git rev-list failed" 1>&2; }
+            numcommits=`echo "$commit_list" | wc -l`
+            v=`echo "$v" | sed "s/\(.*\)-\(.*\)/\1-$numcommits-\2/"`;
+            test "$commit_list" = failed && v=UNKNOWN
+            ;;
+    esac
+
+    # Change the penultimate "-" to ".", for version-comparing tools.
+    # Remove the "g" to save a byte.
+    v=`echo "$v" | sed 's/-\([^-]*\)-g\([^-]*\)$/.\1-\2/'`;
+    v_from_git=1
+elif test "x$fallback" = x || git --version >/dev/null 2>&1; then
+    v=UNKNOWN
+else
+    v=$fallback
+fi
+
+v=`echo "$v" |sed "s/^$prefix//"`
+
+# Test whether to append the "-dirty" suffix only if the version
+# string we're using came from git.  I.e., skip the test if it's "UNKNOWN"
+# or if it came from .tarball-version.
+if test "x$v_from_git" != x; then
+  # Don't declare a version "dirty" merely because a timestamp has changed.
+  git update-index --refresh > /dev/null 2>&1
+
+  dirty=`exec 2>/dev/null;git diff-index --name-only HEAD` || dirty=
+  case "$dirty" in
+      '') ;;
+      *) # Append the suffix only if there isn't one already.
+          case $v in
+            *-dirty) ;;
+            *) v="$v-dirty" ;;
+          esac ;;
+  esac
+fi
+
+# Omit the trailing newline, so that m4_esyscmd can use the result directly.
+printf %s "$v"
+
+# Local variables:
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-time-zone: "UTC0"
+# time-stamp-end: "; # UTC"
+# End:
diff --git a/subprojects/libcapsule/capsule/_int_dlopen.h b/subprojects/libcapsule/capsule/_int_dlopen.h
new file mode 100644
index 0000000000000000000000000000000000000000..127b9dc985851f755a6c96d5d6f836379a058568
--- /dev/null
+++ b/subprojects/libcapsule/capsule/_int_dlopen.h
@@ -0,0 +1,50 @@
+// Copyright © 2017 Collabora Ltd
+//
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+// Standard implementation of _int_dlopen(), the implementation used
+// when a library inside the capsule calls dlopen(). To override this
+// implementation, copy this file to shim/capsule/_int_dlopen.h in
+// your shim library and modify as needed.
+static void *
+_int_dlopen (const char *filename, int flag)
+{
+    if( flag & RTLD_GLOBAL )
+    {
+        fprintf( stderr, "Warning: libcapsule dlopen wrapper cannot pass "
+                         "RTLD_GLOBAL to underlying dlmopen(%s...) call\n",
+                 filename );
+        flag = (flag & ~RTLD_GLOBAL) & 0xfffff;
+    }
+    return capsule_shim_dlopen( cap, filename, flag );
+}
+
+// if the libc instances aren't unified (ie > 1 libc) then
+// we must try to dispatch the to-be-freed pointer to the one
+// that actually allocated it.
+// This is far from foolproof:
+static void
+_wrapped_free (void *ptr)
+{
+    if (ptr)
+        capsule_shim_free( cap, ptr );
+}
+
+static void *
+_wrapped_realloc (void *ptr, size_t size)
+{
+    return capsule_shim_realloc( cap, ptr, size );
+}
diff --git a/subprojects/libcapsule/capsule/capsule-dlmopen.c b/subprojects/libcapsule/capsule/capsule-dlmopen.c
new file mode 100644
index 0000000000000000000000000000000000000000..d863c537f87cd6c3eba9290b344d8225e1c3529c
--- /dev/null
+++ b/subprojects/libcapsule/capsule/capsule-dlmopen.c
@@ -0,0 +1,333 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <string.h>
+#include <fcntl.h>
+
+#include <libelf.h>
+#include <gelf.h>
+#include <dlfcn.h>
+
+#include <capsule/capsule.h>
+#include "capsule/capsule-private.h"
+
+#include "utils/utils.h"
+#include "utils/dump.h"
+#include "utils/mmap-info.h"
+#include "utils/process-pt-dynamic.h"
+#include "utils/ld-cache.h"
+#include "utils/ld-libs.h"
+
+// ==========================================================================
+// some pretty printers for debugging:
+
+// dump out the contents of the ld cache to stderr:
+static void
+dump_ld_cache (ld_libs *ldlibs)
+{
+    ld_cache_foreach( &ldlibs->ldcache, ld_entry_dump, stderr );
+}
+
+/*
+ * wrap:
+ * @name:
+ * @base: Starting address of the program header in memory.
+ *  Addresses are normally relative to this, except for when they are
+ *  absolute (see fix_addr()).
+ * @dyn: An array of ElfW(Dyn) structures, somewhere after @base
+ * @wrappers: Relocations to apply
+ */
+static void
+wrap (const char *name,
+      ElfW(Addr) base,
+      ElfW(Dyn) *dyn,
+      capsule_item *wrappers)
+{
+    int mmap_errno = 0;
+    const char *mmap_error = NULL;
+    ElfW(Addr) start = (ElfW(Addr)) dyn - base;
+    // we don't know the size so we'll have to rely on the linker putting
+    // well formed entries into the mmap()ed DSO region.
+    // (tbf if the linker is putting duff entries here we're boned anyway)
+    //
+    // dyn is the address of the dynamic section
+    // base is the start of the program header in memory
+    // start should be the offset from the program header to its dyn section
+    //
+    // the utility functions expect an upper bound though so set that to
+    // something suitably large:
+    relocation_data rdata = { 0 };
+
+    DEBUG( DEBUG_WRAPPERS,
+           "\"%s\": base address %" PRIxPTR ", dynamic section at %p",
+           name, base, dyn );
+
+    rdata.debug     = debug_flags;
+    rdata.error     = NULL;
+    rdata.relocs    = wrappers;
+
+    // if RELRO linking has happened we'll need to tweak the mprotect flags
+    // before monkeypatching the symbol tables, for which we will need the
+    // sizes, locations and current protections of any mmap()ed regions:
+    rdata.mmap_info = load_mmap_info( &mmap_errno, &mmap_error );
+
+    if( mmap_errno || mmap_error )
+    {
+        DEBUG( DEBUG_MPROTECT,
+               "mmap/mprotect flags information load error (errno: %d): %s",
+               mmap_errno, mmap_error );
+        DEBUG( DEBUG_MPROTECT,
+               "relocation will be unable to handle RELRO linked libraries" );
+    }
+
+    // make all the mmap()s writable:
+    for( int i = 0; rdata.mmap_info[i].start != MAP_FAILED; i++ )
+        if( mmap_entry_should_be_writable( &rdata.mmap_info[i] ) )
+            add_mmap_protection( &rdata.mmap_info[i], PROT_WRITE );
+
+    // if we're debugging wrapper installation in detail we
+    // will end up in a path that's normally only DEBUG_ELF
+    // debugged:
+    if( debug_flags & DEBUG_WRAPPERS )
+        debug_flags = debug_flags | DEBUG_RELOCS;
+
+    // install any required wrappers inside the capsule:
+    process_pt_dynamic( start,  // offset from phdr to dyn section
+                        0,      //  fake size value
+                        (void *) base,   //  address of phdr in memory
+                        process_dt_rela,
+                        process_dt_rel,
+                        &rdata );
+
+    // put the debug flags back in case we changed them
+    debug_flags = rdata.debug;
+
+    // put the mmap()/mprotect() permissions back the way they were:
+    for( int i = 0; rdata.mmap_info[i].start != MAP_FAILED; i++ )
+        if( mmap_entry_should_be_writable( &rdata.mmap_info[i] ) )
+            reset_mmap_protection( &rdata.mmap_info[i] );
+
+    free_mmap_info( rdata.mmap_info );
+    rdata.mmap_info = NULL;
+}
+
+static inline int
+excluded_from_wrap (const char *name, char **exclude)
+{
+    const char *dso = strrchr(name, '/');
+
+    // we can't ever subvert the runtime linker itself:
+    if( strncmp( "/ld-", dso, 4 ) == 0 )
+        return 1;
+
+    for( char **x = exclude; x && *x; x++ )
+        if( strcmp ( *x, dso + 1 ) == 0 )
+            return 1;
+
+    return 0;
+}
+
+/*
+ * @exclude: (array zero-terminated=1): same as for capsule_dlmopen()
+ * @errcode: (out): errno
+ * @error: (out) (transfer full) (optional): Error string
+ */
+// replace calls out to dlopen in the encapsulated DSO with a wrapper
+// which should take care of preserving the /path-prefix and namespace
+// wrapping of the original capsule_dlmopen() call.
+//
+// strictly speaking we can wrap things other than dlopen(),
+// but that's currently all we use this for:
+static int install_wrappers ( void *dl_handle,
+                              capsule_item *wrappers,
+                              const char **exclude,
+                              int *errcode,
+                              char **error)
+{
+    int replacements = 0;
+    struct link_map *map;
+
+    if( dlinfo( dl_handle, RTLD_DI_LINKMAP, &map ) != 0 )
+    {
+        const char *local_error = dlerror();
+
+        if( error )
+            *error = xstrdup( local_error );
+
+        if( errcode )
+            *errcode = EINVAL;
+
+        DEBUG( DEBUG_WRAPPERS, "mangling capsule symbols: %s", local_error );
+
+        return -1;
+    }
+
+    DEBUG( DEBUG_WRAPPERS, "link_map: %p <- %p -> %p",
+               map ? map->l_next : NULL ,
+               map ? map         : NULL ,
+               map ? map->l_prev : NULL );
+
+    // no guarantee that we're at either end of the link map:
+    while( map->l_prev )
+        map = map->l_prev;
+
+    unsigned long df = debug_flags;
+
+    if( debug_flags & DEBUG_WRAPPERS )
+        debug_flags |= DEBUG_RELOCS;
+
+    if (map->l_next)
+    {
+        for( struct link_map *m = map; m; m = m->l_next )
+        {
+            if( excluded_from_wrap(m->l_name, (char **)exclude) )
+            {
+                DEBUG( DEBUG_WRAPPERS, "%s excluded from wrapping",
+                       m->l_name );
+            }
+            else
+            {
+                wrap( m->l_name, m->l_addr, m->l_ld, wrappers );
+            }
+        }
+    }
+
+    debug_flags = df;
+
+    return replacements;
+}
+
+// dump the link map info for the given dl handle (NULL = default)
+static void
+dump_link_map( void *dl_handle )
+{
+    struct link_map *map;
+    void *handle;
+
+    if( !dl_handle )
+        handle = dlopen( NULL, RTLD_LAZY|RTLD_NOLOAD );
+    else
+        handle = dl_handle;
+
+    if( dlinfo( handle, RTLD_DI_LINKMAP, &map ) != 0 )
+    {
+        DEBUG( DEBUG_CAPSULE, "failed to access link_map for handle %p-%p: %s",
+               dl_handle, handle, dlerror() );
+        return;
+    }
+
+    // be kind, rewind the link map:
+    while( map->l_prev )
+        map = map->l_prev;
+
+    fprintf( stderr, "(dl-handle %s", dl_handle ? "CAPSULE" : "DEFAULT" );
+    for( struct link_map *m = map; m; m = m->l_next )
+        fprintf( stderr, "\n  [prev: %p] %p: \"%s\" [next: %p]",
+                 m->l_prev, m, m->l_name, m->l_next );
+    fprintf( stderr, ")\n" );
+}
+
+// ==========================================================================
+void *
+_capsule_load (const capsule cap,
+               capsule_item *wrappers,
+               int *errcode,
+               char **error)
+{
+    void *ret = NULL;
+    ld_libs ldlibs = {};
+
+    if( !ld_libs_init( &ldlibs,
+                       (const char **) cap->ns->combined_exclude,
+                       cap->ns->prefix, debug_flags, errcode, error ) )
+        return NULL;
+
+    // ==================================================================
+    // read in the ldo.so.cache - this will contain all architectures
+    // currently installed (x86_64, i386, x32) in no particular order
+    if( ld_libs_load_cache( &ldlibs, errcode, error ) )
+    {
+        if( debug_flags & DEBUG_LDCACHE )
+            dump_ld_cache( &ldlibs );
+    }
+    else
+    {
+        return NULL;
+    }
+
+    // ==================================================================
+    // find the starting point of our capsule
+    if( !ld_libs_set_target( &ldlibs, cap->meta->soname, errcode, error ) )
+        goto cleanup;
+
+    // ==================================================================
+    // once we have the starting point recursively find all its DT_NEEDED
+    // entries, except for the linker itself and libc, which must not
+    // be different between the capsule and the "real" DSO environment:
+    if( !ld_libs_find_dependencies( &ldlibs, errcode, error ) )
+        goto cleanup;
+
+    // ==================================================================
+    // load the stack of DSOs we need:
+    ret = ld_libs_load( &ldlibs, &cap->ns->ns, 0, errcode, error );
+
+    if( debug_flags & DEBUG_CAPSULE )
+    {
+        dump_link_map( ret  );
+        dump_link_map( NULL );
+    }
+
+    if( !ret )
+        goto cleanup;
+
+    // =====================================================================
+    // stash any free/malloc/etc implementations _before_ we overwrite them:
+    // (only really need to deal with functions that deal with pre-alloc'd
+    //  pointers, ie free and realloc)
+    if( cap->ns->mem->free == NULL )
+        cap->ns->mem->free = dlsym( ret, "free" );
+
+    if( cap->ns->mem->realloc == NULL )
+        cap->ns->mem->realloc = dlsym( ret, "realloc" );
+
+    // =====================================================================
+    // TODO: failure in the dlopen fixup phase should probably be fatal:
+    if( ret      != NULL && // no errors so far
+        wrappers != NULL )  // have a dlopen fixup function
+        install_wrappers( ret, wrappers,
+                          (const char **)cap->ns->combined_exclude,
+                          errcode, error );
+
+    cap->dl_handle = ret;
+
+cleanup:
+    ld_libs_finish( &ldlibs );
+    return ret;
+}
+
diff --git a/subprojects/libcapsule/capsule/capsule-init.c b/subprojects/libcapsule/capsule/capsule-init.c
new file mode 100644
index 0000000000000000000000000000000000000000..4c9d1ed2fc1467e6832ac2d0edb2d23b3e15a28b
--- /dev/null
+++ b/subprojects/libcapsule/capsule/capsule-init.c
@@ -0,0 +1,665 @@
+#include <capsule/capsule.h>
+#include "capsule/capsule-private.h"
+#include "utils/utils.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <dlfcn.h>
+#include <string.h>
+#include <ctype.h>
+#include <sys/param.h>
+
+#include <unistd.h>
+
+#define CAP_ENV_PREFIX "CAPSULE_"
+
+#define DUMP_STRV(what, css) \
+    ({ int _i = 0; for( char **_c = css; _c && *_c; _c++, _i++ ) \
+            DEBUG( DEBUG_CAPSULE, "  ->%s[ %02d ]: %s", #what, _i, *_c ); })
+
+#define DUMP_CAPSULE(x, cap) ({  \
+        DEBUG( DEBUG_CAPSULE, "\nCAPSULE #%d\n", (int) x ); \
+        DEBUG( DEBUG_CAPSULE, "                        \n" \
+              "struct _capsule %p"                     "\n"  \
+              "{"                                      "\n"  \
+              "  void        *dl_handle;          %p"  "\n"  \
+              "  capsule_metadata *meta;          %p"  "\n"  \
+              "  {"                                    "\n"  \
+              "    const char  *soname;           %s"  "\n"  \
+              "    const char  *default_prefix;   %s"  "\n"  \
+              "    const char **exclude;          %p"  "\n"  \
+              "    const char **export;           %p"  "\n"  \
+              "  };"                                   "\n"  \
+              "  capsule_namespace *ns;           %p"  "\n"  \
+              "  {"                                    "\n"  \
+              "    Lmid_t      ns;               %ld"  "\n"  \
+              "    char        *prefix;           %s"  "\n"  \
+              "    char **combined_exclude;       %p"  "\n"  \
+              "    char **combined_export;        %p"  "\n"  \
+              "  };"                                   "\n"  \
+              "};"                                     "\n", \
+              cap                         ,                  \
+              cap->dl_handle              ,                  \
+              cap->meta                   ,                  \
+              cap->meta->soname           ,                  \
+              cap->meta->default_prefix   ,                  \
+              cap->meta->exclude          ,                  \
+              cap->meta->export           ,                  \
+              cap->ns                     ,                  \
+              cap->ns->ns                 ,                  \
+              cap->ns->prefix             ,                  \
+              cap->ns->combined_exclude   ,                  \
+              cap->ns->combined_export);})
+
+/*
+ * never_encapsulated:
+ *
+ * Array of SONAMEs that never have a private copy inside a capsule.
+ * Only one copy of each of these will be loaded, and they will always
+ * be loaded without respecting the #capsule_namespace's @prefix, even
+ * if loaded from inside a capsule.
+ *
+ * This currently contains the libraries built by the glibc source package.
+ */
+static const char * const never_encapsulated[] =
+{
+    "libBrokenLocale.so.1",
+    "libanl.so.1",
+    "libc.so.6",
+    "libcidn.so.1",
+    "libcrypt.so.1",
+    "libdl.so.2",
+    "libm.so.6",
+    "libmvec.so.1",
+    "libnsl.so.1",
+    "libpthread.so.0",
+    "libresolv.so.2",
+    "librt.so.1",
+    "libthread_db.so.1",
+    "libutil.so.1",
+};
+
+static ptr_list *namespaces = NULL;
+
+ptr_list *_capsule_list  = NULL;
+dlsymfunc _capsule_original_dlsym = NULL;
+dlopnfunc _capsule_original_dlopen = NULL;
+
+freefunc   _capsule_original_free    = NULL;
+mallocfunc _capsule_original_malloc  = NULL;
+callocfunc _capsule_original_calloc  = NULL;
+rallocfunc _capsule_original_realloc = NULL;
+palignfunc _capsule_original_pmalign = NULL;
+
+static int str_equal (const void *a, const void *b)
+{
+    if( a == b )
+        return 1;
+
+    if( !a || !b )
+        return 0;
+
+    return strcmp( (char *)a, (char *)b ) == 0;
+}
+
+static const char *get_prefix_nocopy (const char *dflt, const char *soname);
+
+static capsule_namespace *
+get_namespace (const char *default_prefix, const char *soname)
+{
+    const char *prefix = get_prefix_nocopy( default_prefix, soname );
+    capsule_namespace *ns;
+
+    // Normalize so that libraries with prefix NULL, "" or "/" are all
+    // treated as equivalent
+    if( prefix == NULL || prefix[0] == '\0' )
+        prefix = "/";
+
+    if( namespaces == NULL )
+        namespaces = ptr_list_alloc( 4 );
+
+    for( size_t x = 0; x < namespaces->next; x++ )
+    {
+        ns = ptr_list_nth_ptr( namespaces, x );
+
+        if( !ns || strcmp( prefix, ns->prefix ) )
+            continue;
+
+        return ns;
+    }
+
+    ns = calloc( 1, sizeof(capsule_namespace) );
+    ns->ns          = LM_ID_NEWLM;
+    ns->prefix      = strdup( prefix );
+    ns->exclusions  = ptr_list_alloc( 4 );
+    ns->exports     = ptr_list_alloc( 4 );
+    ns->mem         = calloc( 1, sizeof(capsule_memory) );
+
+    ptr_list_push_ptr( namespaces, ns );
+
+    return ns;
+}
+
+// items with names starting with '-' are initialised by hand
+// and will be skipped in the automatic section below:
+static capsule_item alloc_func[] =
+{
+    { "-dlopen"       , (capsule_addr) NULL                       },
+    { "-free"         , (capsule_addr) NULL                       },
+    { "-realloc"      , (capsule_addr) NULL                       },
+    { "malloc"        , (capsule_addr) &_capsule_original_malloc  },
+    { "calloc"        , (capsule_addr) &_capsule_original_calloc  },
+    { "posix_memalign", (capsule_addr) &_capsule_original_pmalign },
+    { NULL }
+};
+
+static void
+get_capsule_metadata (struct link_map *map, const char *only)
+{
+    ElfW(Addr) base = map->l_addr;
+    ElfW(Dyn) *dyn  = map->l_ld;
+    ElfW(Dyn) *entry;
+    ElfW(Sym) *symbol;
+
+    const void *strtab = NULL;
+    const void *symtab = NULL;
+
+    if( map->l_name == NULL || *map->l_name == '\0' )
+        return;
+
+    for( entry = dyn; entry->d_tag != DT_NULL; entry++ )
+    {
+        switch( entry->d_tag )
+        {
+          case DT_SYMTAB:
+            symtab = (const void *) fix_addr( (void *)base, entry->d_un.d_ptr );
+            break;
+
+          case DT_STRTAB:
+            strtab = (const void *) fix_addr( (void *)base, entry->d_un.d_ptr );
+            break;
+
+          default:
+            break;
+        }
+    }
+
+    if( !strtab || !symtab )
+        return;
+
+    for( symbol = (ElfW(Sym) *)symtab;
+         ( (ELFW_ST_TYPE(symbol->st_info) < STT_NUM) &&
+           (ELFW_ST_BIND(symbol->st_info) < STB_NUM) );
+         symbol++ )
+    {
+        capsule_metadata *meta = NULL;
+        capsule cap = NULL;
+        char *name = (char *)strtab + symbol->st_name;
+
+        if( !name || strcmp( "capsule_meta", name ) )
+            continue;
+
+        meta = (capsule_metadata *)fix_addr( (void *)base, symbol->st_value );
+
+        // if we were asked for a specific soname's meta data then ignore
+        // everything else:
+        if( only && strcmp( only, meta->soname ) )
+            continue;
+
+        // not a version of the ABI we understand? skip it.
+        if( meta->capsule_abi != 0 )
+            continue;
+
+        cap = meta->handle;
+
+        if( cap == NULL )
+        {
+            // Functions we must override in the DSOs
+            // inside the capsule (mostly to take account of the fact that
+            // they're pulled in from a tree with a filesystem prefix like /host)
+            // NOTE: the shim address here isn't used, but we give it the same
+            // value as the real function address so it's never accidentally
+            // a value the capsule code will care about:
+            int i = 0;
+            static const capsule_item no_wrapper = { NULL };
+            const capsule_item int_dlopen_wrapper =
+            {
+                "dlopen",
+                (capsule_addr) meta->int_dlopen,
+                (capsule_addr) meta->int_dlopen,
+            };
+
+            const capsule_item int_free_wrapper =
+            {
+                "free",
+                (capsule_addr) meta->int_free,
+                (capsule_addr) meta->int_free,
+            };
+
+            const capsule_item int_realloc_wrapper =
+            {
+                "realloc",
+                (capsule_addr) meta->int_realloc,
+                (capsule_addr) meta->int_realloc,
+            };
+
+            cap = xcalloc( 1, sizeof(struct _capsule) );
+            cap->ns = get_namespace( meta->default_prefix, meta->soname );
+            DEBUG( DEBUG_CAPSULE,
+                   "Creating new capsule %p for metadata %p (%s … %s)",
+                   cap, meta, cap->ns->prefix, meta->soname );
+            cap->meta = meta;
+            cap->seen.all  = ptr_list_alloc( 32 );
+            cap->seen.some = ptr_list_alloc( 32 );
+
+            cap->internal_wrappers[ 0 ] = int_dlopen_wrapper;
+            cap->internal_wrappers[ 1 ] = int_free_wrapper;
+            cap->internal_wrappers[ 2 ] = int_realloc_wrapper;
+
+            for( i = 3; alloc_func[ i ].name != NULL; i++ )
+            {
+                // pre-populated functions that we should skip are
+                // flagged with an invalid name starting with '-'.
+                // in a properly laid out file they'll all be at the start of
+                // alloc_func and we should never see them, but just in case:
+                if( alloc_func[ i ].name[ 0 ] == '-' )
+                    continue;
+
+                capsule_item *capi = &cap->internal_wrappers[ i ];
+                void        **slot = (void **) alloc_func[ i ].real;
+
+                // the addresses held in alloc_func are actually the
+                // addresses of pointers to the real implementations,
+                // which are populated during capsule_init: we deref
+                // those here:
+                capi->name = alloc_func[ i ].name;
+                capi->shim = capi->real = (capsule_addr) *slot;
+            }
+
+            cap->internal_wrappers[ i ] = no_wrapper;
+            meta->handle = cap;
+            ptr_list_push_ptr( _capsule_list, cap );
+        }
+
+        DEBUG( DEBUG_CAPSULE,
+               "found metadata for %s … %s at %p (capsule: %p)",
+               cap->ns->prefix, meta->soname, meta, cap );
+        break;
+    }
+}
+
+/**
+ * cook_list:
+ * @list: a ptr_list containing statically-allocated strings
+ * @extras: (array length=n_extras): extra entries to be added
+ * @n_extras: number of extra entries
+ *
+ * Return @list + @extras as an array of strings. The strings are not
+ * copied, so the result is only valid as long as the strings are.
+ *
+ * Returns: (transfer container) (array zero-terminated=1) (element-type utf8):
+ *  a shallow copy of everything in either @list or @extras
+ */
+static char **
+cook_list (ptr_list *list,
+           const char * const *extras,
+           size_t n_extras)
+{
+    char **cooked = calloc( list->next + n_extras + 1, sizeof(char *) );
+
+    for( size_t j = 0; j < list->next; j++ )
+        cooked[j] = (char *)ptr_list_nth_ptr( list, j );
+
+    for( size_t j = 0; j < n_extras; j++ )
+        cooked[j + list->next] = (char *)extras[j];
+
+    *(cooked + list->next + n_extras) = NULL;
+
+    return cooked;
+}
+
+/**
+ * add_new_strings_to_ptrlist:
+ * @list: a ptr_list
+ * @strings: (transfer none) (array zero-terminated=1) (nullable):
+ *
+ * Add each string in @strings to @list, unless a string with the same
+ * content is already present. The strings must continue to exist as long
+ * as @list does.
+ */
+static void
+add_new_strings_to_ptrlist (ptr_list *list, const char * const *strings)
+{
+    for( const char * const *c = strings; c && *c; c++ )
+        ptr_list_add_ptr( list, (char *) *c, str_equal );
+}
+
+/**
+ * free_strv:
+ * @strv: (transfer container) (array zero-terminated=1) (element-type utf8):
+ *
+ * Free @strv, but not its contents.
+ */
+static void
+free_strv (char **strv)
+{
+    if( !strv )
+        return;
+
+    free( strv );
+}
+
+static void update_namespaces (void);
+
+static void
+update_metadata (const char *match)
+{
+    struct link_map *map;
+    void *handle;
+
+    handle = dlmopen( LM_ID_BASE, NULL, RTLD_LAZY|RTLD_NOLOAD );
+    dlinfo( handle, RTLD_DI_LINKMAP, &map );
+
+    // we're not guaranteed to be at the start of the link map chain:
+    while( map->l_prev )
+        map = map->l_prev;
+
+    // pick up any capsule metadata we can see:
+    // if the soname (match) is NULL, this means _all_ metadata,
+    // otherwise just the metadata that is string-equal:
+    if (map->l_next)
+        for( struct link_map *m = map; m; m = m->l_next )
+            get_capsule_metadata( m, match );
+
+    update_namespaces();
+}
+
+static void
+update_namespaces (void)
+{
+    if( !namespaces )
+        return;
+
+    // wipe out the namespaces' merged lists of exclusions etc. -
+    // they contain strings that point into capsule metadata that might
+    // no longer be valid, if we dlclosed a shim library
+    for( size_t i = 0; i < namespaces->next; i++ )
+    {
+        capsule_namespace *ns = ptr_list_nth_ptr( namespaces, i );
+
+        if( !ns )
+            continue;
+
+        DEBUG( DEBUG_CAPSULE, "Resetting namespace #%zu %p \"%s\"",
+               i, ns, ns->prefix );
+
+        // We don't free the actual strings because we don't own them;
+        // just truncate the list to 0 entries
+        ns->exclusions->next = 0;
+        ns->exports->next = 0;
+    }
+
+    // merge the string lists for each active prefix:
+    // ie all excludes for /host should be in one exclude list,
+    // all export entries for /host should bein another list, all
+    // excludes for /badgerbadger should be in another, etc etc:
+    for( size_t i = 0; i < _capsule_list->next; i++ )
+    {
+        capsule cap = ptr_list_nth_ptr( _capsule_list, i );
+
+        if( !cap )
+            continue;
+
+        DEBUG( DEBUG_CAPSULE,
+               "Collecting strings from capsule #%zu %p \"%s\" into namespace "
+               "%p \"%s\"",
+               i, cap, cap->meta->soname, cap->ns, cap->ns->prefix );
+
+        add_new_strings_to_ptrlist( cap->ns->exclusions, cap->meta->exclude );
+        add_new_strings_to_ptrlist( cap->ns->exports,    cap->meta->export  );
+    }
+
+    // now squash the meta data ptr_lists into char** that
+    // the underlying infrastructure actually uses:
+    for( size_t i = 0; i < namespaces->next; i++ )
+    {
+        capsule_namespace *ns = ptr_list_nth_ptr( namespaces, i );
+
+        if( !ns )
+            continue;
+
+        free_strv( ns->combined_exclude );
+        free_strv( ns->combined_export  );
+
+        ns->combined_exclude = cook_list( ns->exclusions,
+                                          never_encapsulated,
+                                          N_ELEMENTS( never_encapsulated ) );
+        ns->combined_export  = cook_list( ns->exports, NULL, 0 );
+    }
+}
+
+static void __attribute__ ((constructor)) _init_capsule (void)
+{
+    _capsule_list = ptr_list_alloc( 16 );
+
+#ifdef HAVE_SECURE_GETENV
+    set_debug_flags( secure_getenv("CAPSULE_DEBUG") );
+#endif
+
+    // these are needed if there is > 1 libc instance:
+    _capsule_original_free    = dlsym( RTLD_DEFAULT, "free"    );
+    _capsule_original_malloc  = dlsym( RTLD_DEFAULT, "malloc"  );
+    _capsule_original_calloc  = dlsym( RTLD_DEFAULT, "calloc"  );
+    _capsule_original_realloc = dlsym( RTLD_DEFAULT, "realloc" );
+    _capsule_original_pmalign = dlsym( RTLD_DEFAULT, "posix_memalign" );
+
+    update_metadata( NULL );
+
+    _capsule_original_dlsym = dlsym( RTLD_DEFAULT, "dlsym"  );
+    _capsule_original_dlopen = dlsym( RTLD_DEFAULT, "dlopen" );
+
+    if( !(debug_flags & DEBUG_CAPSULE) )
+        return;
+
+    for( unsigned int x = 0; x < _capsule_list->next; x++ )
+    {
+        capsule cap = _capsule_list->loc[ x ].ptr;
+
+        if( !cap )
+            continue;
+
+        const char **soname = cap->meta->exclude;
+
+        DEBUG( DEBUG_CAPSULE, "[%02d] %s metadata:\n", x, cap->meta->soname );
+
+        while( soname && *soname )
+            fprintf( stderr, "    %s\n", *(soname++) );
+    }
+}
+
+char *
+capsule_get_prefix (const char *dflt, const char *soname)
+{
+    const char *prefix = get_prefix_nocopy( dflt, soname );
+
+    if (prefix != NULL)
+        return xstrdup( prefix );
+
+    return NULL;
+}
+
+static const char *
+get_prefix_nocopy (const char *dflt, const char *soname)
+{
+#ifdef HAVE_SECURE_GETENV
+    char env_var[PATH_MAX] = CAP_ENV_PREFIX;
+    const size_t offs = strlen( CAP_ENV_PREFIX );
+    size_t x = 0;
+    const char *prefix = NULL;
+
+    for( ; (x < strlen(soname)) && (x + offs < PATH_MAX); x++ )
+    {
+        char c = *(soname + x);
+
+        env_var[ x + offs ] = isalnum( c ) ? toupper( c ) : '_';
+    }
+
+    safe_strncpy( &env_var[ x + offs ], "_PREFIX", PATH_MAX - (x + offs + 1) );
+    env_var[ PATH_MAX - 1 ] = '\0';
+
+    DEBUG( DEBUG_CAPSULE, "checking %s\n", &env_var[0] );
+    if( (prefix = secure_getenv( &env_var[0] )) )
+    {
+        DEBUG( DEBUG_SEARCH, "Capsule prefix is %s: %s", &env_var[0], prefix );
+        return prefix;
+    }
+
+    DEBUG( DEBUG_CAPSULE, "checking %s\n", CAP_ENV_PREFIX "PREFIX" );
+    if( (prefix = secure_getenv( CAP_ENV_PREFIX "PREFIX" )) )
+    {
+        DEBUG( DEBUG_SEARCH, "Capsule prefix is "CAP_ENV_PREFIX"PREFIX: %s",
+               prefix );
+        return prefix;
+    }
+#endif
+
+    if( dflt )
+    {
+        DEBUG( DEBUG_SEARCH, "Capsule prefix is built-in: %s", dflt );
+        return dflt;
+    }
+
+    DEBUG( DEBUG_SEARCH, "Capsule prefix is missing" );
+    return NULL;
+}
+
+static capsule
+get_capsule_by_soname (const char *soname)
+{
+    for( size_t n = 0; n < _capsule_list->next; n++ )
+    {
+        capsule cap = ptr_list_nth_ptr( _capsule_list, n );
+
+        if( !cap || strcmp( cap->meta->soname, soname ) )
+            continue;
+
+        DUMP_CAPSULE(n, cap);
+        return cap;
+    }
+
+    return NULL;
+}
+
+capsule
+capsule_init (const char *soname)
+{
+    capsule cap;
+    void *dso;
+    int   capsule_errno = 0;
+    char *capsule_error = NULL;
+
+    DEBUG( DEBUG_CAPSULE, "Initializing shim library %s", soname );
+
+    cap = get_capsule_by_soname( soname );
+
+    if( !cap )
+    {
+        DEBUG( DEBUG_CAPSULE, "no metadata for %s registered: "
+               "may be a dlopened capsule", soname );
+        DEBUG( DEBUG_CAPSULE, "updating capsule metadata" );
+        update_metadata( soname );
+        cap = get_capsule_by_soname( soname );
+    }
+
+    if( !cap )
+    {
+        fprintf( stderr,
+                 "libcapsule: %s: Fatal error: cannot initialize shim "
+                 "library (capsule_meta not found)\n",
+                 soname );
+        abort();
+    }
+
+    for( size_t i = 0; i < _capsule_list->next; i++ )
+    {
+        capsule other = ptr_list_nth_ptr( _capsule_list, i );
+
+        if( !other )
+            continue;
+
+        DEBUG( DEBUG_CAPSULE, " ");
+        DUMP_CAPSULE( i, other );
+        DUMP_STRV( excluded, other->ns->combined_exclude );
+        DUMP_STRV( exported, other->ns->combined_export  );
+    }
+
+    dso = _capsule_load( cap, cap->internal_wrappers,
+                         &capsule_errno, &capsule_error );
+
+    if( !dso )
+    {
+        fprintf( stderr, "libcapsule: fatal error: %s: %s\n",
+                 soname, capsule_error );
+        abort();
+    }
+
+    int rloc = _capsule_relocate( cap, &capsule_error );
+
+    if( rloc != 0 ) // relocation failed. we're dead.
+    {
+        fprintf( stderr,
+                 "libcapsule: fatal error: %s could not install "
+                 "relocations\n",
+                 soname );
+        abort();
+    }
+
+    rloc = _capsule_relocate_dlopen( cap, &capsule_error );
+    if( rloc != 0 ) // we may survive this depending on how dlopen is used
+       fprintf( stderr,
+                "libcapsule: warning: %s could not install dlopen() "
+                "dlopen wrappers. This error may or may not be "
+                "fatal later.\n",
+                soname );
+
+    return cap;
+}
+
+#define CLEAR(f,x) ({ f( x ); x = NULL; })
+
+void
+capsule_close (capsule cap)
+{
+    capsule_metadata *meta = cap->meta;
+
+    DEBUG( DEBUG_CAPSULE, "Uninitializing shim library %s", meta->soname );
+
+    // scrub all entries in the manifest pointing to this metadata
+    for( size_t n = 0; n < _capsule_list->next; n++ )
+    {
+        capsule other = ptr_list_nth_ptr( _capsule_list, n );
+
+        if( other == cap )
+        {
+            _capsule_list->loc[ n ].ptr = NULL;
+        }
+        else if( other != NULL )
+        {
+            // There should only be one capsule per capsule_metadata
+            assert( other->meta != meta );
+        }
+    }
+
+    // Remove any pointers from the namespaces into this capsule
+    update_namespaces();
+
+    meta->handle = NULL;
+
+    CLEAR( ptr_list_free, cap->seen.all  );
+    CLEAR( ptr_list_free, cap->seen.some );
+
+    // poison the capsule struct and free it
+    memset( cap, 'X', sizeof(struct _capsule) );
+    free( cap );
+}
diff --git a/subprojects/libcapsule/capsule/capsule-malloc.h b/subprojects/libcapsule/capsule/capsule-malloc.h
new file mode 100644
index 0000000000000000000000000000000000000000..9a760376b73fce547ea21a807863f69921a1e6fc
--- /dev/null
+++ b/subprojects/libcapsule/capsule/capsule-malloc.h
@@ -0,0 +1,208 @@
+#pragma once
+
+#define CAPSULE_MALLOC_EXTRA_CHECKS
+
+#define DEBUG_MALLOC_VOODOO 0
+
+#if DEBUG_MALLOC_VOODOO
+#include <stdio.h>
+#include <sys/param.h>
+// can't printf inside malloc/free et al
+// can't jump to an fputs from a different libc either
+typedef int (*fputsfunc) (const char *buf, FILE *s);
+static fputsfunc wf = NULL;
+
+#define CAN_DEBUG_ALLOCS (wf != NULL)
+
+static void dump_ptr (const char *label, const void *ptr)
+{
+    char ptrbuf[ (sizeof(void *) * 2) + 1 ];
+    unsigned long x = (unsigned long) ptr;
+    static const char pattern[] = "0123456789abcdef";
+    ssize_t start = sizeof(long) * 2;
+
+    ptrbuf[ sizeof(ptrbuf) - 1 ] = '\0';
+
+    for( size_t i = 0; i < sizeof(long) * 2; i++ )
+    {
+        size_t c = (x >> ((((sizeof(long) * 2) - 1) - i) * 4)) & 0b01111;
+        ptrbuf[ i ] = pattern[ c ];
+        if( (start == (sizeof(long) * 2)) && (ptrbuf[i] != '0') )
+            start = i;
+    }
+
+    start = MAX( 0, start - 1 );
+
+    wf( label  , stderr );
+    wf( ": <0x", stderr );
+    wf( ptrbuf + start, stderr );
+    wf( ">\n"  , stderr );
+}
+#define FETCH_FPUTS() \
+    if(UNLIKELY(!wf)) wf = (fputsfunc) dlsym(cap->dl_handle, "fputs")
+#else
+#define dump_ptr(a,b)
+#define FETCH_FPUTS()
+#define CAN_DEBUG_ALLOCS 0
+#endif
+
+
+////////////////////////////////////////////////////////////////////////////
+// copy some vodoo out of libc.
+// it is to be hoped that this is a temporary hack but, well…
+
+#define SIZE_SZ (sizeof(size_t))
+
+struct malloc_chunk
+{
+  size_t               prev_size;  /* Size of previous chunk (if free).  */
+  size_t               size;       /* Size in bytes, including overhead. */
+
+  struct malloc_chunk* fd;         /* double links -- used only if free. */
+  struct malloc_chunk* bk;
+
+  struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */
+  struct malloc_chunk* bk_nextsize;
+};
+
+typedef struct malloc_chunk* mchunkptr;
+
+#define chunk2mem(p)   ((void*)((char*)(p) + 2*SIZE_SZ))
+#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ))
+
+/* size field is or'ed with IS_MMAPPED if the chunk was obtained with mmap() */
+#define IS_MMAPPED     0x2
+
+#define chunk_is_mmapped(p)     ((p)->size & IS_MMAPPED)
+
+////////////////////////////////////////////////////////////////////////////
+// extra voodoo. not clear if we need this:
+#ifdef CAPSULE_MALLOC_EXTRA_CHECKS
+
+#define PREV_INUSE     0x1
+/* similarly for non-main-arena chunks */
+#define NON_MAIN_ARENA 0x4
+
+#define chunk_non_main_arena(p) ((p)->size & NON_MAIN_ARENA)
+
+typedef struct malloc_chunk *mfastbinptr;
+typedef int mutex_t;
+
+#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])
+/* offset 2 to use otherwise unindexable first 2 bins */
+#define fastbin_index(sz) \
+  ((((unsigned int) (sz)) >> (SIZE_SZ == 8 ? 4 : 3)) - 2)
+/* The maximum fastbin request size we support */
+#define MAX_FAST_SIZE  (80 * SIZE_SZ / 4)
+
+#define MALLOC_ALIGNMENT  (2 *SIZE_SZ < __alignof__ (long double) ? \
+                           __alignof__ (long double) : 2 *SIZE_SZ)
+#define MALLOC_ALIGN_MASK (MALLOC_ALIGNMENT - 1)
+#define MIN_CHUNK_SIZE    (offsetof(struct malloc_chunk, fd_nextsize))
+/* The smallest size we can malloc is an aligned minimal chunk */
+#define MINSIZE  \
+  (unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))
+
+#define request2size(req)                                         \
+  (((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE)  ?             \
+   MINSIZE :                                                      \
+   ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)
+
+#define NFASTBINS   (fastbin_index (request2size (MAX_FAST_SIZE)) + 1)
+#define NBINS       128
+#define BINMAPSHIFT 5
+#define BITSPERMAP  (1U << BINMAPSHIFT)
+#define BINMAPSIZE  (NBINS / BITSPERMAP)
+
+struct malloc_state
+{
+  /* Serialize access.  */
+  mutex_t mutex;
+
+  /* Flags (formerly in max_fast).  */
+  int flags;
+
+  /* Fastbins */
+  mfastbinptr fastbinsY[NFASTBINS];
+
+  /* Base of the topmost chunk -- not otherwise kept in a bin */
+  mchunkptr top;
+
+  /* The remainder from the most recent split of a small request */
+  mchunkptr last_remainder;
+
+  /* Normal bins packed as described above */
+  mchunkptr bins[NBINS * 2 - 2];
+
+  /* Bitmap of bins */
+  unsigned int binmap[BINMAPSIZE];
+
+  /* Linked list */
+  struct malloc_state *next;
+
+  /* Linked list for free arenas.  Access to this field is serialized
+     by free_list_lock in arena.c.  */
+  struct malloc_state *next_free;
+
+  /* Number of threads attached to this arena.  0 if the arena is on
+     the free list.  Access to this field is serialized by
+     free_list_lock in arena.c.  */
+  size_t attached_threads;
+
+  /* Memory allocated from the system in this arena.  */
+  size_t system_mem;
+  size_t max_system_mem;
+};
+
+typedef struct malloc_state *mstate;
+
+typedef struct _heap_info
+{
+  mstate ar_ptr; /* Arena for this heap. */
+  struct _heap_info *prev; /* Previous heap. */
+  size_t size;   /* Current size in bytes. */
+  size_t mprotect_size; /* Size in bytes that has been mprotected
+                           PROT_READ|PROT_WRITE.  */
+  /* Make sure the following data is properly aligned, particularly
+     that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
+     MALLOC_ALIGNMENT. */
+  char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
+} heap_info;
+
+#ifndef DEFAULT_MMAP_THRESHOLD_MAX
+  /* For 32-bit platforms we cannot increase the maximum mmap
+     threshold much because it is also the minimum value for the
+     maximum heap size and its alignment.  Going above 512k (i.e., 1M
+     for new heaps) wastes too much address space.  */
+# if __WORDSIZE == 32
+#  define DEFAULT_MMAP_THRESHOLD_MAX (512 * 1024)
+# else
+#  define DEFAULT_MMAP_THRESHOLD_MAX (4 * 1024 * 1024 * sizeof(long))
+# endif
+#endif
+
+#define HEAP_MIN_SIZE (32 * 1024)
+#ifndef HEAP_MAX_SIZE
+# ifdef DEFAULT_MMAP_THRESHOLD_MAX
+#  define HEAP_MAX_SIZE (2 * DEFAULT_MMAP_THRESHOLD_MAX)
+# else
+#  define HEAP_MAX_SIZE (1024 * 1024)
+# endif
+#endif
+
+#define heap_for_ptr(ptr) \
+  ((heap_info *) ((unsigned long) (ptr) & ~(HEAP_MAX_SIZE - 1)))
+#define arena_for_chunk(ptr) \
+  (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : NULL)
+
+#define chunk_at_offset(p, s)  ((mchunkptr) (((char *) (p)) + (s)))
+
+#define NONCONTIGUOUS_BIT (2U)
+#define contiguous(M)     (((M)->flags & NONCONTIGUOUS_BIT) == 0)
+#define noncontiguous(M)  (((M)->flags & NONCONTIGUOUS_BIT) != 0)
+
+#define SIZE_BITS    (PREV_INUSE | IS_MMAPPED | NON_MAIN_ARENA)
+#define chunksize(p) ((p)->size & ~(SIZE_BITS))
+
+#endif // CAPSULE_MALLOC_EXTRA_CHECKS
+////////////////////////////////////////////////////////////////////////////
diff --git a/subprojects/libcapsule/capsule/capsule-private.h b/subprojects/libcapsule/capsule/capsule-private.h
new file mode 100644
index 0000000000000000000000000000000000000000..f4cbda6b2c9512325fbd6a0b5025695c01d38ff3
--- /dev/null
+++ b/subprojects/libcapsule/capsule/capsule-private.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include <link.h>
+#include "utils/utils.h"
+
+typedef void * (*dlsymfunc) (void *handle, const char *symbol);
+typedef void * (*dlopnfunc) (const char *file, int flags);
+
+// we need these to transplant the *alloc/free cluster into
+// the capsule so that the memory allocation implementation
+// is unified (at least until we can force libc to be shared):
+typedef void   (*freefunc)   (void *ptr);
+typedef void * (*mallocfunc) (size_t size);
+typedef void * (*callocfunc) (size_t nmem, size_t size);
+typedef void * (*rallocfunc) (void *ptr, size_t size);
+typedef int    (*palignfunc) (void **memptr, size_t alignment, size_t size);
+
+typedef struct _capsule_memory
+{
+    freefunc    free;
+    rallocfunc  realloc;
+    mallocfunc  malloc;           // UNUSED
+    callocfunc  calloc;           // UNUSED
+    palignfunc  posix_memalign;   // UNUSED
+} capsule_memory;
+
+typedef struct _capsule_namespace
+{
+    Lmid_t      ns;               // dlmopen namespace. LM_ID_NEWLM to create
+    const char *prefix;           // default library tree prefix, eg /host
+    ptr_list   *exclusions;       // sonames to ignore
+    ptr_list   *exports;          // sonames to expose/export
+    char      **combined_exclude; // combined exclude & export lists from all
+    char      **combined_export;  // capsules DSOs sharing the same namespace
+    capsule_memory *mem;
+} capsule_namespace;
+
+struct _capsule
+{
+    void  *dl_handle;
+    struct { ptr_list *all; ptr_list *some; } seen;
+    capsule_metadata *meta;
+    capsule_namespace *ns;
+    capsule_item internal_wrappers[7];
+};
+
+extern ptr_list *_capsule_list;
+extern dlsymfunc _capsule_original_dlsym;
+extern dlopnfunc _capsule_original_dlopen;
+
+extern freefunc   _capsule_original_free;
+extern callocfunc _capsule_original_calloc;
+extern mallocfunc _capsule_original_malloc;
+extern rallocfunc _capsule_original_realloc;
+extern palignfunc _capsule_original_pmalign;
+
+/*
+ * _capsule_load:
+ * @capsule: a #capsule handle as returned by capsule_init()
+ * @dso: The name of the DSO to open (cf dlopen()) - eg libGL.so.1
+ * @namespace: (out) Address of an #Lmid_t value.
+ * @wrappers: Array of #capsule_item used to replace symbols in the namespace
+ * @errcode: (out): location in which to store the error code (errno) on failure
+ * @error: (out) (transfer full) (optional): location in which to store
+ *         an error message on failure, or %NULL to ignore.
+ *         Free with free().
+ *
+ * Returns: A (void *) DSO handle, as per dlopen(), or %NULL on error
+ *
+ * Opens @dso (a library) from a filesystem mounted at @prefix into a
+ * symbol namespace specified by @namespace, using dlmopen().
+ *
+ * Any symbols specified in @wrappers will be replaced with the
+ * corresponding address from @wrappers (allowing you to replace
+ * function definitions inside the namespace with your own).
+ *
+ * This is normally used to replace calls inside the namespace to
+ * certain functions (such as dlopen()) which need to be overridden
+ * to operate correctly inside a private namespace associated with
+ * a nonstandard filsystem tree.
+ *
+ * The #Lmid_t value stored in @namespace gives the symbol namespace
+ * into which the target DSO was loaded. It should normally be #LM_ID_NEWLM.
+ *
+ * In addition to a bare libFOO.so.X style name, @dso may be an
+ * absolute path (or even a relative one) and in those cases should
+ * have the same effect as passing those values to dlopen(). This is
+ * not a normal use case and has not been heavily tested.
+ *
+ */
+void *_capsule_load (const capsule capsule,
+                     capsule_item *wrappers,
+                     int *errcode,
+                     char **error);
+
+/*
+ * _capsule_relocate:
+ * @capsule: a #capsule handle as returned by capsule_init()
+ * @error: (out) (transfer full) (optional): location in which to store
+ *         an error message on failure, or %NULL to ignore.
+ *         Free with free().
+ *
+ * Returns: 0 on success, non-zero on failure.
+ *
+ * This function updates the GOT entries in all DSOs outside the capsule
+ * so that when they call any function listed in the @items of the
+ * #capsule_metadata, they invoke the copy of that function inside the capsule.
+ *
+ * In the unlikely event that an error message is returned in @error it is the
+ * caller's responsibility to free() it.
+ */
+int _capsule_relocate (const capsule capsule,
+                       char **error);
+
+/*
+ * _capsule_relocate_dlopen:
+ * @capsule: a #capsule handle as returned by capsule_init()
+ * @error: (out) (transfer full) (optional): location in which to store
+ *         an error message on failure, or %NULL to ignore.
+ *         Free with free().
+ *
+ * Returns: 0 on success, non-zero on failure.
+ *
+ * This function updates the GOT entries in all DSOs outside the capsule
+ * _except_ those listed in @except: When they call dlopen(), instead
+ * they invoke the copy of that function provided by libcapsule.
+ *
+ * In the unlikely event that an error message is returned in @error it is the
+ * caller's responsibility to free() it.
+ */
+int _capsule_relocate_dlopen (const capsule capsule,
+                              char **error);
diff --git a/subprojects/libcapsule/capsule/capsule-relocate.c b/subprojects/libcapsule/capsule/capsule-relocate.c
new file mode 100644
index 0000000000000000000000000000000000000000..513178dda980154140a2d058833f9a09be279328
--- /dev/null
+++ b/subprojects/libcapsule/capsule/capsule-relocate.c
@@ -0,0 +1,252 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <capsule/capsule.h>
+#include "capsule/capsule-private.h"
+
+#include "utils/dump.h"
+#include "utils/utils.h"
+#include "utils/process-pt-dynamic.h"
+
+// ==========================================================================
+// some entries require us to peer into others to make sense of them:
+// can't make full sense of relocations without looking names up
+// in the stringtab, which does not have to occur at any fixed point in
+// in the PT_DYNAMIC entry.
+// IOW PT_DYNAMIC contains both relocations (DT_RELA, DT_REL) and a stringtab
+// (DT_STRTAB) in arbitrary order but the former do not make sense without
+// the latter.
+
+
+// =========================================================================
+
+/*
+ * process_phdr:
+ * @info: struct dl_phdr_info describing the shared object
+ * @size: sizeof(info)
+ * @rdata: the closure that we passed to dl_iterate_phdr()
+ *
+ * Callback called for each shared object loaded into the program.
+ */
+static int
+process_phdr (struct dl_phdr_info *info,
+              size_t size,
+              relocation_data *rdata)
+{
+    int ret = 0;
+
+    for( int j = 0; !ret && (j < info->dlpi_phnum); j++ )
+    {
+        if( info->dlpi_phdr[j].p_type == PT_DYNAMIC )
+        {
+            ret = process_pt_dynamic( info->dlpi_phdr[j].p_vaddr,
+                                      info->dlpi_phdr[j].p_memsz,
+                                      (void *) info->dlpi_addr,
+                                      process_dt_rela,
+                                      process_dt_rel,
+                                      rdata );
+        }
+    }
+
+    if( ret == 0 && rdata->seen != NULL )
+        ptr_list_push_addr( rdata->seen, info->dlpi_addr );
+
+    return ret;
+}
+
+static int
+dso_is_blacklisted (const char *path, relocation_flags flags)
+{
+    const char * const *soname;
+    const char * const libc[] =
+    {
+        "libc.so",
+        "libdl.so",
+        "libpthread.so",
+        NULL
+    };
+    static const char *never = "libcapsule.so";
+
+    if( soname_matches_path( never, path ) )
+        return 1;
+
+    if( flags & RELOCATION_FLAGS_AVOID_LIBC )
+        for( soname = libc; soname && *soname; soname++ )
+            if( soname_matches_path( *soname, path ) )
+                return 1;
+
+    return 0;
+}
+
+static int
+dso_has_been_relocated (ptr_list *seen, ElfW(Addr) base)
+{
+    if( seen == NULL )
+        return 0;
+
+    if( ptr_list_contains( seen, base ) )
+        return 1;
+
+    return 0;
+}
+
+// first level of the callback: all we're doing here is skipping over
+// any program headers that (for whatever reason) we decide we're not
+// interested in.
+// In practice we have to handle all existing DSOs, as any of them may
+// call into the library we are acting as a shim for.
+static int
+relocate_cb (struct dl_phdr_info *info, size_t size, void *data)
+{
+    relocation_data *rdata = data;
+    const char *dso_path = *info->dlpi_name ? info->dlpi_name : "-elf-";
+
+    if( dso_is_blacklisted( dso_path, rdata->flags ) )
+    {
+        DEBUG( DEBUG_RELOCS, "skipping %s %p (blacklisted)",
+               dso_path, (void *) info->dlpi_addr );
+        return 0;
+    }
+
+    if( dso_has_been_relocated( rdata->seen, info->dlpi_addr ) )
+    {
+        DEBUG( DEBUG_RELOCS, "skipping %s %p (already relocated)",
+               dso_path, (void *) info->dlpi_addr );
+        return 0;
+    }
+
+    DEBUG( DEBUG_RELOCS, "processing %s %p",
+           dso_path, (void *) info->dlpi_addr );
+
+    return process_phdr( info, size, rdata );
+}
+
+static int relocate (const capsule cap,
+                     capsule_item *relocations,
+                     relocation_flags flags,
+                     ptr_list *seen,
+                     char **error)
+{
+    relocation_data rdata = { 0 };
+    capsule_item *map;
+    int mmap_errno = 0;
+    const char *mmap_error = NULL;
+    int rval = 0;
+
+    // load the relevant metadata into the callback argument:
+    rdata.debug     = debug_flags;
+    rdata.error     = NULL;
+    rdata.flags     = flags;
+    rdata.mmap_info = load_mmap_info( &mmap_errno, &mmap_error );
+    rdata.relocs    = relocations;
+    rdata.seen      = seen;
+
+    if( mmap_errno || mmap_error )
+    {
+        DEBUG( DEBUG_RELOCS|DEBUG_MPROTECT,
+               "mmap/mprotect flags information load error (errno: %d): %s\n",
+               mmap_errno, mmap_error );
+        DEBUG( DEBUG_RELOCS|DEBUG_MPROTECT,
+               "relocation will be unable to handle relro linked libraries" );
+    }
+
+    // no source dl handle means we must have a pre-populated
+    // map of shim-to-real function pointers in `relocations',
+    // otherwise populate the map using [the real] dlsym():
+    if( cap->dl_handle )
+        for( map = cap->meta->items; map->name; map++ )
+        {
+            if( !map->shim )
+                map->shim = (ElfW(Addr)) _capsule_original_dlsym( RTLD_DEFAULT, map->name );
+
+            if( !map->real )
+                map->real = (ElfW(Addr)) _capsule_original_dlsym( cap->dl_handle, map->name );
+        }
+
+    // time to enter some sort of ... dangerous... zone:
+    // we need the mmap()ed DSO regions to have the PROT_WRITE
+    // flag set, so that if they've been RELRO linked we can still
+    // overwrite their GOT entries.
+    for( int i = 0; rdata.mmap_info[i].start != MAP_FAILED; i++ )
+        if( mmap_entry_should_be_writable( &rdata.mmap_info[i] ) )
+            add_mmap_protection( &rdata.mmap_info[i], PROT_WRITE );
+
+    dl_iterate_phdr( relocate_cb, &rdata );
+
+    // and now we put those PROT_WRITE permissions back the way they were:
+    for( int i = 0; rdata.mmap_info[i].start != MAP_FAILED; i++ )
+        if( mmap_entry_should_be_writable( &rdata.mmap_info[i] ) )
+            reset_mmap_protection( &rdata.mmap_info[i] );
+
+    if( rdata.error )
+    {
+        if( error )
+            *error = rdata.error;
+        else
+            free( rdata.error );
+
+        rval = (rdata.count.failure == 0) ? -1 : rdata.count.failure;
+    }
+
+    free_mmap_info( rdata.mmap_info );
+    rdata.mmap_info = NULL;
+
+    return rval;
+}
+
+int
+_capsule_relocate (const capsule cap, char **error)
+{
+    DEBUG( DEBUG_RELOCS, "beginning global symbol relocation:" );
+    return relocate( cap, cap->meta->items, RELOCATION_FLAGS_NONE, cap->seen.all, error );
+}
+
+static capsule_item capsule_external_dl_relocs[] =
+{
+  { "dlopen",
+    (capsule_addr) capsule_external_dlopen ,
+    (capsule_addr) capsule_external_dlopen },
+  { NULL }
+};
+
+int
+_capsule_relocate_dlopen (const capsule cap,
+                          char **error)
+{
+    unsigned long df = debug_flags;
+
+    if( debug_flags & DEBUG_DLFUNC )
+        debug_flags |= DEBUG_RELOCS;
+
+    DEBUG( DEBUG_RELOCS, "beginning restricted symbol relocation:" );
+    int rv = relocate( cap, capsule_external_dl_relocs,
+                       RELOCATION_FLAGS_AVOID_LIBC, cap->seen.some, error );
+
+    debug_flags = df;
+
+    return rv;
+}
diff --git a/subprojects/libcapsule/capsule/capsule-shim.h b/subprojects/libcapsule/capsule/capsule-shim.h
new file mode 100644
index 0000000000000000000000000000000000000000..7b6eb7941f089544e33e970538154e565f54a11f
--- /dev/null
+++ b/subprojects/libcapsule/capsule/capsule-shim.h
@@ -0,0 +1,59 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <execinfo.h>
+
+#include <capsule/capsule.h>
+
+#define UNVERSIONED_STUB(name) \
+    void name (void) __attribute__((noreturn,visibility("default"))); \
+    void name (void)                                   \
+    {                                                  \
+        fprintf(stderr, "! SHIM " #name " called\n" ); \
+        backtrace_shim_call();                         \
+        abort();                                       \
+    }
+
+#define VERSIONED_STUB(name,version) \
+    UNVERSIONED_STUB(name);
+
+static void
+backtrace_shim_call (void)
+{
+    void *trace[16] = { NULL };
+    int traced = 0;
+    char **symbols = NULL;
+
+    traced  = backtrace( trace, sizeof(trace)/sizeof(void *) );
+    symbols = backtrace_symbols( trace, traced );
+
+    for( int x = 1; x < traced; x++ )
+        fprintf( stderr, "  -> %s\n", symbols[x] );
+
+    free( symbols );
+}
+
+// We don't support versioned symbols properly yet, they need som
+// asm magic that looks like this and I'm not clear on the details:
+// __asm__(".symver _" #name "," #name #version)
+
diff --git a/subprojects/libcapsule/capsule/capsule-wrappers.c b/subprojects/libcapsule/capsule/capsule-wrappers.c
new file mode 100644
index 0000000000000000000000000000000000000000..320829f70c5532fc684ed0c23d6713958baf3fb4
--- /dev/null
+++ b/subprojects/libcapsule/capsule/capsule-wrappers.c
@@ -0,0 +1,373 @@
+#include "capsule/capsule.h"
+#include "capsule/capsule-private.h"
+#include "capsule/capsule-malloc.h"
+#include "utils/utils.h"
+#include "utils/ld-libs.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <malloc.h>
+
+static int
+dso_is_exported (const char *dsopath, char **exported)
+{
+    for( char **ex = exported; ex && *ex; ex++ )
+        if( soname_matches_path( *ex, dsopath ) )
+            return 1;
+
+    return 0;
+}
+
+static void *
+_dlsym_from_capsules (const char *symbol)
+{
+    void *addr = NULL;
+
+    for( size_t n = 0; n < _capsule_list->next; n++ )
+    {
+        capsule cap = ptr_list_nth_ptr( _capsule_list, n );
+
+        if( !cap )
+            continue;
+
+        // TODO: If handle != cap->dl_handle, should we skip it?
+        // TODO: RTLD_NEXT isn't implemented (is it implementable?)
+        addr = _capsule_original_dlsym ( cap->dl_handle, symbol );
+
+        if( addr )
+        {
+            Dl_info dso = { 0 };
+
+            // only keep addr from the capsule if it's from an exported DSO:
+            // or if we are unable to determine where it came from (what?)
+            if( dladdr( addr, &dso ) )
+            {
+                if( !dso_is_exported( dso.dli_fname, cap->ns->combined_export ) )
+                    addr = NULL;
+
+                DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS,
+                       "symbol %s is from soname %s - %s",
+                       symbol, dso.dli_fname, addr ? "OK" : "Ignored" );
+
+                if( addr )
+                    break;
+            }
+        }
+    }
+
+    return addr;
+}
+
+static int
+_dlsymbol_is_encapsulated (const void *addr)
+{
+    Dl_info dso = { 0 };
+
+    // no info, symbol may not even be valid:
+    if( !dladdr( addr, &dso ) )
+        return 0;
+
+    // no file name, can't be a shim:
+    if( !dso.dli_fname || *dso.dli_fname == '\0' )
+        return 0;
+
+    // check to see if addr came from a registered capsule:
+    for( size_t n = 0; n < _capsule_list->next; n++ )
+    {
+        capsule cap = ptr_list_nth_ptr( _capsule_list, n );
+
+        if( cap && soname_matches_path( cap->meta->soname, dso.dli_fname) )
+            return 1;
+    }
+
+    return 0;
+}
+
+// TODO: Implement dlvsym()?
+// TODO: RTLD_NEXT needs special handling
+
+// revised algorithm here:
+//
+// use the vanilla dlsym
+// if nothing is found, peek into the whole capsule; return the result
+//
+// if a symbol is found, check to see if it came from a shim
+// if it did (ie it is a dummy), peek into the capsule as above
+// if it did not, return what was found
+//
+// The main weakness here is that if the caller expects to find a
+// symbol XYZ via ‘handle’ which does _not_ come from the capsule
+// but the capsule also has a symbol XYZ which is from an explicitly
+// exported-from soname then the caller will get the capsule's
+// XYZ symbol.
+//
+// We can't just check for RTLD_DEFAULT as the handle since
+// dlopen( NULL, … ) and/or the RTLD_GLOBAL flag can be used to
+// promote symbols that would otherwise not be visible from a given
+// handle (libGL does this).
+void *
+capsule_external_dlsym (void *handle, const char *symbol)
+{
+    DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS, "dlsym(%s)", symbol );
+    void *addr = _capsule_original_dlsym ( handle, symbol );
+
+    // nothing found, must be from a capsule or nowhere at all:
+    if( !addr )
+    {
+        DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS,
+               "%s not found, searching capsule", symbol );
+        addr = _dlsym_from_capsules( symbol );
+        DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS,
+               "capsule %s has address %p", symbol, addr );
+        return addr;
+    }
+
+    // found something. is it a dummy symbol from a shim?
+    if( _dlsymbol_is_encapsulated( addr ) )
+    {
+        DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS,
+               "dummy %s found, searching capsule", symbol );
+        addr = _dlsym_from_capsules( symbol );
+        DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS,
+               "capsule %s has address %p", symbol, addr );
+        return addr;
+    }
+
+    DEBUG( DEBUG_DLFUNC|DEBUG_WRAPPERS,
+           "vanilla %s found at %p", symbol, addr );
+    return addr;
+}
+
+void *
+capsule_external_dlopen(const char *file, int flag)
+{
+    void *handle = NULL;
+    char *error  = NULL;
+
+    if( _capsule_original_dlopen )
+    {
+        handle = _capsule_original_dlopen ( file, flag );
+    }
+    else
+    {
+        fprintf( stderr,
+                 "capsule_external_dlopen() has no dlopen() implementation\n" );
+        abort();
+    }
+
+    if( handle != NULL )
+    {
+        unsigned long df = debug_flags;
+
+        if( debug_flags & DEBUG_DLFUNC )
+            debug_flags |= DEBUG_RELOCS;
+        // This may not even be necessary, so it should not be fatal.
+        // We do want to log it though as it might be an important clue:
+        for( size_t n = 0; n < _capsule_list->next; n++ )
+        {
+            const capsule c = ptr_list_nth_ptr( _capsule_list, n );
+
+            if( !c )
+                continue;
+
+            if( _capsule_relocate( c, &error ) != 0 )
+            {
+                fprintf( stderr,
+                         "relocation from %s after dlopen(%s, …) failed: %s\n",
+                         c->meta->soname, file, error );
+                free( error );
+            }
+
+            if( _capsule_relocate_dlopen( c, &error ) != 0 )
+            {
+                fprintf( stderr,
+                         "dl-wrapper relocation from %s after "
+                         "dlopen(%s, …) failed: %s\n",
+                         c->meta->soname, file, error );
+                free( error );
+            }
+        }
+
+        debug_flags = df;
+    }
+
+    return handle;
+}
+
+void *
+capsule_shim_dlopen(const capsule cap, const char *file, int flag)
+{
+    void *res = NULL;
+    int code = 0;
+    char *errors = NULL;
+    ld_libs ldlibs = {};
+
+    DEBUG( DEBUG_WRAPPERS|DEBUG_DLFUNC,
+           "dlopen(%s, %x) wrapper: LMID: %ld; prefix: %s;",
+           file, flag, cap->ns->ns, cap->ns->prefix );
+
+    if( cap->ns->prefix && strcmp(cap->ns->prefix, "/") )
+    {
+        if( !ld_libs_init( &ldlibs, (const char **)cap->ns->combined_exclude,
+                           cap->ns->prefix, debug_flags, &code, &errors ) )
+        {
+            DEBUG( DEBUG_LDCACHE|DEBUG_WRAPPERS|DEBUG_DLFUNC,
+                   "Initialising ld_libs data failed: error %d: %s",
+                   code, errors);
+              goto cleanup;
+        }
+
+        if( !ld_libs_load_cache( &ldlibs, &code, &errors ) )
+        {
+            DEBUG( DEBUG_LDCACHE|DEBUG_WRAPPERS|DEBUG_DLFUNC,
+                   "Loading ld.so.cache from %s: error %d: %s", cap->ns->prefix,
+                   code, errors );
+            goto cleanup;
+        }
+
+        // find the initial DSO (ie what the caller actually asked for):
+        if( !ld_libs_set_target( &ldlibs, file, &code, &errors ) )
+        {
+            DEBUG( DEBUG_SEARCH|DEBUG_WRAPPERS|DEBUG_DLFUNC,
+                           "Not found: %s under %s: error %d: %s",
+                           file, cap->ns->prefix, code, errors );
+            goto cleanup;
+        }
+
+        // harvest all the requested DSO's dependencies:
+        if( !ld_libs_find_dependencies( &ldlibs, &code, &errors ) )
+        {
+            DEBUG( DEBUG_WRAPPERS|DEBUG_DLFUNC,
+                   "capsule dlopen error %d: %s", code, errors );
+            goto cleanup;
+        }
+
+        // load them up in reverse dependency order:
+        res = ld_libs_load( &ldlibs, &cap->ns->ns, flag, &code, &errors );
+
+        if( !res )
+            DEBUG( DEBUG_WRAPPERS|DEBUG_DLFUNC,
+                   "capsule dlopen error %d: %s", code, errors );
+
+        goto cleanup;
+    }
+    else // no prefix: straightforward dlmopen into our capsule namespace:
+    {
+        res = dlmopen( cap->ns->ns, file, flag );
+
+        if( !res )
+            DEBUG( DEBUG_WRAPPERS|DEBUG_DLFUNC,
+                   "capsule dlopen error %s: %s", file, dlerror() );
+    }
+
+    return res;
+
+cleanup:
+    ld_libs_finish( &ldlibs );
+    free( errors );
+    return res;
+}
+
+#ifdef CAPSULE_MALLOC_EXTRA_CHECKS
+static inline int chunk_is_vanilla (mchunkptr p, void *ptr)
+{
+    mstate av = arena_for_chunk (p);
+
+    // arena_for_chunk can't find the main arena... but if this poiner
+    // is from the _main_ main arena then I think it would have been
+    // trapped by the heap check in capsule_shim_free already, so
+    // this did not come from the main instance of libc:
+    if( LIKELY(!av) )
+        return 0;
+
+    size_t size = chunksize (p);
+    mchunkptr nextchunk = chunk_at_offset(p, size);
+
+    // invalid next size (fast)
+    if( UNLIKELY( nextchunk->size        <= 2 * SIZE_SZ   ) ||
+        UNLIKELY( chunksize( nextchunk ) >= av->system_mem) )
+        return 0;
+
+    // double free or corruption (out)
+    if( UNLIKELY( contiguous(av) &&
+                  (char *)nextchunk >= (char *)av->top + chunksize( av->top )) )
+        return 0;
+
+    return 1;
+}
+#else
+static inline chunk_is_vanilla (mchunkptr p, void *ptr) { return 0; }
+#endif
+
+static int address_within_main_heap (ElfW(Addr) addr)
+{
+    static ElfW(Addr) base = (ElfW(Addr)) NULL;
+    ElfW(Addr) top = (ElfW(Addr)) sbrk( 0 );
+
+    // past the end of the heap:
+    if( top <= addr )
+        return 0;
+
+    if( base == (ElfW(Addr)) NULL )
+    {
+        struct mallinfo mi = mallinfo();
+        base = top - (ElfW(Addr)) mi.arena;
+    }
+
+    // address is below heap base
+    // ∴ either a mmapped address, non-malloc'd memory
+    // or an address from a secondary arena
+    if( base > addr )
+        return 0;
+
+    return 1;
+}
+
+void *
+capsule_shim_realloc (const capsule cap, void *ptr, size_t size)
+{
+    if( !ptr || address_within_main_heap( (ElfW(Addr)) ptr ) )
+        return realloc( ptr, size );
+
+    mchunkptr p = mem2chunk( ptr );
+
+    if( chunk_is_mmapped( p ) || chunk_is_vanilla( p, ptr ) )
+        return realloc( ptr, size );
+
+    return cap->ns->mem->realloc( ptr, size );
+}
+
+void
+capsule_shim_free (const capsule cap, void *ptr)
+{
+    if( !ptr )
+        return;
+
+    // from the main heap: ie from the vanilla libc outside the capsule
+    if( address_within_main_heap( (ElfW(Addr)) ptr ) )
+    {
+        free( ptr );
+        return;
+    }
+
+    mchunkptr p = mem2chunk( ptr );
+    // mmapped pointer/chunk: can't tell whose this is but since we
+    // override the malloc/free cluster as early as possible we're
+    // kind of hoping we don't have any of these from inside the capsule
+    //
+    // we'd only have such a pointer if the libraries we dlmopen() into
+    // the capsule allocated large chunks of memory in their initialiser(s):
+    if( chunk_is_mmapped( p ) || chunk_is_vanilla( p, ptr ) )
+    {
+        free( ptr );
+        return;
+    }
+
+    // doesn't look like a valid pointer to the main libc,
+    // pass it to the capsule libc and hope for the best:
+    cap->ns->mem->free( ptr );
+}
diff --git a/subprojects/libcapsule/capsule/capsule.h b/subprojects/libcapsule/capsule/capsule.h
new file mode 100644
index 0000000000000000000000000000000000000000..1435514f5987d2005e38206ac4973f8bbedee4a3
--- /dev/null
+++ b/subprojects/libcapsule/capsule/capsule.h
@@ -0,0 +1,279 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#include <link.h>
+
+#define _CAPSULE_PUBLIC __attribute__((visibility("default"))) extern
+
+/**
+ * capsule_addr:
+ *
+ * Identical to an ElfW(Addr) from libelf. You may treat this as
+ * equivalent to a void * when assigning to it.
+ */
+typedef ElfW(Addr) capsule_addr;
+
+/**
+ * capsule
+ *
+ * A handle returned by capsule_init: A required parameter for all
+ * other capsule calls.
+ */
+typedef struct _capsule *capsule;
+
+/**
+ * capsule_item:
+ * @name: The name of the symbol to be relocated
+ * @real: address of the ‘real’ symbol in the target library
+ * @shim: address of the ‘fake’ symbol in the proxy library
+ *
+ * @real and @shim may typically be left empty by the shim library.
+ *
+ * Both slots will typically hold the correct values after a successful
+ * capsule… call. While this is sometimes important internally it is
+ * not usually of interest to the caller (except maybe for debugging)
+ */
+typedef struct _capsule_item capsule_item;
+
+struct _capsule_item
+{
+    const char *name;
+    capsule_addr real;
+    capsule_addr shim;
+
+    /*< private >*/
+    void *unused0;
+    void *unused1;
+    void *unused2;
+    void *unused3;
+};
+
+/**
+ * capsule_metadata:
+ * @capsule_abi: Version of the libcapsule ABI implmented by this struct
+ * @soname: The soname of the encapsulated library
+ * @default_prefix: The default root location of the filesystem from which the
+ *                  encapsulated library should be loaded
+ * @exclude: (array zero-terminated=1): an array of char *, each
+ *           specifying a DSO not to load, terminated by a %NULL entry
+ * @export: (array zero-terminated=1): an array of char *, each
+ *          specifying a DSO whose symbols should be exported from this
+ *          capsule
+ * @items: (array zero-terminated=1): Array of capsule_item
+ *          specifying which symbols to export, terminated by a
+ *          #capsule_item whose @name is %NULL
+ * @int_dlopen: Implementation of the same API as `dlopen`
+ *
+ * This struct allows libcapsule proxy libraries to statically declare
+ * metadata about themselves that libcapsule needs at link time in order
+ * to function properly.
+ *
+ * The #capsule_item entries in @items need only specify the symbol
+ * name: The shim and real fields will be populated automatically if they
+ * are not pre-filled (this is the normal use case, as it would be unusual
+ * to know these value in advance).
+ */
+typedef struct _capsule_metadata capsule_metadata;
+
+struct _capsule_metadata
+{
+    /*< public >*/
+    const int     capsule_abi;
+    const char   *soname;
+    const char   *default_prefix;
+    const char  **exclude;
+    const char  **export;
+    capsule_item *items;
+    void *(*int_dlopen) (const char *filename, int flag);
+    void  (*int_free) (void *ptr);
+    void *(*int_realloc) (void *ptr, size_t size);
+    /*< private >*/
+    capsule handle;
+};
+
+/**
+ * capsule_init:
+ * @soname: the soname of the target library
+ *
+ * Returns: a #capsule handle.
+ *
+ * Does any initialisation necessary to use libcapsule’s functions.
+ *
+ * Initialises internal accounting structures within the capsule
+ * and triggers the metadata setup if this caspsule has been
+ * acquired via dlopen(), and finishes registering the capsule
+ * proxy with libcapsule itself.
+ */
+_CAPSULE_PUBLIC
+capsule capsule_init (const char *soname);
+
+////////////////////////////////////////////////////////////////////////////
+// do not move the Returns declaration in this next doc-comment:
+// gtk-doc does not tolerate any non-whitespace text between the
+// last parameter and the Returns line if the name of the function
+// is capsule_shim_dlopen. This was discovered empirically.
+// As this is clearly insane, it must be a bad interaction with something
+// else which shares some element of that name, but I have no idea what.
+////////////////////////////////////////////////////////////////////////////
+
+/**
+ * capsule_shim_dlopen:
+ * @cap: The capsule from which `dlopen` was called
+ * @file: SONAME or filename to be opened
+ * @flag: `dlopen` flags to pass to the real `dlopen` call
+ *
+ * Returns: A handle as if for `dlopen`
+ *
+ * An implementation of dlopen suitable to be called from inside a
+ * namespace. Load @file into @cap namespace.
+ *
+ * If @cap has a non-trivial prefix, load @file and its recursive
+ * dependencies from @cap prefix instead of from the root filesystem.
+ *
+ * This helper function exists because dlopen() cannot safely be called
+ * by a DSO opened into a private namespace. It takes @file and @flag
+ * arguments cf dlopen() and a @cap handle,
+ * and performs a safe dlmopen() call instead.
+ *
+ * Typically this function is used to implement a safe wrapper for dlopen()
+ * which is assigned to the int_dlopen member of the #capsule_metadata.
+ * This * replaces calls to dlopen() by all DSOs in the capsule,
+ * allowing libraries which use dlopen() to work inside the capsule.
+ *
+ * Limitations: RTLD_GLOBAL is not supported in @flag. This is a glibc
+ * limitation in the dlmopen() implementation.
+ */
+_CAPSULE_PUBLIC
+void *capsule_shim_dlopen (const capsule cap, const char *file, int flag);
+
+/**
+ * capsule_external_dlsym:
+ * @handle: A dl handle, as passed to dlsym()
+ * @symbol: A symbol name, as passed to dlsym()
+ *
+ * An implementation of `dlsym`, used when it is called by the executable
+ * or by a library outside the capsule.
+ *
+ * Some libraries have a use pattern in which their caller/user
+ * uses dlsym() to obtain symbols rather than using those symbols
+ * directly in its own code (libGL is an example of this).
+ *
+ * Since the target library may have a different symbol set than the
+ * one the libcapsule proxy shim was generated from we can’t rely on
+ * dlsym() finding those symbols in the shim’s symbol table.
+ *
+ * Instead we must intercept dlsym() calls made outside the capsule
+ * and attempt to look for the required symbol in the namespace defined
+ * by the active capsules first - If the required symbol is found there
+ * AND is from one of the DSO names present in the exported list then that
+ * symbol is returned. If either of those conditions is not met then
+ * a normal dlsym call with the passed handle is made.
+ *
+ * This function provides the functionality described above, and is
+ * normally used automatically by libcapsule. It is exposed as API in
+ * case a libcapsule proxy library needs to provide its own specialised
+ * symbol lookup mechanism.
+ *
+ * Returns: The address associated with @symbol (as if for `dlsym`), or %NULL
+ */
+_CAPSULE_PUBLIC
+void *capsule_external_dlsym (void *handle, const char *symbol);
+
+/**
+ * capsule_external_dlopen:
+ * @file: A soname, filename or path as passed to dlopen()
+ * @flag: The dl flags, as per dlopen()
+ *
+ * An implementation of `dlopen`, used when it is called by the executable
+ * or by a library outside the capsule.
+ *
+ * This wrapper is meant to be replace normal calls to dlopen() made by the
+ * main program or a non-capsule library - it is necessary because en ELF
+ * object loaded by dlopen() may need us to trigger the _capsule_relocate()
+ * operation in order to make sure its GOT entries are correctly updated.
+ *
+ * This wrapper carries out a normal dlopen() and then re-triggers the
+ * initial _capsule_relocate() call immediately, before returning
+ * the same value that dlopen() would have, given the same @file and @flag
+ * arguments.
+ *
+ * Returns: The handle returned by `dlopen`
+ */
+_CAPSULE_PUBLIC
+void *capsule_external_dlopen(const char *file, int flag);
+
+/**
+ * capsule_close:
+ * @cap: a #capsule handle as returned by capsule_init()
+ *
+ * This function should be called from a capsule proxy library’s destructor:
+ * Its job is to clean up capsule-specific allocated memory and metadata when
+ * a capsule proxy is discarded via dlclose(), and ensure that libproxy itself
+ * won’t try to access any related invalidated memory afterwards.
+ */
+_CAPSULE_PUBLIC
+void capsule_close (capsule cap);
+
+/**
+ * capsule_get_prefix:
+ * @dflt: A default capsule prefix path
+ * @soname: The soname of the library we are encapsulating
+ *
+ * Returns: A newly allocated char * pointing to the prefix path
+ *
+ * libcapsule provides a proxy to a library, potentially from a foreign
+ * filesystem tree (found at, for example, ‘/host’).
+ *
+ * Since it is useful for this location to be overrideable at startup
+ * on a per-target-library basis this function standardises the prefix
+ * selection algorithm as follows:
+ *
+ * - An environment variable based on @soname:
+ *   libGL.so.1 would map to CAPSULE_LIBGL_SO_1_PREFIX
+ * - If that is unset, the CAPSULE_PREFIX environment variable
+ * - Next: The default to the value passed in @dflt
+ * - And if all that failed, NULL (which is internally equivalent to "/")
+ *
+ * The environment variables are ignored if the process is privileged
+ * (setuid, setgid, given special capabilities, or marked as privileged
+ * by a LSM), or if libcapsule was compiled against a glibc version
+ * older than 2.17.
+ *
+ * Although the value is newly allocated it will typically be cached
+ * in a structure that needs to survive the entire lifespan of the
+ * running program, so freeing it is unlikely to be a concern.
+ **/
+_CAPSULE_PUBLIC
+char *capsule_get_prefix(const char *dflt, const char *soname);
+
+/**
+ * capsule_shim_free:
+ * @cap: The capsule from which `free` was called
+ * @ptr: The pointer to be freed
+ *
+ * Tries to safely route an allocated pointer to the correct free()
+ * implementation.
+ *
+ */
+_CAPSULE_PUBLIC
+void capsule_shim_free (const capsule cap, void *ptr);
+
+_CAPSULE_PUBLIC
+void * capsule_shim_realloc (const capsule cap, void *ptr, size_t size);
+
diff --git a/subprojects/libcapsule/ci/Jenkinsfile b/subprojects/libcapsule/ci/Jenkinsfile
new file mode 100644
index 0000000000000000000000000000000000000000..2b04fe5a29f0ae65f0461bafdaac369a2096d5dc
--- /dev/null
+++ b/subprojects/libcapsule/ci/Jenkinsfile
@@ -0,0 +1,121 @@
+#!/usr/bin/env groovy
+/*
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * Copyright © 2017-2018 Collabora Ltd
+ *
+ * This package is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This package 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this package.  If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+@Library('steamos-ci') _
+
+pipeline {
+  agent none
+
+  options {
+    timestamps()
+    skipDefaultCheckout()
+  }
+
+  stages {
+    stage ("deb-build-snapshot") {
+      agent {
+        docker {
+          label 'docker-slave'
+          image "docker-registry.internal.collabora.com/steamos/package-builder:${env.CI_SUITE ?: 'brewmaster'}"
+          registryUrl 'https://docker-registry.internal.collabora.com'
+          registryCredentialsId 'ccu-docker-internal-collabora-com'
+          alwaysPull true
+        }
+      }
+      environment {
+        HOME="${env.WORKSPACE}"
+        TMPDIR="${env.WORKSPACE}"
+        PYTHONUNBUFFERED="1"
+      }
+      steps {
+        sh '''
+        git config --global user.name Jenkins
+        git config --global user.email nobody@example.com
+        '''
+        checkoutCollaboraGitlab('vivek/libcapsule', 'master', 'src')
+        sh '''
+        mkdir -p debs
+        echo "jenkins::$(id -u):$(id -g):Jenkins:$(pwd):/bin/sh" > passwd
+        export NSS_WRAPPER_PASSWD=$(pwd)/passwd
+        export NSS_WRAPPER_GROUP=/dev/null
+        export LD_PRELOAD=libnss_wrapper.so
+        ( cd src && deb-build-snapshot -d ../debs -s -u localhost )
+        '''
+        stash name: 'debs', includes: 'debs/**'
+        archiveArtifacts 'debs/**'
+      }
+      post {
+        always {
+          deleteDir()
+        }
+      }
+    }
+
+    stage ("autopkgtest") {
+      agent {
+        docker {
+          label 'docker-slave'
+          args '--device=/dev/kvm'
+          image 'docker-registry.internal.collabora.com/steamos/package-builder:stretch'
+          registryUrl 'https://docker-registry.internal.collabora.com'
+          registryCredentialsId 'ccu-docker-internal-collabora-com'
+          alwaysPull true
+        }
+      }
+      environment {
+        HOME="${env.WORKSPACE}"
+        TMPDIR="${env.WORKSPACE}"
+        PYTHONUNBUFFERED="1"
+      }
+      steps {
+        sh 'git config --global user.name Jenkins'
+        sh 'git config --global user.email nobody@example.com'
+        checkoutCollaboraGitlab('vivek/libcapsule', 'master', 'src')
+        unstash 'debs'
+        sh '''
+        mkdir -p tmp
+        mkdir -p output
+        wget --progress=dot:giga -O autopkgtest.qcow2 https://images.collabora.co.uk/steam/autopkgtest-virt-qemu/${CI_SUITE:=brewmaster}_amd64.qcow2
+
+        case "${CI_SUITE}" in
+            (brewmaster)
+                set -- --add-apt-source='deb http://deb.debian.org/debian jessie main'
+                for x in /etc/apt/trusted.gpg.d/*.gpg; do
+                    set -- --copy="$x:$x" "$@"
+                done
+                ;;
+            (*)
+                set --
+                ;;
+        esac
+
+        autopkgtest "$@" --apt-upgrade --no-built-binaries --output-dir ./output/ debs/*.deb ./src/ -- qemu --qemu-options="-drive format=raw,read-only,if=pflash,file=/usr/share/ovmf/OVMF.fd" ./autopkgtest.qcow2
+        '''
+      }
+      post {
+        always {
+          archiveArtifacts 'output/**'
+          deleteDir()
+        }
+      }
+    }
+  }
+}
+/* vim:set sts=2 sw=2 et: */
diff --git a/subprojects/libcapsule/ci/gitlab-ci.yml b/subprojects/libcapsule/ci/gitlab-ci.yml
new file mode 100644
index 0000000000000000000000000000000000000000..5dcef84aadb0b23ed05f1eed01d6cb8ff76cf3f7
--- /dev/null
+++ b/subprojects/libcapsule/ci/gitlab-ci.yml
@@ -0,0 +1,163 @@
+# Copyright © 2019 Collabora Ltd.
+#
+# SPDX-License-Identifier: MIT
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be included
+# in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+variables:
+    BUILD_IMAGE: 'debian:buster-slim'
+    CI_DISABLE_LIBRARY: ''
+    CI_DISTCHECK: ''
+    CI_MAKE_J: '-j -l2'
+
+    # A known-good version of deb-build-snapshot
+    DEB_BUILD_SNAPSHOT_TAG: v0.20190828.0
+
+before_script:
+    - |
+        set -eux
+        apt-get -y update
+        apt-get install -y --no-install-recommends \
+          autoconf \
+          autoconf-archive \
+          automake \
+          bubblewrap \
+          build-essential \
+          ca-certificates \
+          chrpath \
+          debhelper \
+          devscripts \
+          dh-autoreconf \
+          git \
+          gtk-doc-tools \
+          libelf-dev \
+          libipc-run-perl \
+          libglib2.0-dev \
+          libxml2 \
+          perl \
+          python3-debian \
+          rsync \
+          xsltproc \
+          zlib1g \
+          ${NULL+}
+
+.build:
+    image: ${BUILD_IMAGE}
+    script: |
+        set -eux
+        NOCONFIGURE=1 ./autogen.sh
+        ./configure \
+            CC=${CC-cc} \
+            ${CI_DISABLE_LIBRARY+--disable-library} \
+            SANITIZE_CFLAGS="${SANITIZE_CFLAGS-}" \
+            ${NULL+}
+        make ${CI_MAKE_J}
+        make ${CI_MAKE_J} check
+        if [ -n "$CI_DISTCHECK" ]; then
+            make ${CI_MAKE_J} distcheck
+        fi
+    artifacts:
+        paths:
+            - config.log
+            - test-suite.log
+
+build:ubsan:
+    extends: .build
+    variables:
+        SANITIZE_CFLAGS: "-fsanitize=undefined -fno-sanitize=alignment -static-libubsan"
+
+build:asan:
+    extends: .build
+    variables:
+        # The actual libcapsule library doesn't work with AddressSanitizer,
+        # but at least we can unit-test utility code and test the tools
+        CI_DISABLE_LIBRARY: "yes"
+        SANITIZE_CFLAGS: "-fsanitize-recover=all -fsanitize=address -fsanitize=undefined -fno-sanitize=alignment -fPIE -pie"
+
+.deb-build-snapshot:
+    image: ${BUILD_IMAGE}
+    script: |
+        set -eux
+
+        set -- --upstream --source
+
+        mkdir -p "debian/tmp/artifacts/${CI_JOB_NAME}"
+        set -- "$@" --download "$(pwd)/debian/tmp/artifacts/${CI_JOB_NAME}"
+
+        if [ -n "${CI_DISPOSABLE_ENVIRONMENT-}" ]; then
+            set -- "$@" --install --force-local-install
+        fi
+
+        tempdir="$(mktemp -d)"
+        git clone --branch ${DEB_BUILD_SNAPSHOT_TAG} https://gitlab.collabora.com/smcv/deb-build-snapshot.git "$tempdir/deb-build-snapshot"
+        "$tempdir/deb-build-snapshot/deb-build-snapshot" "$@" localhost
+        rm -fr "$tempdir"
+    artifacts:
+        paths:
+            - debian/tmp/artifacts/${CI_JOB_NAME}
+
+build:stretch:
+    extends: .deb-build-snapshot
+    variables:
+        BUILD_IMAGE: 'debian:stretch-slim'
+
+build:buster:
+    extends: .deb-build-snapshot
+    variables:
+        BUILD_IMAGE: 'debian:buster-slim'
+
+build:bionic:
+    extends: .deb-build-snapshot
+    variables:
+        BUILD_IMAGE: 'ubuntu:bionic'
+
+build:arch:
+    when: manual
+    extends: .build
+    variables:
+        BUILD_IMAGE: 'archlinux'
+    before_script:
+        # bubblewrap-suid instead of bubblewrap will be necessary on some
+        # kernels.
+        #
+        # Full test coverage is likely to require running with
+        # docker run --privileged, which Gitlab-CI doesn't normally do;
+        # as much as anything, this is here to document what's necessary
+        # to build libcapsule with full test coverage.
+        - |
+            pacman -Syu --needed --noconfirm --noprogressbar \
+            autoconf \
+            autoconf-archive \
+            automake \
+            bubblewrap \
+            chrpath \
+            gcc \
+            glib2 \
+            gtk-doc \
+            libelf \
+            libtool \
+            libxml2 \
+            libxslt \
+            make \
+            perl \
+            perl-ipc-run \
+            pkgconf \
+            sudo \
+            ${NULL+}
diff --git a/subprojects/libcapsule/configure.ac b/subprojects/libcapsule/configure.ac
new file mode 100644
index 0000000000000000000000000000000000000000..00b6911b5ecae6e84c8b97f4bdd621ec67804ba1
--- /dev/null
+++ b/subprojects/libcapsule/configure.ac
@@ -0,0 +1,138 @@
+dnl Copyright © 2017 Collabora Ltd
+
+dnl This file is part of libcapsule.
+
+dnl libcapsule is free software: you can redistribute it and/or modify
+dnl it under the terms of the GNU Lesser General Public License as
+dnl published by the Free Software Foundation; either version 2.1 of the
+dnl License, or (at your option) any later version.
+
+dnl libcapsule is distributed in the hope that it will be useful,
+dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
+dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+dnl GNU Lesser General Public License for more details.
+
+dnl You should have received a copy of the GNU Lesser General Public
+dnl License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+AC_PREREQ([2.60])
+
+AC_INIT([libcapsule],
+        m4_esyscmd([build-aux/git-version-gen .tarball-version]),
+        [], [], [https://gitlab.collabora.com/vivek/libcapsule])
+AC_CONFIG_AUX_DIR([build-aux])
+AC_CONFIG_MACRO_DIR([m4])
+AC_REQUIRE_AUX_FILE([tap-driver.sh])
+AC_ARG_VAR([PERL])
+AC_PATH_PROG([PERL], [perl], [perl])
+
+m4_pattern_forbid(
+    [^AX_(IS_RELEASE|COMPILER_FLAGS)],
+    [Please install GNU autoconf-archive 2016.09.16 or later])
+
+dnl don't kvetch about gnu makefile syntax
+dnl and don't require ChangeLog &co if they don't exist
+AM_INIT_AUTOMAKE([-Wno-portability foreign])
+AM_SILENT_RULES([yes])
+AC_USE_SYSTEM_EXTENSIONS
+AC_SYS_LARGEFILE
+# So we can install x86_64-linux-gnu-capsule-symbols etc.
+AC_CANONICAL_HOST
+# Make sure we are not mixing up --host=i686-linux-gnu with a 64-bit compiler
+# or vice versa
+CAPSULE_MATCHING_ABI
+
+LT_INIT
+
+AX_IS_RELEASE([dash-version])
+dnl -Wno-pointer-arith: We do a lot of arithmetic on void *, relying on gcc
+dnl     making (void * + int) the same as (char * + int)
+dnl -Wno-declaration-after-statement: Stylistic choice, this is C99
+AX_COMPILER_FLAGS([], [], [], [], [ \
+    -Wno-pointer-arith \
+    -Wno-declaration-after-statement \
+])
+WARN_CFLAGS=$(echo "$WARN_CFLAGS" | sed -Ee 's,-W(error=)?declaration-after-statement,,')
+WARN_CFLAGS=$(echo "$WARN_CFLAGS" | sed -Ee 's,-W(error=)?pointer-arith,,')
+
+AC_CHECK_FUNCS_ONCE([secure_getenv])
+AC_CHECK_HEADERS_ONCE([stdalign.h])
+
+AM_CONDITIONAL([ENABLE_SHARED], [test "x$enable_shared" = xyes])
+
+AC_ARG_ENABLE([library],
+  [AS_HELP_STRING([--enable-library],
+                  [build the libcapsule library (disable if you only want CLI tools)])],
+  [],
+  [enable_library="$enable_shared"])
+AS_IF([test "x$enable_shared" != xyes && test "x$enable_library" = xyes],
+      [AC_MSG_ERROR([libcapsule is only useful as a shared library])])
+AM_CONDITIONAL([ENABLE_LIBRARY], [test "x$enable_library" = xyes])
+
+dnl make sure we've got the _right_ libelf (doesn't always ship a .pc)
+PKG_CHECK_MODULES([LIBELF], [libelf >= 0.152], [have_libelf=yes], [
+    AC_MSG_WARN([libelf.pc not found])
+    AC_MSG_WARN([Set LIBELF_CFLAGS, LIBELF_LIBS if -lelf is insufficient])
+    save_LIBS="$LIBS"
+    AC_CHECK_TYPE([Elf_Cmd], [], [], [#include <libelf.h>])
+    AC_CHECK_LIB([elf], [elf_begin], [],
+                 [AC_MSG_ERROR([libelf not found])])
+    AC_MSG_CHECKING([Elf_Cmd ELF_C_READ_MMAP])
+    AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <libelf.h>],
+                                    [Elf_Cmd x = ELF_C_READ_MMAP;])],
+                   [AC_MSG_RESULT([yes])],
+                   [AC_MSG_RESULT([no])]
+                   [AC_MSG_ERROR([ELF_C_READ_MMAP missing: libelfg vs libelf0?])])
+    LIBS="$save_LIBS"
+    LIBELF_LIBS=-lelf
+])
+
+AC_ARG_WITH([glib],
+  [AS_HELP_STRING([--with-glib=yes/no],
+    [use GLib for better test coverage [default=yes]])],
+  [],
+  [with_glib=auto])
+AS_IF([test "$with_glib" != no],
+  [PKG_CHECK_MODULES([GLIB], [glib-2.0 >= 2.38], [have_glib=yes], [have_glib=no])],
+  [have_glib=no])
+AS_IF([test "$with_glib" = yes && test "$have_glib" = no],
+  [AC_MSG_ERROR([GLib required but not found])])
+AM_CONDITIONAL([HAVE_GLIB], [test "x$have_glib" = xyes])
+
+AC_ARG_ENABLE([host-prefix],
+  [AS_HELP_STRING([--enable-host-prefix=PREFIX/yes/no],
+    [install link to tools with a prefix [default=$host-]])],
+  [],
+  [enable_host_prefix=yes])
+
+AM_CONDITIONAL([ENABLE_HOST_PREFIX], [test "x$enable_host_prefix" != xno])
+AS_IF([test "x$enable_host_prefix" = xyes],
+  [enable_host_prefix="$host-"])
+AS_IF([test "x$enable_host_prefix" = xno],
+  [enable_host_prefix=""])
+AC_SUBST([HOST_PREFIX], [$enable_host_prefix])
+
+AC_ARG_ENABLE([tools-rpath],
+  [AS_HELP_STRING([--enable-tools-rpath=RPATH/yes/no],
+    [install link to tools with a RPATH ready to override with chrpath])],
+  [],
+  [enable_tools_rpath=no])
+
+AS_IF([test "x$enable_tools_rpath" = xyes],
+  [enable_tools_rpath="/_ORIGIN_/__/lib/$host"])
+AS_IF([test "x$enable_tools_rpath" = xno],
+  [enable_tools_rpath=""])
+AC_SUBST([enable_tools_rpath])
+AM_CONDITIONAL([ENABLE_TOOLS_RPATH], [test "x$enable_tools_rpath" != x])
+
+AC_SEARCH_LIBS([dlmopen], [dl])
+
+AC_PATH_PROG([XSLTPROC], [xsltproc])
+AM_CONDITIONAL([HAVE_XSLTPROC], [test "x$XSLTPROC" != x])
+GTK_DOC_CHECK
+
+AC_ARG_VAR([SANITIZE_CFLAGS],
+           [Extra CFLAGS for modules that are instrumented for error-checking])
+
+AC_CONFIG_FILES([Makefile data/libcapsule.pc data/libcapsule-uninstalled.pc data/libcapsule-tools.pc data/libcapsule-tools-uninstalled.pc data/dirconf.txt])
+AC_OUTPUT
diff --git a/subprojects/libcapsule/data/capsule-init-project b/subprojects/libcapsule/data/capsule-init-project
new file mode 100755
index 0000000000000000000000000000000000000000..d044cd026f4615a0da1a9c8fbf5bd60e1f79c4df
--- /dev/null
+++ b/subprojects/libcapsule/data/capsule-init-project
@@ -0,0 +1,297 @@
+#!/bin/bash
+
+set -e;
+set -u;
+
+me=$(basename $0);
+
+usage ()
+{
+cat << EOF
+Usage: $me [OPTIONS] TARGET...
+    Options:
+        --capsule-pkgdatadir=PATH
+                                Use supporting files from PATH
+                                [default: libcapsule's \${pkgdatadir}]
+        --capsule-symbols-tool=PATH
+                                Use replacement capsule-symbols(1)
+                                [default: ${CAPSULE_SYMBOLS_TOOL:-ask pkg-config}]
+        --capsule-version-tool=PATH
+                                Use replacement capsule-version(1)
+                                [default: ${CAPSULE_VERSION_TOOL:-ask pkg-config}]
+        --search-tree=PATH      Find libraries to be proxied in this
+                                chroot, sysroot or container now
+                                [default: /]
+        --set-version=VERSION   Use this as the library's version number
+                                (major, major.minor or major.minor.micro)
+                                [default: based on first TARGET]
+        --symbols-from-dir=PATH Use symbols from files in this directory
+                                instead of running capsule-symbols(1)
+        --runtime-tree=PATH     Generated code will find proxied
+                                libraries in this chroot, sysroot or
+                                container at runtime if CAPSULE_PREFIX
+                                is unset [default: /host]
+        --package-name=PACKAGE  Use this name for the generated package
+                                [default: based on name of first TARGET]
+        --destination=PATH      Create the project here
+                                [default: ./PACKAGE]
+        TARGET...               SONAMEs of the libraries to be proxied
+                                (libfoo.so.2), or libfoo.so.2/2.1.3
+                                to force a version
+EOF
+    exit "${1:-0}"
+}
+
+getopt_temp="$(getopt -o 'h' \
+    -l 'autoreconf,no-autoreconf,capsule-pkgdatadir:,capsule-symbols-tool:,capsule-version-tool:,destination:,package-name:,search-tree:,set-version:,symbols-from-dir:,runtime-tree:,help' \
+    -n "$me" -- "$@")"
+eval set -- "$getopt_temp"
+
+autoreconf=yes
+dest=
+package_name=
+runtime_tree=
+search_tree=
+symbols_from=
+ver=
+
+while true;
+do
+    case "$1" in
+        (--help|-h)
+            usage 0;
+            ;;
+
+        (--autoreconf)
+            autoreconf=yes;
+            shift;
+            continue;
+            ;;
+
+        (--no-autoreconf)
+            autoreconf=;
+            shift;
+            continue;
+            ;;
+
+        (--capsule-pkgdatadir)
+            CAPSULE_MKINC="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--capsule-symbols-tool)
+            CAPSULE_SYMBOLS_TOOL="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--capsule-version-tool)
+            CAPSULE_VERSION_TOOL="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--destination)
+            dest="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--package-name)
+            package_name="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--runtime-tree)
+            runtime_tree="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--search-tree)
+            search_tree="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--set-version)
+            ver="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--symbols-from-dir)
+            symbols_from="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--)
+            shift;
+            break;
+            ;;
+        (*)
+            echo "$me: Internal error" >&2;
+            usage 2 >&2;
+            ;;
+    esac
+done
+
+if [ "$#" -lt 1 ];
+then
+    usage 2 >&2;
+fi;
+
+first_target_maybe_ver="$1";
+first_target="${first_target_maybe_ver%%/*}";
+
+if [ -z "$package_name" ];
+then
+    package_name="${first_target%%.so.*}";
+    package_name="${package_name%%.so}-proxy";
+fi;
+
+if [ -z "$dest" ];
+then
+    dest="$package_name";
+fi;
+
+: "${PKG_CONFIG:=pkg-config}"
+: "${CAPSULE_MKINC:="$($PKG_CONFIG --variable=makeinc libcapsule-tools)"}"
+: "${CAPSULE_SYMBOLS_TOOL:="$($PKG_CONFIG --variable=CAPSULE_SYMBOLS_TOOL libcapsule-tools)"}"
+: "${CAPSULE_VERSION_TOOL:="$($PKG_CONFIG --variable=CAPSULE_VERSION_TOOL libcapsule-tools)"}"
+: "${runtime_tree:="/host"}"
+: "${search_tree:="/"}"
+
+if [ -z "$ver" ];
+then
+    if [ "x$first_target" != "x$first_target_maybe_ver" ];
+    then
+        ver="${first_target_maybe_ver#*/}";
+    else
+        read x x ver x < <("$CAPSULE_VERSION_TOOL" "$first_target" "$search_tree");
+    fi;
+fi;
+
+echo "Generating project for $package_name, shims for $runtime_tree libraries: $*";
+
+echo "Creating project directory $dest";
+
+mkdir -p "$dest";
+cd $dest;
+mkdir -p m4;
+mkdir -p shim;
+
+echo "Working in $PWD";
+
+escaped_target="$(echo "$first_target" | sed -e 's/\\/\\\\/g' -e 's/,/\\,/g')"
+escaped_search_tree="$(echo "$search_tree" | sed -e 's/\\/\\\\/g' -e 's/,/\\,/g')"
+escaped_runtime_tree="$(echo "$runtime_tree" | sed -e 's/\\/\\\\/g' -e 's/,/\\,/g')"
+sed \
+    -e "1,/^\$/d" \
+    -e "s,@TARGET@,$escaped_target," \
+    < "$CAPSULE_MKINC/shim-README.in" \
+    > README;
+
+echo Initialising configure.ac;
+
+sed \
+    -e "1,/^\$/d" \
+    -e "s,@RUNTIME_TREE@,$escaped_runtime_tree," \
+    -e "s,@SEARCH_TREE@,$escaped_search_tree," \
+    -e "s,@PACKAGE@,$package_name," \
+    -e "s,@VER@,$ver," \
+    < "$CAPSULE_MKINC/shim-configure.ac.in" \
+    > configure.ac;
+
+echo Initialising Makefile.am;
+
+sed \
+    -e "1,/^\$/d" \
+    -e "s,@RUNTIME_TREE@,$escaped_runtime_tree," \
+    -e "s,@SEARCH_TREE@,$escaped_search_tree," \
+    -e "s,@PACKAGE@,$package_name," \
+    -e "s,@VER@,$ver," \
+    < "$CAPSULE_MKINC/shim-Makefile.am" \
+    > Makefile.am;
+
+for target_maybe_ver in "$@"; do
+    target="${target_maybe_ver%%/*}";
+    if [ "x$target" != "x$target_maybe_ver" ];
+    then
+        target_ver="${target_maybe_ver#*/}";
+    else
+        read x x target_ver x < <("$CAPSULE_VERSION_TOOL" "$target" "$search_tree");
+    fi;
+
+    base=${target%%.so.*};
+    base=${base%.so};
+    name=${base#lib};
+    lcbase=${base,,};
+    amname=${name//-/_};
+
+    escaped_target="$(echo "$target" | sed -e 's/\\/\\\\/g' -e 's/,/\\,/g')"
+    automake_target="${escaped_target//-/_}"
+    escaped_lib="$(echo "$name" | sed -e 's/\\/\\\\/g' -e 's/,/\\,/g')"
+    automake_lib="$(echo "$amname" | sed -e 's/\\/\\\\/g' -e 's/,/\\,/g')"
+
+    maj=${ver%%.*};
+
+    echo "Preparing proxy source files: $target"
+    touch shim/$target.{shared,excluded};
+
+    if [ -z "$symbols_from" ]; then
+        echo Extracting dynamic symbols from $search_tree : $target;
+
+        "$CAPSULE_SYMBOLS_TOOL" "$target" "$search_tree" > "shim/$target.symbols.tmp";
+        LC_ALL=C sort -u "shim/$target.symbols.tmp" > "shim/$target.symbols";
+        rm -f "shim/$target.symbols.tmp";
+    else
+        LC_ALL=C sort -u "$symbols_from/$target.symbols" > "shim/$target.symbols";
+    fi
+    touch "shim/$target.symbols.updated-for";
+
+    sed \
+        -e "1,/^\$/d" \
+        -e "s,@TARGET@,$escaped_target," \
+        -e "s,@AMTARGET@,$automake_target," \
+        -e "s,@LIB@,$escaped_lib," \
+        -e "s,@AMLIB@,$automake_lib," \
+        -e "s,@RUNTIME_TREE@,$escaped_runtime_tree," \
+        -e "s,@SEARCH_TREE@,$escaped_search_tree," \
+        -e "s,@PACKAGE@,$package_name," \
+        -e "s,@TARGET_VER@,$target_ver," \
+        < "$CAPSULE_MKINC/shim-target-Makefile.am" \
+        >> Makefile.am;
+done
+
+cp "$CAPSULE_MKINC/shim-.gitignore" .gitignore
+
+if [ -n "$autoreconf" ]; then
+    echo Running initial configuration;
+    autoreconf -ivf
+fi
+
+cat - <<EOF
+$PWD is ready for ./configure, see README for details.
+
+You will probably want to add the following files to your version
+control system:
+
+Makefile.am
+README
+configure.ac
+EOF
+
+
+for target_maybe_ver in "$@"; do
+    target="${target_maybe_ver%%/*}";
+    cat - <<EOF
+shim/$target.excluded
+shim/$target.shared
+shim/$target.symbols
+shim/$target.symbols.updated-for
+EOF
+done
diff --git a/subprojects/libcapsule/data/capsule-mkstublib b/subprojects/libcapsule/data/capsule-mkstublib
new file mode 100755
index 0000000000000000000000000000000000000000..8871f22f10c1eb68ec8392efe265f5c5aeaa4dff
--- /dev/null
+++ b/subprojects/libcapsule/data/capsule-mkstublib
@@ -0,0 +1,342 @@
+#!/bin/bash
+
+# Copyright © 2017 Collabora Ltd
+
+# This file is part of libcapsule.
+
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+
+# libcapsule 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 Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+set -u
+set -e
+
+declare -A NODE;
+top=$(dirname $0);
+top=${top:-.};
+me=$(basename $0);
+
+usage ()
+{
+cat << EOF
+Usage: $me [OPTIONS] TARGET EXCLUDES EXPORTS OUTPUT [DSO-VERSION [TREE]]"
+    Options:
+        --capsule-pkgdatadir=PATH
+                                Use supporting files from PATH
+                                [default: libcapsule's \${pkgdatadir}]
+        --capsule-symbols-tool=PATH
+                                Use replacement capsule-symbols(1)
+        --symbols-from=PATH     Use symbols from this file
+                                [default: \${OUTPUT%.c}.symbols]
+        --[no-]update-symbols   Do or don't update symbols list if EXPORTS
+                                is newer than it [default: do]
+        --search-tree=PATH      Find libraries to be proxied in this
+                                chroot, sysroot or container now
+                                [default: TREE or /]
+        --runtime-tree=PATH     Generated code will find proxied
+                                libraries in this chroot, sysroot or
+                                container at runtime if CAPSULE_PREFIX
+                                is unset [default: TREE or /host]
+    Positional parameters:
+        TARGET                  SONAME of shared library to proxy,
+                                for example libGL.so.1
+        EXCLUDES                List of SONAMEs never loaded from TREE
+                                in addition to libdl.so.2
+        EXPORTS                 List of extra SONAMEs whose symbols are
+                                to be exported from the shim, in addition
+                                to TARGET itself
+        OUTPUT                  C source code filename to output,
+                                for example libGL.so.c
+        DSO-VERSION             Major or MAJOR:MINOR:MICRO version (as for
+                                libtool -version-number) to export, for
+                                example 1 or 1:x:y for libGL.so.1
+                                [default: parse SONAME]
+        TREE                    Chroot or container to find libraries in,
+                                both now and at runtime; overridden by
+                                --runtime-tree and --search-tree
+EOF
+    exit "${1:-0}"
+}
+
+update_symbols=
+
+getopt_temp="$(getopt -o 'h' \
+    -l 'capsule-symbols-tool:,search-tree:,runtime-tree:,symbols-from:,update-symbols,no-update-symbols,help' \
+    -n "$me" -- "$@")"
+eval set -- "$getopt_temp"
+
+while true;
+do
+    case "$1" in
+        (--capsule-symbols-tool)
+            CAPSULE_SYMBOLS_TOOL="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--runtime-tree)
+            runtime_tree="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--search-tree)
+            search_tree="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--symbols-from)
+            symbol_file="$2"
+            shift 2;
+            continue;
+            ;;
+
+        (--update-symbols)
+            update_symbols=yes;
+            shift;
+            continue;
+            ;;
+
+        (--no-update-symbols)
+            update_symbols=;
+            shift;
+            continue;
+            ;;
+
+        (--help|-h)
+            usage 0;
+            ;;
+
+        (--)
+            shift;
+            break;
+            ;;
+        (*)
+            echo "$me: Internal error" >&2;
+            usage 2 >&2;
+            ;;
+    esac
+done
+
+: "${PKG_CONFIG:=pkg-config}"
+: "${CAPSULE_SYMBOLS_TOOL:="$($PKG_CONFIG --variable=CAPSULE_SYMBOLS_TOOL libcapsule-tools)"}"
+
+if [ "$#" -lt 4 ] || [ "$#" -gt 6 ];
+then
+    usage 2 >&2;
+fi;
+
+proxied_dso=$1;    shift;
+proxy_excluded=$1; shift;
+proxy_extra=$1;    shift;
+proxy_src=$1;      shift;
+
+if [ "$#" -gt 0 ];
+then
+    ltver=$1; shift;
+fi
+
+if [ "$#" -gt 0 ];
+then
+    proxy_tree=$1; shift;
+else
+    proxy_tree=
+fi
+
+if [ -n "${ltver:-}" ];
+then
+    major_version="${ltver%%:*}"
+elif [ "x${proxied_dso%.so.*}" != "x$proxied_dso" ]; then
+    major_version="${proxied_dso##*.so.}"
+fi
+
+: "${runtime_tree:="$proxy_tree"}"
+: "${runtime_tree:=/host}"
+: "${search_tree:="$proxy_tree"}"
+: "${search_tree:=/}"
+
+dso_base=${proxied_dso#lib}
+dso_base=${dso_base%.so*}
+proxied_dso=lib${dso_base}.so.${major_version};
+
+map_file=${proxy_src%.c}.map;
+
+if [ -z "${symbol_file:-}" ];
+then
+    symbol_file=${proxy_src%.c}.symbols;
+fi
+
+exec >$proxy_src.tmp;
+
+echo '#include "capsule/capsule-shim.h"'
+
+# generate the .symbols file if it doesn't exist, or if the proxy_extra
+# control file has been updated:
+if [ -n "$update_symbols" ] && [ $symbol_file -ot $proxy_extra ];
+then
+    echo -n > $symbol_file;
+    if (for pt in $proxied_dso $(cat $proxy_extra);
+        do
+            if [ x$V = x1 ];
+            then
+                echo "  $CAPSULE_SYMBOLS_TOOL $pt $search_tree" >&2;
+            else
+                echo "  SYMBOLS $pt $search_tree" >&2;
+            fi;
+            $CAPSULE_SYMBOLS_TOOL $pt $search_tree || exit 1;
+        done) > $symbol_file.tmp;
+    then
+        LC_ALL=C sort -u "$symbol_file.tmp" > "$symbol_file.tmp2";
+        rm -f "$symbol_file.tmp";
+        mv "$symbol_file.tmp2" "$symbol_file";
+    else
+        code=$?;
+        rm $symbol_file.tmp
+        exit $code;
+    fi;
+fi;
+
+
+while read symbol version dependency;
+do
+    case $version in
+        @*)
+            echo "VERSIONED_STUB  ( $symbol, $version );";
+            node=${version##*@};
+            NODE[$node]=${NODE[$node]:-}" "$symbol;
+            ;;
+        *)
+            echo "UNVERSIONED_STUB( $symbol );";
+            ;;
+    esac;
+done < $symbol_file;
+
+cat - <<EOF
+static capsule cap;
+static const char soname[] = "$proxied_dso";
+
+// _int_dlopen() from libcapsule or a locally overridden version
+#include "capsule/_int_dlopen.h"
+
+// Array of SONAMEs that will not have a private copy inside a capsule.
+// Only one copy of each of these will be loaded, and they will
+// be loaded without respecting the #capsule_namespace's @prefix.
+static const char *exclude[] =
+{
+EOF
+
+while read excluded x;
+do
+    case $excluded in lib*) printf "%32s \"%s\",\n" "" $excluded; ;; esac;
+done < $proxy_excluded;
+
+cat - <<EOF
+  NULL
+};
+
+// DSOs to restrict dlsym lookups to:
+static const char *valid_dlsym_sources[] =
+{
+  soname,
+EOF
+
+if [ -f "${proxy_extra}" ];
+then
+    for pt in $(cat $proxy_extra);
+    do
+        cat - <<EOF
+  "$pt",
+EOF
+    done;
+fi;
+
+cat - <<EOF
+  NULL
+};
+
+// -------------------------------------------------------------
+// this is an array of the functions we want to act as a shim for:
+static capsule_item relocs[] =
+{
+EOF
+
+while read sym x;
+do
+    cat - <<EOF
+  { "$sym" },
+EOF
+done < $symbol_file;
+
+cat - <<EOF
+  { "dlsym", (capsule_addr) capsule_external_dlsym,
+             (capsule_addr) capsule_external_dlsym, },
+  { NULL }
+};
+
+// make sure this symbol has global visibility so
+// that the libcapsule initialisation can find it
+__attribute__ ((visibility("default")))
+capsule_metadata capsule_meta =
+{
+    .capsule_abi      = 0,
+    .soname           = soname,
+    .default_prefix   = "/host",
+    .exclude          = exclude,
+    .export           = valid_dlsym_sources,
+    .items            = relocs,
+    .int_dlopen       = _int_dlopen,
+    .int_free         = _wrapped_free,
+    .int_realloc      = _wrapped_realloc,
+};
+
+static void __attribute__ ((constructor)) _capsule_init (void)
+{
+    // Don't use capsule_meta.soname here, because if we did, it would be
+    // a relocatable, interposable reference to capsule_meta (assuming the
+    // shim library wasn't linked with -Bsymbolic). At runtime, we'd get
+    // an arbitrarily chosen one of the capsule_meta symbols defined by
+    // the other shim libraries sharing our global symbol namespace,
+    // and in particular not necessarily our own, with predictably bad
+    // results.
+    cap = capsule_init( soname );
+}
+
+static void __attribute__ ((destructor)) _capsule_exit (void)
+{
+    capsule_close( cap );
+    cap = NULL;
+}
+EOF
+
+echo -n > $map_file;
+exec >& $map_file;
+
+for node in ${!NODE[@]};
+do
+    echo "$node {";
+    echo "  global:";
+    for symbol in ${NODE[$node]};
+    do
+        echo "    $symbol;";
+    done;
+    echo "};";
+    echo;
+done;
+
+# scrub the (symbol version) map file if it's empty:
+if [ ! -s $map_file ];
+then
+    rm -f $map_file;
+fi;
+
+mv "$proxy_src.tmp" "$proxy_src";
diff --git a/subprojects/libcapsule/data/capsule-shim.mk b/subprojects/libcapsule/data/capsule-shim.mk
new file mode 100644
index 0000000000000000000000000000000000000000..bd9bf066384522e2c459aca8fed48a1dc33f7ba5
--- /dev/null
+++ b/subprojects/libcapsule/data/capsule-shim.mk
@@ -0,0 +1,65 @@
+# allow stub file generation to be quiet or verbose per the value of V
+V         ?= 0
+GENSTUB_V1 =
+GENSTUB_V0 = @echo "  GENSTUB " $@;
+GENSTUB    = $(GENSTUB_V$(V))
+
+CAPSULE_TREE ?= /host
+CAPSULE_RUNTIME_TREE ?= $(CAPSULE_TREE)
+CAPSULE_SEARCH_TREE ?= $(CAPSULE_TREE)
+
+comma = ,
+
+define foreach_shim =
+shim_ldflags_$(subst -,_,$(1)) = -version-number $$(subst .,:,$$(CAPSULE_VERSION_$(subst -,_,$(1)))) \
+    $$(patsubst %,-Wl$$(comma)--version-script=%,$$(wildcard shim/$(1).map))
+maintainer-update-capsule-symbols: maintainer-update-capsule-symbols/$(1)
+endef
+
+$(foreach CAPSULE_SONAME,$(CAPSULE_SONAMES),$(eval $(call foreach_shim,$(CAPSULE_SONAME))))
+
+# regenerate if any dependencies get updated:
+shim/%.c: $(srcdir)/shim/%.excluded $(srcdir)/shim/%.shared $(srcdir)/shim/%.symbols
+	$(GENSTUB)V=$V \
+		$(CAPSULE_MKSTUBLIB_TOOL) \
+		--no-update-symbols \
+		--symbols-from=$(srcdir)/shim/$*.symbols \
+		$(AM_CAPSULE_MKSTUBLIB_FLAGS) \
+		$(CAPSULE_MKSTUBLIB_FLAGS) \
+		$* \
+		$(srcdir)/shim/$*.excluded \
+		$(srcdir)/shim/$*.shared \
+		$@ \
+		$(subst .,:,$(CAPSULE_VERSION_$(subst -,_,$*))) \
+		$(CAPSULE_RUNTIME_TREE)
+
+# error out when it's time to regenerate the exportable symbols list
+# (we do not do this automatically because it's part of our ABI)
+$(srcdir)/shim/%.symbols: $(srcdir)/shim/%.shared
+	@if cmp -s $< $@.updated-for; then \
+		touch $@; \
+	else \
+		echo "*** ERROR: List of shared libraries has changed" >&2; \
+		echo "*** Run: make maintainer-update-capsule-symbols [CAPSULE_SEARCH_TREE=/some-sysroot]" >&2; \
+		exit 1; \
+	fi
+
+maintainer-update-capsule-symbols: always
+	@true
+.PHONY: maintainer-update-capsule-symbols
+
+maintainer-update-capsule-symbols/%: always
+	$(AM_V_GEN)set -e; \
+	shared=$(srcdir)/shim/$*.shared; \
+	out=$(srcdir)/shim/$*.symbols; \
+	rm -f $$out.updated-for; \
+	for dso in $* $$(cat "$$shared"); \
+	do \
+		$(CAPSULE_SYMBOLS_TOOL) $$dso $(CAPSULE_SEARCH_TREE); \
+	done > "$$out.tmp"; \
+	LC_ALL=C sort -u "$$out.tmp" > "$$out.tmp2"; \
+	rm -f "$$out.tmp"; \
+	mv "$$out.tmp2" "$$out"; \
+	cp $(srcdir)/shim/$*.shared $$out.updated-for
+
+.PHONY: always
diff --git a/subprojects/libcapsule/data/dirconf.txt.in b/subprojects/libcapsule/data/dirconf.txt.in
new file mode 100644
index 0000000000000000000000000000000000000000..b1bcd397c2465a418f69c5213136aa7495de8850
--- /dev/null
+++ b/subprojects/libcapsule/data/dirconf.txt.in
@@ -0,0 +1,22 @@
+datarootdir   : @datarootdir@
+bindir        : @bindir@
+datadir       : @datadir@
+datarootdir   : @datarootdir@
+docdir        : @docdir@
+dvidir        : @dvidir@
+exec_prefix   : @exec_prefix@
+htmldir       : @htmldir@
+includedir    : @includedir@
+infodir       : @infodir@
+libdir        : @libdir@
+bindir        : @bindir@
+localedir     : @localedir@
+localstatedir : @localstatedir@
+mandir        : @mandir@
+oldincludedir : @oldincludedir@
+pdfdir        : @pdfdir@
+prefix        : @prefix@
+psdir         : @psdir@
+sbindir       : @sbindir@
+sharedstatedir: @sharedstatedir@
+sysconfdir    : @sysconfdir@
diff --git a/subprojects/libcapsule/data/disabled.mk b/subprojects/libcapsule/data/disabled.mk
new file mode 100644
index 0000000000000000000000000000000000000000..a1d0bd339f4d7ce7b784b3f660c95fa813e709f7
--- /dev/null
+++ b/subprojects/libcapsule/data/disabled.mk
@@ -0,0 +1,6 @@
+# all these catch-all rules do is slow things down
+%:: %,v
+%:: RCS/%
+%:: RCS/%,v
+%:: SCCS/s.%
+%:: s.%
diff --git a/subprojects/libcapsule/data/libcapsule-tools-uninstalled.pc.in b/subprojects/libcapsule/data/libcapsule-tools-uninstalled.pc.in
new file mode 100644
index 0000000000000000000000000000000000000000..3aff18ac82b3d82f1d477133aff0d900d7b17016
--- /dev/null
+++ b/subprojects/libcapsule/data/libcapsule-tools-uninstalled.pc.in
@@ -0,0 +1,10 @@
+makeinc=@abs_top_srcdir@/data
+CAPSULE_CAPTURE_LIBS_TOOL=@abs_top_builddir@/capsule-capture-libs
+CAPSULE_INIT_PROJECT_TOOL=@abs_top_srcdir@/data/capsule-init-project
+CAPSULE_MKSTUBLIB_TOOL=@abs_top_srcdir@/data/capsule-mkstublib
+CAPSULE_SYMBOLS_TOOL=@abs_top_builddir@/capsule-symbols
+CAPSULE_VERSION_TOOL=@abs_top_builddir@/capsule-version
+
+Name: libcapsule-tools
+Description: segregated dynamic linking library - development tools
+Version: @VERSION@
diff --git a/subprojects/libcapsule/data/libcapsule-tools.pc.in b/subprojects/libcapsule/data/libcapsule-tools.pc.in
new file mode 100644
index 0000000000000000000000000000000000000000..6e38223f6a43b5588ffcc5eb074708b2f76c4425
--- /dev/null
+++ b/subprojects/libcapsule/data/libcapsule-tools.pc.in
@@ -0,0 +1,17 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+libexecdir=@libexecdir@
+datarootdir=@datarootdir@
+datadir=@datarootdir@
+makeinc=@datadir@/libcapsule
+CAPSULE_CAPTURE_LIBS_TOOL=@bindir@/@HOST_PREFIX@capsule-capture-libs
+CAPSULE_INIT_PROJECT_TOOL=@bindir@/capsule-init-project
+CAPSULE_MKSTUBLIB_TOOL=@bindir@/@HOST_PREFIX@capsule-mkstublib
+CAPSULE_SYMBOLS_TOOL=@bindir@/@HOST_PREFIX@capsule-symbols
+CAPSULE_VERSION_TOOL=@bindir@/@HOST_PREFIX@capsule-version
+
+Name: libcapsule-tools
+Description: segregated dynamic linking library - development tools
+Version: @VERSION@
diff --git a/subprojects/libcapsule/data/libcapsule-uninstalled.pc.in b/subprojects/libcapsule/data/libcapsule-uninstalled.pc.in
new file mode 100644
index 0000000000000000000000000000000000000000..eab9321c7858476e36f147770f7aa7d539c69568
--- /dev/null
+++ b/subprojects/libcapsule/data/libcapsule-uninstalled.pc.in
@@ -0,0 +1,10 @@
+libdir=@abs_top_builddir@/capsule
+includedir=@abs_top_srcdir@/capsule
+
+Name: libcapsule
+Description: segregated dynamic linking library
+Version: @VERSION@
+
+Requires:
+Libs: -L${libdir} -lcapsule
+Cflags: -I${includedir}
diff --git a/subprojects/libcapsule/data/libcapsule.pc.in b/subprojects/libcapsule/data/libcapsule.pc.in
new file mode 100644
index 0000000000000000000000000000000000000000..1c5300b5168e7ec26ffe8dc82a0619bc307bcaa4
--- /dev/null
+++ b/subprojects/libcapsule/data/libcapsule.pc.in
@@ -0,0 +1,15 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+libexecdir=@libexecdir@
+datarootdir=@datarootdir@
+datadir=@datarootdir@
+
+Name: libcapsule
+Description: segregated dynamic linking library
+Version: @VERSION@
+
+Requires:
+Libs: -L${libdir} -lcapsule
+Cflags: -I${includedir}
diff --git a/subprojects/libcapsule/data/shim-.gitignore b/subprojects/libcapsule/data/shim-.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..bca62216bdb700810ee6ec5cf67bfd115d53ea86
--- /dev/null
+++ b/subprojects/libcapsule/data/shim-.gitignore
@@ -0,0 +1,21 @@
+*.a
+*.la
+*.lo
+*.o
+.deps/
+.dirstamp
+.libs/
+/Makefile
+/Makefile.in
+/aclocal.m4
+/autom4te.cache/
+/build-aux/
+/capsule-shim.mk
+/config.log
+/config.status
+/configure
+/libtool
+/m4/
+/preconfigure.log
+/shim/*.so.*.c
+/shim/*.so.*.map
diff --git a/subprojects/libcapsule/data/shim-Makefile.am b/subprojects/libcapsule/data/shim-Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..a25dacf1d3d58ed119e4b80c4ad940bc798ec863
--- /dev/null
+++ b/subprojects/libcapsule/data/shim-Makefile.am
@@ -0,0 +1,17 @@
+# vim:set ft=automake:
+# Processed by capsule-init-project. Everything before the first blank
+# line is removed during processing.
+
+AUTOMAKE_OPTIONS = subdir-objects
+ACLOCAL_AMFLAGS  = -I m4
+AM_CFLAGS        = -std=c99 -D_GNU_SOURCE -Wall -Werror
+AM_CFLAGS       += $(CAPSULE_CFLAGS)
+AM_CAPSULE_MKSTUBLIB_FLAGS :=
+
+CAPSULE_SONAMES :=
+lib_LTLIBRARIES =
+CLEANFILES      =
+BUILT_SOURCES   =
+
+include @CAPSULE_MKINC@/disabled.mk
+include @CAPSULE_MKINC@/capsule-shim.mk
diff --git a/subprojects/libcapsule/data/shim-README.in b/subprojects/libcapsule/data/shim-README.in
new file mode 100644
index 0000000000000000000000000000000000000000..d40105106a72dc0cef18961c97c6adb97e8d2acd
--- /dev/null
+++ b/subprojects/libcapsule/data/shim-README.in
@@ -0,0 +1,27 @@
+# Processed by capsule-init-project. Everything before the first blank
+# line is removed during processing.
+
+This is a libcapsule based proxy library.
+
+Its purpose is to allow a library (@TARGET@) to be loaded from a
+foreign filesystem tree (usually /host, but configurable at compile
+time) without exposing any other libraries from that tree to the
+program using it.
+
+You can export the symbols of more than one library via the proxy:
+To do so, add a line containing the SONAME (libFOO.so.X)
+of each extra library to shim/@TARGET@.c.shared and rebuild.
+
+You can also prevent a library from the foreign tree from being loaded
+at all (at least as a result of being a dependency of @TARGET@).
+To achieve this, add a line containing the SONAME (libFOO.so.X)
+of the excluded library to shim/@TARGET@.c.excluded and rebuild.
+
+The generated library will have the same name as the main proxy target
+(@TARGET@). You can control the exact version number (cf libfoo.x.y.z)
+by setting CAPSULE_VERSION_@AMTARGET@ in Makefile.am
+
+If libcapsule's standard implementation of dlopen() for callers inside
+the capsule is not suitable, copy capsule/_int_dlopen.h from
+libcapsule's development files into shim/capsule/, modify it as
+necessary, and add it to EXTRA_DIST in Makefile.am.
diff --git a/subprojects/libcapsule/data/shim-configure.ac.in b/subprojects/libcapsule/data/shim-configure.ac.in
new file mode 100644
index 0000000000000000000000000000000000000000..ef685b3fe926c7f39e31e517c17338642b97b151
--- /dev/null
+++ b/subprojects/libcapsule/data/shim-configure.ac.in
@@ -0,0 +1,50 @@
+# vim:set ft=config:
+# Processed by capsule-init-project. Everything before the first blank
+# line is removed during processing.
+
+AC_PREREQ([2.60])
+
+AC_INIT([@PACKAGE@], [@VER@])
+AC_CONFIG_AUX_DIR([build-aux])
+AC_CONFIG_MACRO_DIR([m4])
+
+dnl don't kvetch about gnu makefile syntax
+dnl and don't require ChangeLog &co if they don't exist
+AM_INIT_AUTOMAKE([-Wno-portability foreign])
+AM_SILENT_RULES([yes])
+AC_USE_SYSTEM_EXTENSIONS
+AC_SYS_LARGEFILE
+
+LT_INIT([disable-static])
+
+PKG_CHECK_MODULES([CAPSULE], [libcapsule])
+m4_ifndef([PKG_CHECK_VAR],
+          [AC_DEFUN([PKG_CHECK_VAR],
+                    [AC_ARG_VAR([$1])
+                     : ${$1:=$PKG_CONFIG --variable="$3" "$2"}])])
+PKG_CHECK_VAR([CAPSULE_MKINC], [libcapsule-tools], [makeinc])
+PKG_CHECK_VAR([CAPSULE_MKSTUBLIB_TOOL], [libcapsule-tools], [CAPSULE_MKSTUBLIB_TOOL])
+PKG_CHECK_VAR([CAPSULE_SYMBOLS_TOOL], [libcapsule-tools], [CAPSULE_SYMBOLS_TOOL])
+
+AC_ARG_WITH([runtime-tree],
+            [AS_HELP_STRING([--with-runtime-tree=PATH],
+                            [Where to find encapsulated libraries at runtime [@RUNTIME_TREE@]])],
+            [], [with_runtime_tree=${CAPSULE_RUNTIME_TREE:-${CAPSULE_TREE:-@RUNTIME_TREE@}}])
+AS_CASE([$with_runtime_tree],
+        [/*],
+            [],
+            [AC_MSG_ERROR([--with-runtime-tree requires an absolute path argument])])
+AC_SUBST([CAPSULE_RUNTIME_TREE], [$with_runtime_tree])
+
+AC_ARG_WITH([search-tree],
+            [AS_HELP_STRING([--with-search-tree=PATH],
+                            [Where to find encapsulated libraries when updating symbols list [@SEARCH_TREE@]])],
+            [], [with_search_tree=${CAPSULE_SEARCH_TREE:-${CAPSULE_TREE:-@SEARCH_TREE@}}])
+AS_CASE([$with_search_tree],
+        [/*],
+            [],
+            [AC_MSG_ERROR([--with-search-tree requires an absolute path argument])])
+AC_SUBST([CAPSULE_SEARCH_TREE], [$with_search_tree])
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/subprojects/libcapsule/data/shim-target-Makefile.am b/subprojects/libcapsule/data/shim-target-Makefile.am
new file mode 100644
index 0000000000000000000000000000000000000000..1c439897da4baae2f1bd914cde90e0fc91641052
--- /dev/null
+++ b/subprojects/libcapsule/data/shim-target-Makefile.am
@@ -0,0 +1,19 @@
+# vim:set ft=automake:
+# Processed by capsule-init-project. Everything before the first blank
+# line is removed during processing; the second blank line is to
+# provide some spacing between target libraries.
+
+
+# -------- @TARGET@ --------
+
+CAPSULE_SONAMES += @TARGET@
+CAPSULE_VERSION_@AMTARGET@ := @TARGET_VER@
+lib_LTLIBRARIES += lib@LIB@.la
+
+nodist_lib@AMLIB@_la_SOURCES = shim/@TARGET@.c
+lib@AMLIB@_la_LDFLAGS = $(shim_ldflags_@AMTARGET@)
+lib@AMLIB@_la_LIBADD = $(CAPSULE_LIBS)
+
+CLEANFILES     += $(nodist_lib@AMLIB@_la_SOURCES) \
+                  $(nodist_lib@AMLIB@_la_SOURCES:.c=.map)
+BUILT_SOURCES  += $(nodist_lib@AMLIB@_la_SOURCES)
diff --git a/subprojects/libcapsule/debian/.gitignore b/subprojects/libcapsule/debian/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..642ca1661ba18469d15e5c5a924d12d33a003c60
--- /dev/null
+++ b/subprojects/libcapsule/debian/.gitignore
@@ -0,0 +1,18 @@
+/*.debhelper
+/*.log
+/*.substvars
+/autoreconf*
+/files
+/libGL-proxy/
+/libGL-proxy-build/
+/libcapsule-dev/
+/libcapsule-dev-bin
+/libcapsule-doc/
+/libcapsule-gl-shims/
+/libcapsule-tools/
+/libcapsule-tools-relocatable/
+/libcapsule-libgl1-shims/
+/libcapsule-tests/
+/libcapsule0/
+/*-stamp
+/tmp/
diff --git a/subprojects/libcapsule/debian/changelog b/subprojects/libcapsule/debian/changelog
new file mode 100644
index 0000000000000000000000000000000000000000..2c3eadea86e162033cd1d9a77404d86256efcf60
--- /dev/null
+++ b/subprojects/libcapsule/debian/changelog
@@ -0,0 +1,276 @@
+libcapsule (0.20210114.0-0co1) upstream; urgency=medium
+
+  * ldlibs: Try loading Clear Linux ld.so cache (/var/cache/ldconfig).
+    Partially addresses ValveSoftware/steam-runtime#345
+  * tests: Silence some Perl warnings
+  * tests: Ignore symbol-versions in nm output.
+    The Debian 11 toolchain produces them like this.
+
+ -- Simon McVittie <smcv@collabora.com>  Thu, 14 Jan 2021 14:57:29 +0000
+
+libcapsule (0.20210104.0-0co1) upstream; urgency=medium
+
+  * ld-libs: Try loading Exherbo architecture-specific ld.so cache
+    before generic path.
+    I'm using Exherbo as an example of a host OS with an unusual layout that
+    breaks libcapsule's normal assumptions. As well as fixing Exherbo
+    itself, this puts the framework in place to deal with other
+    distributions' architecture-specific ld.so caches.
+    - Developers of other distributions with unusual ld.so cache paths
+      can edit ld_cache_filenames in utils/ld-cache.c and bind_usr() in
+      tests/CapsuleTest.pm to teach libcapsule about their paths.
+  * tests: Use libxml2 instead of libjpeg to test versioned symbols
+  * tests: Make Exherbo architecture-specific ld.so stuff available too
+  * tests: Allow even more variations of what a $libdir looks like
+
+ -- Simon McVittie <smcv@collabora.com>  Mon, 04 Jan 2021 17:08:59 +0000
+
+libcapsule (0.20201120.0-0co1) upstream; urgency=medium
+
+  [ Ludovico de Nittis ]
+  * capture-libs: Add support for remapping link target prefix.
+    If you don't want to change the link target for all the symlinks that
+    will be created, instead of using `--link-target`, it is now possible to
+    use the new `--remap-link-prefix` and just list the directories that
+    should be located under a different target.
+
+  [ Simon McVittie ]
+  * debian: Suppress automatic debug symbols for libcapsule-tools-relocatable.
+    The debug symbols for libcapsule-tools-relocatable would be the same as
+    for libcapsule-tools, making them non-co-installable. Use the environment
+    variable instead of --no-automatic-dbgsym for compatibility with
+    Steam Runtime 1 'scout', which is too old to have that option.
+
+ -- Simon McVittie <smcv@collabora.com>  Fri, 20 Nov 2020 14:09:54 +0000
+
+libcapsule (0.20201022.0-0co1) upstream; urgency=medium
+
+  [ Ludovico de Nittis ]
+  * capture-libs: Skip libs with unmet dependencies when "if exists" flag
+    is set.
+    If we have the CAPTURE_FLAG_IF_EXISTS option flag, we should not
+    propagate an error if we are not able to find some dependencies of a
+    library. Instead we should just skip it.
+    This fixes an error that some users reported because they had some
+    leftover unused libraries from the NVIDIA proprietary driver,
+    and libcapsule reported an error while trying to capture their
+    (missing) dependencies.
+
+ -- Simon McVittie <smcv@collabora.com>  Thu, 22 Oct 2020 10:05:30 +0100
+
+libcapsule (0.20200921.0-0co1) upstream; urgency=medium
+
+  * debian: Delete files that were generated by gtk-doc during dist.
+    This fixes failure to build from the tarball with gtk-doc >= 1.30,
+    when the tarball was generated with older gtk-doc.
+
+ -- Simon McVittie <smcv@collabora.com>  Mon, 21 Sep 2020 19:17:22 +0100
+
+libcapsule (0.20200908.0-0co1) upstream; urgency=medium
+
+  [ Ludovico de Nittis ]
+  * utils: Add public/private knowledge about symbol versions and symbols.
+    We are now able to list the symbol versions and/or symbols that are
+    known to be public/private.
+    This is especially useful for comparing libraries that removed symbol
+    versions, or symbols, from one release to another. As this already
+    happened multiple times in the past, like for example with
+    `libdrm_nouveau.so.2` or `libedit.so.2`.
+
+ -- Simon McVittie <smcv@collabora.com>  Tue, 08 Sep 2020 18:48:30 +0100
+
+libcapsule (0.20200708.0-0co1) upstream; urgency=medium
+
+  [ Ludovico de Nittis, Simon McVittie ]
+  * utils: Add functions to compare symbols and versions with libelf.
+    When two libraries have the same numeric tail we were not able to
+    reliably determine which one was the newer. In particular, many
+    distributions install libgcc_s.so.1 as a regular file, rather than
+    a symlink to a versioned name, so library_cmp_by_name() can't work.
+
+  [ Simon McVittie ]
+  * ci: Run CI with UndefinedBehaviourSanitizer and AddressSanitizer
+  * tests: Work around a binutils behaviour change (see #964457)
+  * capture-libs: Make library comparison configurable on the command-line.
+    This is primarily useful for testing and experimenting.
+  * capture-libs: Add --library-knowledge option.
+    This gives capsule-capture-libs a source of library-specific knowledge,
+    which we can use to choose the correct library. (T16562)
+
+ -- Simon McVittie <smcv@collabora.com>  Wed, 08 Jul 2020 13:49:28 +0100
+
+libcapsule (0.20200624.0-0co1) upstream; urgency=medium
+
+  * ld-libs: Always clear ldlibs->needed entry if ld_lib_open() fails,
+    fixing mishandling of libraries that are newer in the container but
+    only for one architecture
+  * build: Check that the compiler and --host are consistent
+  * build: Silence unwanted warnings
+  * Add basic Gitlab-CI on Debian (with deb-build-snapshot) and Arch Linux
+  * capture-libs: Expand list of known ld.so names
+  * capture-libs: Allow patterns to be read from a file
+  * tests: Fix Debianisms
+  * tests/capture-libs: Tolerate path to ld.so not being fully resolved
+  * capsule-malloc: Remove duplicate typedef for mstate, for clang 8
+  * Distinguish between enabling the library and enabling shared libraries.
+    Some tests for capsule-capture-libs will need to compile shared
+    libraries, even if libcapsule itself is not built.
+  * tests: Make older linkers happy with one of our test libraries
+  * tests: Cope with newer versions of GNU make
+  * debian: Tell git-version-gen that we are behaving as an upstream
+
+ -- Simon McVittie <smcv@collabora.com>  Wed, 24 Jun 2020 19:06:02 +0100
+
+libcapsule (0.20190926.0-0co1) upstream; urgency=medium
+
+  * utils: Add --help, --version and GNU argument parsing to all tools
+  * capture-libs: Clarify by introducing more temporaries
+  * capture-libs: Factor out library_cmp_by_name()
+  * capture-libs: Treat unversioned libraries as in indeterminate order.
+    This avoids always choosing the container version of libgcc_s.so.1
+    when using a Debian-derived container on Fedora, even if the host
+    version is newer.
+
+ -- Simon McVittie <smcv@collabora.com>  Thu, 26 Sep 2019 16:45:42 +0100
+
+libcapsule (0.20190724.0-0co1) upstream; urgency=medium
+
+  * process_pt_dynamic: Add missing 'break' to DT_JMPREL case
+  * safe_strncpy: Work around -Werror=stringop-truncation
+  * Add some notes on building from git on older host systems
+  * capture-libs: Fix a misleading debug message
+  * capture-libs: Don't capture results of path-match if they are other ABIs
+
+ -- Simon McVittie <smcv@collabora.com>  Wed, 24 Jul 2019 17:13:32 +0100
+
+libcapsule (0.20190402.0-0co1) upstream; urgency=medium
+
+  * debian: Install tools with canonicalized multiarch prefix
+  * debian: Build relocatable versions of libcapsule binary tools
+  * debian: Use dh_missing --fail-missing
+  * Build *.1 man pages with xsltproc even if gtk-doc is disabled
+  * shim: Add a minimal fallback version of PKG_CHECK_VAR
+  * Skip GLib tests when GLib is older than 2.38
+
+ -- Simon McVittie <smcv@collabora.com>  Tue, 02 Apr 2019 17:32:19 +0100
+
+libcapsule (0.20190130.0-0co1) upstream; urgency=medium
+
+  * Be more compatible with older distributions:
+    - debian: Be compatible with Ubuntu 14.04's dpkg
+    - Compile on older glibc that didn't have secure_getenv()
+    - dump: Avoid naming collision with basename()
+    - capture-libs: Avoid shadowing global abs()
+    - process-pt-dynamic: Don't print name of R_386_SIZE32 if undefined
+    - devhelp2man: Add minimal support for older gtk-doc versions
+    - debian: Install libglib2.0-dev if building documentation,
+      as required by older gtk-doc versions
+    - debian: Use macros from libcapsule itself to build libGL proxy
+    - tests/utils.c: Tolerate archaic GLib versions
+  * autogen.sh: Install macros into m4/
+  * capsule-init-project: Create m4/
+  * debian: Separate documentation into libcapsule-doc
+  * debian: Don't build with fatal warnings
+  * debian: Explicitly disable silent makefile rules,
+    which debhelper didn't always do for us
+  * build: Be more specific about the tokens we don't want to see,
+    to future-proof against newer autoconf-archive versions
+  * ci: Check out the right git repository
+  * CapsuleTest: Tolerate bwrap not being installed at all
+  * tests/capture-libs.pl: Make executable
+
+ -- Simon McVittie <smcv@collabora.com>  Wed, 30 Jan 2019 15:48:43 +0000
+
+libcapsule (0.20180517.0-0co1) upstream; urgency=medium
+
+  * build: Bundle updated m4 macros in source releases so that they can
+    be built on older host OSs that do not have a recent autoconf-archive
+  * capture-libs test: Cope with i686, cmov CPU capability flags
+  * tests: Fix perl warnings for shadowing a local variable
+  * Fix cross-compilation for i386 on amd64:
+    - Architecture-qualify perl as :any
+    - Only build GLib-based tests on amd64 since libglib2.0-dev was not
+      multiarch co-installable until recently
+    - tests: Work when compiling for i386 on amd64
+  * ci: Run autopkgtests on Debian stretch and SteamOS brewmaster
+
+ -- Simon McVittie <smcv@collabora.com>  Thu, 17 May 2018 21:35:21 +0100
+
+libcapsule (0.20180430.0-0co1) upstream; urgency=medium
+
+  [ Vivek Das Mohapatra ]
+  * Fix a potential segfault in the ELF memory dumper
+  * Replace the free/*alloc cluster inside the capsule
+  * Improve API documentation
+  * Improve debug output
+  * debian: Ignore some package/build debris
+
+  [ Simon McVittie ]
+  * Add capture-libs tool and use it for tests/manual/gl.pl
+  * Don't stop reading symbol tables at an unknown ELFW_ST_TYPE, ELFW_ST_BIND
+  * Improve error propagation
+  * Improve debug output
+  * utils: Use getenv rather than secure_getenv to reduce glibc dependencies
+    (libcapsule itself still uses secure_getenv)
+  * libGL: Use the symbol set from GLVND libGL
+  * libGL: Add shims for libXi, libgbm, libGLX, libEGL, libOpenGL, libGLESv2
+  * libGL: Don't exclude misc X libraries from encapsulation
+  * tests/manual/gl.pl: Bind-mount more host files into the container
+  * tests/manual/gl.pl: Merge /gl and /updates into /overrides
+  * tests/manual/gl.pl: Make anything from the host available via /run/host
+  * build: Install capsule-elf-dump
+  * build: Consolidate lists of GL shims using patsubst
+  * build: Add maintainer-update-gl-shims convenience target
+  * build: Make host prefix for tools modifiable
+  * build: Add a build option to add a -rpath to the tools
+  * build: Allow shared library to be disabled, if you only want the tools
+  * build: Don't run a test program, just link it, to help cross-compiling
+  * ci: Add a Jenkinsfile to build libcapsule on SteamOS
+  * debian: Add more test dependencies
+
+ -- Simon McVittie <smcv@collabora.com>  Mon, 30 Apr 2018 14:35:22 +0100
+
+libcapsule (0.20171116.0-0co1) upstream; urgency=medium
+
+  [ Vivek Das Mohapatra ]
+  * Fix NULL dereferences after dlclose()
+  * Improve dlsym() emulation
+
+  [ Simon McVittie ]
+  * tests/manual/gl.pl: Provide finer-grained control over libraries
+  * tests/manual/gl.pl: Provide shortcuts for plausible approaches to
+    Mesa and Nvidia graphics stacks
+  * Adjust packaging so libcapsule-tools can be installed without
+    libcapsule-dev, for better ability to test multiarch containers
+    - capsule.pc has been replaced by libcapsule.pc (library)
+      and libcapsule-tools.pc (tools)
+
+ -- Simon McVittie <smcv@collabora.com>  Thu, 16 Nov 2017 13:42:22 +0000
+
+libcapsule (0.20171113.0-0co1) upstream; urgency=medium
+
+  [ Vivek Das Mohapatra ]
+  * Allow multiple encapsulated libraries, sharing one namespace per prefix
+  * Generate working Autotools projects for libraries with hyphen/minus in
+    their names
+  * Handle DT_REL entries
+  * Cope with shim libraries being dlclose()d
+
+  [ Simon McVittie ]
+  * Change capsule-init-project invocation so it can generate multiple
+    shim libraries per Autotools project
+  * Mark libcapsule as non-deletable so its exported functions can never
+    disappear from library users' relocations
+  * Always use the same dlopen() implementation outside the capsule
+  * Automatically exclude glibc from encapsulation
+  * Install libGL shims in a new libcapsule-libgl1-shims binary package
+  * Install development tools into $(bindir)
+
+ -- Simon McVittie <smcv@collabora.com>  Mon, 13 Nov 2017 18:57:50 +0000
+
+libcapsule (0.20171019.0-0co1) upstream; urgency=medium
+
+  [ Vivek Das Mohapatra ]
+  * Initial release
+
+ -- Simon McVittie <smcv@collabora.com>  Thu, 19 Oct 2017 16:18:55 +0100
diff --git a/subprojects/libcapsule/debian/clean b/subprojects/libcapsule/debian/clean
new file mode 100644
index 0000000000000000000000000000000000000000..13ce54a46a8b98deee3978a8fe7ab20209a1ea3d
--- /dev/null
+++ b/subprojects/libcapsule/debian/clean
@@ -0,0 +1,4 @@
+gtk-doc.make
+libcapsule-docs.xml
+libcapsule-overrides.txt
+libcapsule-sections.txt
diff --git a/subprojects/libcapsule/debian/compat b/subprojects/libcapsule/debian/compat
new file mode 100644
index 0000000000000000000000000000000000000000..ec635144f60048986bc560c5576355344005e6e7
--- /dev/null
+++ b/subprojects/libcapsule/debian/compat
@@ -0,0 +1 @@
+9
diff --git a/subprojects/libcapsule/debian/control b/subprojects/libcapsule/debian/control
new file mode 100644
index 0000000000000000000000000000000000000000..8872be41fe91c00031db47b4b9cf8e9218d988b7
--- /dev/null
+++ b/subprojects/libcapsule/debian/control
@@ -0,0 +1,157 @@
+Source: libcapsule
+Section: libs
+Priority: optional
+Maintainer: Vivek Das Mohapatra <vivek@collabora.com>
+Standards-Version: 3.9.8
+Build-Depends:
+ autoconf,
+ autoconf-archive <pkg.libcapsule.from-git>,
+ automake,
+ bubblewrap,
+ chrpath,
+ debhelper,
+ dh-autoreconf,
+ gtk-doc-tools,
+ libelf-dev,
+ libipc-run-perl,
+ libglib2.0-0 <!nocheck>,
+ libglib2.0-dev [amd64],
+ libxml2 <!nocheck>,
+ perl:any | perl,
+ xsltproc,
+ zlib1g <!nocheck>,
+Build-Depends-Indep:
+ libglib2.0-dev,
+Vcs-Git: https://gitlab.collabora.com/vivek/libcapsule.git
+Vcs-Browser: https://gitlab.collabora.com/vivek/libcapsule
+Homepage: https://gitlab.collabora.com/vivek/libcapsule
+
+Package: libcapsule-dev
+Architecture: amd64 i386
+Multi-Arch: same
+Section: libdevel
+Depends:
+ libc6-dev,
+ libcapsule0 (= ${binary:Version}),
+ libelf-dev,
+Description:
+ libcapsule is a helper library used to implement segregated run-time
+ dynamic linking proxy libraries - used (for example) to load a library
+ that has dependencies incompatible with the regular libraries that are
+ part of the host OS, such as an incompatible libstdc++ version.
+ .
+ This package contains the files needed to build libcapsule proxy libraries.
+
+Package: libcapsule-doc
+Architecture: all
+Multi-Arch: foreign
+Section: doc
+Depends:
+ ${misc:Depends},
+Description:
+ libcapsule is a helper library used to implement segregated run-time
+ dynamic linking proxy libraries - used (for example) to load a library
+ that has dependencies incompatible with the regular libraries that are
+ part of the host OS, such as an incompatible libstdc++ version.
+ .
+ This package contains documentation.
+
+Package: libcapsule-tools
+Architecture: amd64 i386
+Multi-Arch: same
+Section: libdevel
+Depends:
+ ${shlibs:Depends},
+Recommends:
+ libcapsule-dev (= ${binary:Version}),
+Breaks: libcapsule-dev-bin (<< ${binary:Version}),
+Replaces: libcapsule-dev-bin (<< ${binary:Version}),
+Description:
+ libcapsule is a helper library used to implement segregated run-time
+ dynamic linking proxy libraries - used (for example) to load a library
+ that has dependencies incompatible with the regular libraries that are
+ part of the host OS, such as an incompatible libstdc++ version.
+ .
+ This package contains the files needed to generate libcapsule proxy libraries.
+
+Package: libcapsule-tools-relocatable
+Architecture: amd64 i386
+Multi-Arch: same
+Section: misc
+Depends:
+ ${shlibs:Depends},
+Description:
+ libcapsule is a helper library used to implement segregated run-time
+ dynamic linking proxy libraries - used (for example) to load a library
+ that has dependencies incompatible with the regular libraries that are
+ part of the host OS, such as an incompatible libstdc++ version.
+ .
+ This package contains relocatable versions of the tools used to enumerate
+ library symbols and dependencies. After building this package on a system
+ with the oldest glibc that is to be supported, they can be bundled in
+ projects that need to run on an arbitrary host system and make use of
+ libcapsule to prepare a container. To do this, the relocatable tools
+ must be copied to PATH/bin/TUPLE-capsule-TOOL, and their required
+ non-glibc libraries (libelf and zlib) must be copied to PATH/lib/TUPLE.
+
+Package: libcapsule-gl-shims
+Architecture: amd64 i386
+Multi-Arch: same
+Section: libs
+Breaks: libcapsule-libgl1-shims (<< ${binary:Version}),
+Replaces: libcapsule-libgl1-shims (<< ${binary:Version}),
+Description: OpenGL-related shim libraries
+ libcapsule is a helper library used to implement segregated run-time
+ dynamic linking proxy libraries - used (for example) to load a library
+ that has dependencies incompatible with the regular libraries that are
+ part of the host OS, such as an incompatible libstdc++ version.
+ .
+ This package contains libcapsule proxy libraries for libGL, libGLX,
+ libOpenGL and related libraries.
+
+Package: libcapsule-libgl1-shims
+Architecture: amd64 i386
+Multi-Arch: same
+Section: libs
+Depends: libcapsule-gl-shims
+Description: OpenGL-related shim libraries - transitional package
+ libcapsule is a helper library used to implement segregated run-time
+ dynamic linking proxy libraries - used (for example) to load a library
+ that has dependencies incompatible with the regular libraries that are
+ part of the host OS, such as an incompatible libstdc++ version.
+ .
+ This transitional package depends on libcapsule-gl-shims.
+
+Package: libcapsule-tests
+Architecture: amd64 i386
+Section: misc
+Depends:
+ bubblewrap,
+ libcapsule-gl-shims (= ${binary:Version}),
+ libipc-run-perl,
+ libsort-versions-perl,
+ mesa-utils,
+ perl,
+ pkg-config,
+ ${shlibs:Depends},
+Description:
+ libcapsule is a helper library used to implement segregated run-time
+ dynamic linking proxy libraries - used (for example) to load a library
+ that has dependencies incompatible with the regular libraries that are
+ part of the host OS, such as an incompatible libstdc++ version.
+ .
+ This package contains automated tests.
+
+Package: libcapsule0
+Architecture: amd64 i386
+Multi-Arch: same
+Section: libs
+Depends:
+ ${shlibs:Depends},
+Description: Segregated run-time linker library
+ libcapsule is a helper library used to implement segregated run-time
+ dynamic linking proxy libraries - used (for example) to load a library
+ that has dependencies incompatible with the regular libraries that are
+ part of the host OS, such as an incompatible libstdc++ version.
+ .
+ This package contains the run-time component(s) of libcapsule.
diff --git a/subprojects/libcapsule/debian/copyright b/subprojects/libcapsule/debian/copyright
new file mode 100644
index 0000000000000000000000000000000000000000..74a5eff2c220bcbde0b18a44904413af9fcca8dc
--- /dev/null
+++ b/subprojects/libcapsule/debian/copyright
@@ -0,0 +1,88 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Upstream-Name: libcapsule
+Source: https://gitlab.collabora.com/vivek/libcapsule
+
+Files:
+ *
+ debian/*
+Copyright:
+ © 1999-2017 Free Software Foundation, Inc.
+ © 2017 Collabora Ltd.
+License: LGPL-2.1+
+
+Files:
+ tests/CapsuleTestDpkg.pm
+Copyright:
+ © 2007 Raphaël Hertzog
+ © 2009-2010 Modestas Vainius
+License: GPL-2+
+
+Files:
+ build-aux/git-version-gen
+Copyright:
+ © 2007-2017 Free Software Foundation, Inc.
+License: GPL-3+
+
+Files:
+ m4/ax_is_release.m4
+ m4/capsule_matching_abi.m4
+Copyright:
+ © 2015 Philip Withnall
+ © 2016-2019 Collabora Ltd.
+License: Autoconf-permissive
+
+License: LGPL-2.1+
+ libcapsule is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of the
+ License, or (at your option) any later version.
+ .
+ libcapsule 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 Lesser General Public License for more details.
+ .
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+Comment:
+ On Debian systems, the full text of the GNU Lesser General Public License
+ version 2.1 can be found in the file '/usr/share/common-licenses/LGPL-2.1'.
+
+License: GPL-2+
+ 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, see <https://www.gnu.org/licenses/>.
+Comment:
+ On Debian systems, the full text of the GNU General Public License
+ version 2 can be found in the file '/usr/share/common-licenses/GPL-2'.
+
+License: GPL-3+
+ 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 3 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, see <https://www.gnu.org/licenses/>.
+Comment:
+ On Debian systems, the full text of the GNU General Public License
+ version 3 can be found in the file '/usr/share/common-licenses/GPL-3'.
+
+License: Autoconf-permissive
+ Copying and distribution of this file, with or without modification, are
+ permitted in any medium without royalty provided the copyright notice
+ and this notice are preserved.
diff --git a/subprojects/libcapsule/debian/git-version-gen.control b/subprojects/libcapsule/debian/git-version-gen.control
new file mode 100644
index 0000000000000000000000000000000000000000..59073ac4c5c3c37bd7e547f99946fc83186fb0b8
--- /dev/null
+++ b/subprojects/libcapsule/debian/git-version-gen.control
@@ -0,0 +1 @@
+Upstream: yes
diff --git a/subprojects/libcapsule/debian/libcapsule-dev.examples b/subprojects/libcapsule/debian/libcapsule-dev.examples
new file mode 100644
index 0000000000000000000000000000000000000000..06bcb71ab4c700205540493a652834d6adc50790
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule-dev.examples
@@ -0,0 +1 @@
+debian/libGL-proxy
diff --git a/subprojects/libcapsule/debian/libcapsule-dev.install b/subprojects/libcapsule/debian/libcapsule-dev.install
new file mode 100644
index 0000000000000000000000000000000000000000..d1c0b917557de72ed1d518fc717ad2637b45e07d
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule-dev.install
@@ -0,0 +1,4 @@
+usr/include/*
+usr/lib/*/libcapsule.a
+usr/lib/*/libcapsule.so
+usr/lib/*/pkgconfig/libcapsule.pc
diff --git a/subprojects/libcapsule/debian/libcapsule-doc.install b/subprojects/libcapsule/debian/libcapsule-doc.install
new file mode 100644
index 0000000000000000000000000000000000000000..d2f34faa31564833410a6890b874202d8cb64f14
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule-doc.install
@@ -0,0 +1,3 @@
+usr/share/doc/libcapsule/* usr/share/doc/libcapsule-dev
+usr/share/gtk-doc/html/libcapsule/*
+usr/share/man/man3/*
diff --git a/subprojects/libcapsule/debian/libcapsule-gl-shims.install b/subprojects/libcapsule/debian/libcapsule-gl-shims.install
new file mode 100644
index 0000000000000000000000000000000000000000..05743115ad1e72a37cc9747e8fbef14ff218ce2a
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule-gl-shims.install
@@ -0,0 +1 @@
+usr/lib/libcapsule/shims
diff --git a/subprojects/libcapsule/debian/libcapsule-tests.install b/subprojects/libcapsule/debian/libcapsule-tests.install
new file mode 100644
index 0000000000000000000000000000000000000000..c564fec23484316244c5a664df78fe055ed9f97e
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule-tests.install
@@ -0,0 +1,2 @@
+usr/lib/libcapsule/installed-tests/libcapsule
+usr/share/installed-tests/libcapsule
diff --git a/subprojects/libcapsule/debian/libcapsule-tools-relocatable.install b/subprojects/libcapsule/debian/libcapsule-tools-relocatable.install
new file mode 100644
index 0000000000000000000000000000000000000000..9084c938a29f73f325125b8df88139f89957a8b8
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule-tools-relocatable.install
@@ -0,0 +1 @@
+usr/lib/libcapsule/relocatable
diff --git a/subprojects/libcapsule/debian/libcapsule-tools.install b/subprojects/libcapsule/debian/libcapsule-tools.install
new file mode 100644
index 0000000000000000000000000000000000000000..dab3d19e16369a9c23c3ad97e507ad8d2b826643
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule-tools.install
@@ -0,0 +1,9 @@
+usr/bin/*-capsule-capture-libs
+usr/bin/*-capsule-elf-dump
+usr/bin/*-capsule-mkstublib
+usr/bin/*-capsule-symbols
+usr/bin/*-capsule-version
+usr/bin/capsule-init-project
+usr/lib/*/pkgconfig/libcapsule-tools.pc
+usr/share/libcapsule/*
+usr/share/man/man1/*
diff --git a/subprojects/libcapsule/debian/libcapsule0.install b/subprojects/libcapsule/debian/libcapsule0.install
new file mode 100644
index 0000000000000000000000000000000000000000..a372279df23c9dd4a31b0f47666614ab3b1bffe7
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule0.install
@@ -0,0 +1,2 @@
+usr/lib/*/libcapsule.so.0
+usr/lib/*/libcapsule.so.0.*
diff --git a/subprojects/libcapsule/debian/libcapsule0.symbols b/subprojects/libcapsule/debian/libcapsule0.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..10bc06ffb183ec85f9b4039f25561e3b09f95f3f
--- /dev/null
+++ b/subprojects/libcapsule/debian/libcapsule0.symbols
@@ -0,0 +1,10 @@
+libcapsule.so.0 libcapsule0 #MINVER#
+* Build-Depends-Package: libcapsule-dev
+ capsule_close@Base 0.20180430.0
+ capsule_external_dlopen@Base 0.20180430.0
+ capsule_external_dlsym@Base 0.20180430.0
+ capsule_get_prefix@Base 0.20180430.0
+ capsule_init@Base 0.20180430.0
+ capsule_shim_dlopen@Base 0.20180430.0
+ capsule_shim_free@Base 0.20180430.0
+ capsule_shim_realloc@Base 0.20180430.0
diff --git a/subprojects/libcapsule/debian/rules b/subprojects/libcapsule/debian/rules
new file mode 100755
index 0000000000000000000000000000000000000000..d4d34932d1cbae7dd40ac88e42094d2ace733390
--- /dev/null
+++ b/subprojects/libcapsule/debian/rules
@@ -0,0 +1,133 @@
+#!/usr/bin/make -f
+
+-include /usr/share/dpkg/default.mk
+
+%:
+	dh $@ --with autoreconf
+
+binaries := $(shell dh_listpackages)
+fake_rpath :=  /_ORIGIN_/__/lib/${DEB_HOST_MULTIARCH}
+real_rpath := $${ORIGIN}/../lib/${DEB_HOST_MULTIARCH}
+confflags = \
+	--disable-Werror \
+	--disable-silent-rules \
+	--enable-host-prefix=$(DEB_HOST_MULTIARCH)- \
+	--enable-tools-rpath='${fake_rpath}' \
+	--libexecdir=\$${exec_prefix}/lib/libcapsule \
+	$(NULL)
+
+ifneq ($(filter libcapsule-doc,$(binaries)),)
+confflags += --enable-gtk-doc
+else
+confflags += --disable-gtk-doc
+endif
+
+ifeq ($(DEB_HOST_ARCH),amd64)
+confflags += --with-glib=auto
+else
+confflags += --without-glib
+endif
+
+override_dh_clean:
+	rm -fr html
+	dh_clean
+
+override_dh_auto_configure:
+	dh_auto_configure -- $(confflags)
+
+proxy_vars = \
+	CAPSULE_CFLAGS="-I$(CURDIR)" \
+	CAPSULE_LIBS="-L$(CURDIR)/.libs -lcapsule" \
+	CAPSULE_MKINC="$(CURDIR)/data" \
+	CAPSULE_MKSTUBLIB_TOOL="$(CURDIR)/data/capsule-mkstublib" \
+	CAPSULE_SYMBOLS_TOOL="$(CURDIR)/capsule-symbols" \
+	CAPSULE_VERSION_TOOL="$(CURDIR)/capsule-version" \
+	$(NULL)
+
+override_dh_auto_build:
+	dh_auto_build
+	$(proxy_vars) ./data/capsule-init-project \
+		--capsule-pkgdatadir="$(CURDIR)/data" \
+		--destination="$(CURDIR)/debian/libGL-proxy" \
+		--no-autoreconf \
+		--package-name=libGL-proxy \
+		--search-tree=/nonexistent \
+		--symbols-from-dir="$(CURDIR)/examples/shim" \
+		libGL.so.1/1 \
+		libX11.so.6/6 \
+		libXext.so.6/6 \
+		libXi.so.6/6 \
+		libxcb-dri2.so.0/0 \
+		libxcb-glx.so.0/0 \
+		libxcb-present.so.0/0 \
+		libxcb-sync.so.1/1 \
+		libxcb.so.1/1 \
+		$(NULL)
+	# Copy it, so we can install the original source as an example
+	cp -a debian/libGL-proxy debian/libGL-proxy-build
+	cd debian/libGL-proxy-build && autoreconf -fi -I $(CURDIR)/m4
+	cd debian/libGL-proxy-build && ./configure --prefix=/usr $(proxy_vars)
+	$(MAKE) -C debian/libGL-proxy-build V=1 $(proxy_vars)
+
+override_dh_auto_install:
+	dh_auto_install
+	if [ $(DEB_HOST_MULTIARCH) != $(DEB_HOST_GNU_TYPE) ]; then \
+		set -e; \
+		cd debian/tmp/usr/bin; \
+		for bin in $(DEB_HOST_MULTIARCH)-*; do \
+			tail="$${bin#$(DEB_HOST_MULTIARCH)-}"; \
+			ln -s "$$bin" "$(DEB_HOST_GNU_TYPE)-$$tail"; \
+		done; \
+	fi
+	install -d debian/tmp/usr/lib/libcapsule/relocatable
+	cp -a \
+		debian/tmp/usr/bin/*-capsule-capture-libs \
+		debian/tmp/usr/bin/*-capsule-elf-dump \
+		debian/tmp/usr/bin/*-capsule-symbols \
+		debian/tmp/usr/bin/*-capsule-version \
+		debian/tmp/usr/lib/libcapsule/relocatable
+	set -e; for bin in debian/tmp/usr/bin/$(DEB_HOST_MULTIARCH)-*; do \
+		case "$$bin" in \
+			(*-capsule-mkstublib) \
+				;; \
+			(*) \
+				chrpath -d "$$bin"; \
+				chrpath -l "$$bin" || :; \
+				;; \
+		esac; \
+	done
+	chrpath -r '${real_rpath}' \
+		debian/tmp/usr/lib/libcapsule/relocatable/$(DEB_HOST_MULTIARCH)-*
+	chrpath -l debian/tmp/usr/lib/libcapsule/relocatable/*
+	$(MAKE) -C debian/libGL-proxy-build install V=1 $(proxy_vars) \
+		DESTDIR=$(CURDIR)/debian/libGL-proxy-build/shims
+	install -d debian/tmp/usr/lib/libcapsule/shims/lib/$(DEB_HOST_MULTIARCH)
+	mv debian/libGL-proxy-build/shims/usr/lib/* \
+		debian/tmp/usr/lib/libcapsule/shims/lib/$(DEB_HOST_MULTIARCH)
+
+override_dh_install:
+	rm -f debian/tmp/usr/lib/*/*.la
+	rm -f debian/tmp/usr/lib/libcapsule/shims/lib/*/*.la
+	find debian/tmp/usr/lib/libcapsule/installed-tests -name '*.la' -print -delete
+	# Only keep the architecture-qualified version, delete the unprefixed
+	# version, so we can be Multi-Arch: same
+	rm -f debian/tmp/usr/bin/capsule-capture-libs
+	rm -f debian/tmp/usr/bin/capsule-elf-dump
+	rm -f debian/tmp/usr/bin/capsule-symbols
+	rm -f debian/tmp/usr/bin/capsule-version
+	dh_install
+
+# The debug symbols for libcapsule-tools-relocatable would be the same
+# as for libcapsule-tools, making them non-co-installable.
+# Use the environment variable instead of --no-automatic-dbgsym for
+# compatibility with Steam Runtime 1 'scout'.
+override_dh_strip:
+	DEB_BUILD_OPTIONS='$(DEB_BUILD_OPTIONS) noautodbgsym' dh_strip -plibcapsule-tools-relocatable
+	dh_strip --remaining-packages
+
+override_dh_missing:
+	dh_missing --fail-missing
+
+override_dh_autoreconf:
+	NOCONFIGURE=1 dh_autoreconf ./autogen.sh -- $(confflags)
+
diff --git a/subprojects/libcapsule/debian/source/format b/subprojects/libcapsule/debian/source/format
new file mode 100644
index 0000000000000000000000000000000000000000..163aaf8d82b6c54f23c45f32895dbdfdcc27b047
--- /dev/null
+++ b/subprojects/libcapsule/debian/source/format
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/subprojects/libcapsule/debian/tests/control b/subprojects/libcapsule/debian/tests/control
new file mode 100644
index 0000000000000000000000000000000000000000..527b81ae4d305d12c717046a5fe65537b9d18ece
--- /dev/null
+++ b/subprojects/libcapsule/debian/tests/control
@@ -0,0 +1,18 @@
+Tests: gnome-desktop-testing smoke
+Restrictions: allow-stderr
+Depends:
+ autoconf,
+ automake,
+ bubblewrap,
+ build-essential,
+ gnome-desktop-testing,
+ libcapsule-dev,
+ libcapsule-tests,
+ libcapsule-tools,
+ libipc-run-perl,
+ libglib2.0-0,
+ libxml2,
+ libtool,
+ perl,
+ pkg-config,
+ zlib1g,
diff --git a/subprojects/libcapsule/debian/tests/gnome-desktop-testing b/subprojects/libcapsule/debian/tests/gnome-desktop-testing
new file mode 100755
index 0000000000000000000000000000000000000000..2958be2dab55e801a2c51efa85e0fe4added2c6c
--- /dev/null
+++ b/subprojects/libcapsule/debian/tests/gnome-desktop-testing
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+set -e
+exec 2>&1
+exec gnome-desktop-testing-runner libcapsule
diff --git a/subprojects/libcapsule/debian/tests/smoke b/subprojects/libcapsule/debian/tests/smoke
new file mode 100755
index 0000000000000000000000000000000000000000..cd52406f3f8497c9a3343700491f036a839d40cc
--- /dev/null
+++ b/subprojects/libcapsule/debian/tests/smoke
@@ -0,0 +1,55 @@
+#!/usr/bin/perl
+
+# Copyright © 2017 Collabora Ltd.
+#
+# This library is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# This library 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library.  If not, see <http://www.gnu.org/licenses/>.
+
+use autodie;
+use File::Temp qw();
+use IPC::Run qw(run);
+use Test::More;
+
+my $scratch = File::Temp->newdir();
+chdir($scratch);
+
+open(my $fh, '>', 'trivial.c');
+print {$fh} <<'EOF';
+#define _GNU_SOURCE
+
+#include <stdio.h>
+
+#include <capsule/capsule.h>
+
+int
+main (void)
+{
+  char *prefix = capsule_get_prefix( "/host", "libfoo.so.0" );
+  printf( "%s\n", prefix );
+  return 0;
+}
+EOF
+close($fh);
+
+ok(! system('gcc -c -o trivial.o trivial.c $(pkg-config --cflags libcapsule)'),
+    'compiled');
+ok(! system('gcc -o trivial trivial.o $(pkg-config --libs libcapsule)'),
+    'linked');
+ok(-x 'trivial', 'executable');
+my $output;
+$ENV{CAPSULE_LIBFOO_SO_0_PREFIX} = '/foo';
+ok(run(['./trivial'], '>', \$output), 'executed successfully');
+is($output, "/foo\n", 'printed desired output');
+
+chdir('/');
+done_testing;
diff --git a/subprojects/libcapsule/doc/Building-libcapsule.txt b/subprojects/libcapsule/doc/Building-libcapsule.txt
new file mode 100644
index 0000000000000000000000000000000000000000..db3d7c057d964df32a83dafe804a963b6a2dd4f0
--- /dev/null
+++ b/subprojects/libcapsule/doc/Building-libcapsule.txt
@@ -0,0 +1,10 @@
+Building libcapsule from git requires GNU autoconf-archive macros. The
+oldest supported version is 20180313-1 from Debian 9 'stretch'. If your
+development system or container is older, unpack or clone a suitable
+version of autoconf-archive and use commands like:
+
+   libcapsule$ ACLOCAL_PATH=/path/to/autoconf-archive/m4 NOCONFIGURE=1 ./autogen.sh
+   libcapsule$ ./configure
+   libcapsule$ make
+
+Building from tarball releases removes this requirement.
diff --git a/subprojects/libcapsule/doc/Building.txt b/subprojects/libcapsule/doc/Building.txt
new file mode 100644
index 0000000000000000000000000000000000000000..b1dab395df64b2e34f2ba78ccf223b3281cd8bbc
--- /dev/null
+++ b/subprojects/libcapsule/doc/Building.txt
@@ -0,0 +1,191 @@
+libcapsule provides a few tools to help initialise a proxy library project:
+
+Assuming a host tree containing libGL mounted at /host, the simplest route
+to a [hopefully] working project is as follows:
+
+  ~$ $(pkg-config --variable=CAPSULE_INIT_PROJECT_TOOL libcapsule-tools) libGL.so.1 /host
+
+  Generating project for /host : libGL.so.1 1.2.0 proxy
+  Creating project directory libGL-proxy
+  Working in /home/vivek/src/valve/dlmopen/libGL-proxy
+  Extracting dynamic symbols from /host : libGL.so.1
+  Preparing proxy source files
+  Initialising configure.ac
+  Initialising Makefile.am
+  Bootstrapping autoconf templates for libGL.so.1
+  Running initial configuration
+  autoreconf: Entering directory `.'
+  autoreconf: configure.ac: not using Gettext
+  autoreconf: running: aclocal --force 
+  autoreconf: configure.ac: tracing
+  autoreconf: running: libtoolize --copy --force
+  libtoolize: putting auxiliary files in `.'.
+  libtoolize: copying file `./ltmain.sh'
+  libtoolize: putting macros in AC_CONFIG_MACRO_DIR, `m4'.
+  libtoolize: copying file `m4/libtool.m4'
+  libtoolize: copying file `m4/ltoptions.m4'
+  libtoolize: copying file `m4/ltsugar.m4'
+  libtoolize: copying file `m4/ltversion.m4'
+  libtoolize: copying file `m4/lt~obsolete.m4'
+  libtoolize: Consider adding `-I m4' to ACLOCAL_AMFLAGS in Makefile.am.
+  autoreconf: running: /usr/bin/autoconf --force
+  autoreconf: configure.ac: not using Autoheader
+  autoreconf: running: automake --add-missing --copy --force-missing
+  Makefile.am: installing './depcomp'
+  autoreconf: Leaving directory `.'
+  /home/x/src/valve/dlmopen/libGL-proxy is ready for ./configure, \
+      see README for details
+
+This will set up a project to build a proxy for libGL.so.1, expected
+to be found in a foreign filesystem tree mounted at /host.
+
+The target library (the real libGL.so.1) must be present in a well-formed
+tree mounted at /host at the point at which you run this command.
+
+If you ever make changes which require the proxy library source files
+to be regenerated (changing the foreign tree location from /host or
+altering the list of symbols to export) then the foreign tree must
+likewise be present.
+
+However you do not need the tree to be present to build the library itself
+once the project has been initialised.
+
+The layout of the project will be as follows:
+
+
+  ./shim/
+  ./shim/libGL.so.1.shared
+  ./shim/libGL.so.1.dlopen
+  ./shim/libGL.so.1.symbols.updated-for
+  ./shim/libGL.so.1.excluded
+  ./shim/libGL.so.1.dlsym
+  ./configure
+  ./build-aux/…
+  ./m4/…
+  ./README
+  ./dd.log
+  ./preconfigure.log
+  ./Makefile.am
+  ./configure.ac
+  ./aclocal.m4
+  ./arena.log
+  ./Makefile.in
+
+A few of these files are of immediate interest:
+----------------------------------------------------------------------------
+preconfigure.log:
+
+  This records the initial bootstrapping step of the project, which uses
+  autoconf to set up the build system from libcapsule's templates.
+
+  If things are working, there's little of interest here, but if something's
+  not quite right there may be important clues recorded here.
+----------------------------------------------------------------------------
+Makefile.am
+
+  A few important details are recorded here:
+
+  CAPSULE_LIBRARY - The stub of the library name (‘GL’ in this case).
+
+  CAPSULE_VERSION_libfoo.so.0 -
+                    The version number of the proxy library for libfoo.so.0
+                    defaults to the same version as the library in /host
+                    if that can be deduced from the /host tree.
+
+                    Only the major version (major.minor.sub) needs to match,
+                    otherwise you may change this as desired.
+
+  CAPSULE_TREE    - The location of the foreign tree.
+
+                    You may change this, but doing so requires the source
+                    to be regenerated, which requires the new foreign tree
+                    to be present.
+
+  CAPSULE_MAJOR   - Major version of the library we're encapsulating
+----------------------------------------------------------------------------
+shim/libGL.so.1.excluded
+
+  This file should contain a list of libraries which should _NOT_
+  be picked up from the /host tree and isolated in the capsule,
+  one per line.
+
+  The following library is always considered to be excluded by
+  the code generator, so there's no need to add it explicitly:
+
+  libdl.so.2
+
+  Other libraries which you may need to add, depending on the exact
+  API presented by the proxied library, are:
+
+  libc.so.6          - if memory allocated inside the target library
+                       is freed by the main program, or similar.
+
+  libpthread.so.0    - if thread synchronisation or similar occurs
+  libpthread-2.19.so   both inside the proxied library and your main
+                       program AND the two need to be aware of one
+                       another.
+
+
+----------------------------------------------------------------------------
+shim/libGL.so.1.shared
+
+  This file should contain any extra libraries whose symbols should _also_ be
+  exported from the capsule, along with those from the main library libGL.so.
+
+  It will generally be empty, but in libGL.so's case it must contain:
+
+  libxcb.so.1
+  libxcb-glx.so.0
+  libxcb-dri2.so.0
+  libxcb-dri3.so.0
+  libxcb-present.so.0
+  libxcb-sync.so.1
+  libX11.so
+
+  Every time this file is modified, you will need to run
+
+      make maintainer-update-capsule-symbols
+
+  to update the list of exported symbols in shim/libGL.so.1.symbols.
+  This will also update shim/libGL.so.1.symbols.updated-for to record
+  the fact that it does not need updating again. You can use
+
+      make maintainer-update-capsule-symbols CAPSULE_SEARCH_TREE=/sysroot
+
+  to specify a custom root directory to search for these libraries.
+
+----------------------------------------------------------------------------
+shim/capsule/_int_dlopen.h
+
+  This file is not created by default.
+
+  The _int_dlopen wrapper is installed inside the capsule and takes
+  care of any /host path prefixing so that the encapsulated libraries
+  get their native targets with dlmopen (and don't break encapsulation
+  by calling dlopen).
+
+  A default implementation provided by libcapsule is normally used.
+  See libcapsule's <capsule/_int_dlopen.h> for that implementation.
+
+  To use a custom implementation, create the file and implement a
+  function with the same signature:
+
+  static void *_int_dlopen (const char *filename, int flags)
+  {
+    â‹®
+  }
+
+----------------------------------------------------------------------------
+That's it - you should be ready to:
+
+  ./configure
+  make
+
+Your generated capsule libraries will be in .libs,
+and you can use ‘make install’ et al, as usual, to install them.
+
+Note that you should be careful about this, as you are deliberately
+generating a library which has the same name as a ‘real’ one, which
+is very much not a normal situation, and carelessness here could break
+your system.
+
diff --git a/subprojects/libcapsule/doc/Capsules.txt b/subprojects/libcapsule/doc/Capsules.txt
new file mode 100644
index 0000000000000000000000000000000000000000..7b5023e6c8ceb1f336f3ab4403dfb554fd4e08d3
--- /dev/null
+++ b/subprojects/libcapsule/doc/Capsules.txt
@@ -0,0 +1,157 @@
+This document covers the details of how libcapsule manages proxy capsule
+libraries.
+
+For details of how symbol hiding/relocation is done, see Strategy.txt.
+
+============================================================================
+Summary:
+
+Each capsule library acts as a shim for at least one target library, with
+which it shares a soname, and whose symbols it exports to the libraries
+that link to it (both at dynamic link time and via dlopen/dlsym).
+
+Capsules _may_ act as a shim for more than one library, but they can have
+only one soname: This pattern of use was necessary during the early
+development of libcapsule when we supported at most one capsule in any
+given link chain but is no longer required.
+
+============================================================================
+Details:
+
+Each capsule library must
+
+a) depend on libcapsule (ie have libcapsule in a DT_NEEDED entry)
+
+b) #include "capsule/capsule-shim.h"
+
+c) declare a globally visible symbol called capsule_meta which should
+   be a populated struct of type capsule_metadata
+
+   NOTE: it should not attempt to use this variable, only
+   the handle returned by capsule_init(). That handle will be
+   initialised based on the metadata.
+
+d) declare UNVERSIONED_STUB( f ); for each function ‘f’ it wishes to proxy
+
+e) declare a static global variable:
+   static capsule cap;
+
+f) In its constructor:
+
+   assign the return of capsule_init to ‘cap’
+
+g) In its destructor:
+
+   call capsule_close
+
+============================================================================
+Metadata example (for a libGL.so.1 capsule):
+
+static const char *exclude[] =
+{ // MUST NOT be pulled from the capsule
+  // prefixed filesystem tree:
+  "libdl.so.2",
+  NULL
+};
+
+// make sure this symbol has global visibility so
+// that the libcapsule initialisation can find it
+__attribute__ ((visibility("default")))
+capsule_metadata capsule_meta =
+{
+    .capsule_abi      = 0,
+    .soname           = "libGL.so.1",
+    .default_prefix   = "/host",
+    .exclude          = exclude, 
+    .export           = valid_dlsym_sources,
+};
+
+============================================================================
+Sequences of events:
+
+Normal linking
+
+A library or executable using a capsule is loaded
+
+The linker notes that the capsule is listed in DT_NEEDED and loads it
+
+The linker notes that libcapsule is DT_NEEDED by the capsule and loads _that_
+(if it is not already loaded).
+
+Various libraries are loaded - once all DT_NEEDEDs have been satisfied:
+
+The constructors for the loaded DSOs run in reverse dependency order,
+in particular:
+
+The constructor for libcapsule runs, and does the following:
+
+  - harvests metadata from all currently loaded capsules and caches them
+  - caches the addresses of the default implementations of dlopen and dlsym
+  - groups the metadata by filesystem prefix
+    - all capsules that specify /foo as their prefix will be grouped into
+      the same private namespace
+    - a capsule's prefix is determined by (in descending priority)
+      - the soname specific env var (eg CAPSULE_LIBGL_SO_1_PREFIX)
+      - the CAPSULE_PREFIX env var
+      - the static prefix in the capsule's metadata 
+      - NOTE: env vars don't apply here to setuid/setgid processes,
+        or if libcapsule was compiled using glibc older than 2.17
+  - aggregates the exclude and export metadata lists, grouping
+    them by prefix (and deduplicating the resulting lists)
+    - eg the ‘export’ list from a namespace will be all the sonames from
+      all the .export entries from all capsules that share that namespace
+      (as determined by their prefix) 
+
+The constructor for each loaded capsule runs and calls
+capsule_init( static-copy-of-soname ) which does the following:
+
+  - calls _capsule_load to load the real target library
+
+  - calls _capsule_relocate to update the global offset tables (GOTs)
+    of all DSOs outside the capsule to use the real symbols from the
+    target instead of the dummy entries in the capsule
+
+  - calls _capsule_relocate_dlopen to install wrappers for any special-case
+    functions (typically just dlopen - dlsym is handled by _capsule_relocate)
+    that require careful handling.
+
+dlopen() of a capsule (from outside the capsule):
+
+This is similar to normal linking, except:
+
+  - The libcapsule constructor does not run
+  - the capsule's constructor's call to capsule_init triggers a re-harvesting
+    of available metadata, and a re-calculation of the aggregated lists
+  - the _capsule_relocate and _capsule_relocate_dlopen sequences for _all_
+    capsules are triggered
+
+dlopen() of a normal library (from outside the capsule):
+
+  - the _capsule_relocate and _capsule_relocate_dlopen sequences for _all_
+    capsules are triggered
+
+dlopen() of a normal library inside the capsule:
+
+  - secretly translated into a capsule_shim_dlopen call
+
+dlopen() of a capsule from inside the capsule:
+
+  - cats and dogs living together
+  - total chaos
+  - the end of the world
+
+dlsym() from inside the capsule:
+
+  - nothing special, works as normal
+  - this might break if dlsym is called on the NULL handle
+    with the expectation that the calling namespace and
+    public namespace are the same
+
+dlsym() from outside the capsule
+
+  - handled by the wrapper we installed earlier
+  - looks first inside each capsule namespace for the requested symbol
+    - if said symbol is found _and_ comes from a DSO in the export list
+      then it is returned
+    - otherwise we fall back to vanilla dlsym
+
diff --git a/subprojects/libcapsule/doc/Debugging.txt b/subprojects/libcapsule/doc/Debugging.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3ad0ff1a91ec4c724f286a2697107c85a93254ea
--- /dev/null
+++ b/subprojects/libcapsule/doc/Debugging.txt
@@ -0,0 +1,26 @@
+If you wish to debug the library loading & symbol proxying process
+libcapsule exposes a lot of detail depending on the contents of the
+CAPSULE_DEBUG environment variable.
+
+CAPSULE_DEBUG is a comma-or-space separated variable, and currently
+understand the following debug flags:
+
+  path    : path manipulation and translation
+  search  : searching for DSOs
+  ldcache : loading/processing the ld cache
+  capsule : setting up the proxy capsule
+  mprotect: subverting mprotect (for RELRO linking)
+  wrappers: function wrappers installed in the capsule
+  reloc   : patching capsule symbols into external DSOs
+  dlfunc  : dlsym/dlfunc wrapper info as they execute (overlaps wrappers)
+  elf     : detailed ELF introspection logging
+  all     : all of the above
+
+At this point gdb does not yet know about dlmopen()ed DSOs and their
+symbols, so debugging a libcapsule proxy library with gdb isn't very
+convenient. It's on the list of things to fix.
+
+If you are debugging the unit tests, you might want to set
+CAPSULE_TESTS_KEEP_TEMP to a non-empty value so temporary
+directories will not be cleaned up. See tests/CapsuleTest.pm
+or `perldoc -F tests/CapsuleTest.pm` for more details.
diff --git a/subprojects/libcapsule/doc/Limitations.txt b/subprojects/libcapsule/doc/Limitations.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ca6a8b266d6f8892bc4a046ba76a5d7837a203e9
--- /dev/null
+++ b/subprojects/libcapsule/doc/Limitations.txt
@@ -0,0 +1,58 @@
+libcapsule currently has the following limitations:
+
+============================================================================
+
+If there are > 1 copies of a library which handles allocation (eg
+libstdc++) the capsule cannot protect you from an object allocated
+by the encapsulated copy being freed by the public copy.
+
+Some libraries may be OK with this pattern of behaviour, others
+may not.
+
+============================================================================
+
+There can only ever be one copy of libdl shared between the capsule
+and the public namespace. This behaviour is baked into the stub library
+generator script. Probably nothing particularly sane will happen if you
+try to ignore this limitation.
+
+============================================================================
+
+Currently only functions are exported from the capsule. Unlike the
+previous items, this limitation is not intrinsic and could be lifted.
+
+============================================================================
+
+libcapsule relies on the ld.so.cache from the capsule tree to find
+libraries. If the cache is not there, it may still find some libraries
+in the default-standard locations but many paths will end up being ignored.
+
+============================================================================
+
+libcapsule replaces any instances of dlopen in the encapsulated libraries
+with a wrapper around dlmopen: It does this so that a capsule opened on the
+"/host" prefix (for example) continues to use DSOs (libraries and modules)
+from "/host", and to prevent dlopen() calls from within the capsule from
+breaking encapsulation.
+
+NB: Currently a dlopen() call from inside a dlmopen() namespace is unsupported
+by glibc anyway, and would result in a segfault.
+
+============================================================================
+
+Related to the above: The dlmopen wrapper cannot support RTLD_GLOBAL.
+
+If the library you are opening relies on calling dlopen() with RTLD_GLOBAL
+to expose symbols from one DSO to another it will not work.
+
+This is a glibc limitation - it could be lifted but would require
+the dlmopen libc call (and the linker) to be modified.
+
+============================================================================
+
+If you are using Mesa before v 17.x you will need to use `patchelf' v0.9
+or later to add a DT_NEEDED entry for libglapi.so.0 to your dri.so drivers
+in order for the libGL capsule to be able to work (assuming it is
+libGL.so tat you are trying to proxy).
+
+============================================================================
diff --git a/subprojects/libcapsule/doc/Quick-Start.txt b/subprojects/libcapsule/doc/Quick-Start.txt
new file mode 100644
index 0000000000000000000000000000000000000000..29299b60755f4247d1b1e9fe70d4b90d08f2170a
--- /dev/null
+++ b/subprojects/libcapsule/doc/Quick-Start.txt
@@ -0,0 +1,59 @@
+In the top level directory:
+
+  autoreconf -ivf
+  ./configure
+  make
+
+libcapsule.so will be in .libs/
+
+The test environment is currently:
+
+A chroot containing a debian/jessie filesystem with libGL.so removed,
+the libcapsule .deb installed and with the "real" OS filesystem bind
+mounted at /host.
+
+In the chroot:
+
+Generate a proxy project for libGL and related libraries:
+
+  ~$ mkdir proxies
+  ~$ cd proxies
+  ~$ CAPTOOL=$(pkg-config --variable=CAPSULE_INIT_PROJECT_TOOL libcapsule-tools)
+  ~$ LIBS="xcb.so.1 xcb-glx.so.0 xcb-dri2.so.0 xcb-dri3.so.0 xcb-present.so.0"
+  ~$ LIBS="$LIBS xcb-sync.so.1 X11.so.6 GL.so.1 Xext.so.6"
+  ~$ for x in $LIBS; do $CAPTOOL lib$x /host; done
+  ~$ for x in *-proxy; do (cd $x && ./configure --prefix=/shim) || break; done
+  ~$ for x in *-proxy; do (cd $x && make) || break; done
+  ~$ for x in *-proxy; do (cd $x && sudo make install) || break; done
+
+Now make said proxy libraries available. You should also:
+Ensure that ldd $(which glxgears) points to the libGL.so capsule
+Make sure a full filesystem from the host OS is mounted at /host
+
+  ~$ export LD_LIBRARY_PATH=/shim/lib
+  ~$ export DISPLAY=:0 # if you need this
+
+Make sure your chroot is set up so that trivial XOrg programs can run
+(xterm, xev, xeyes or whatever).
+
+Run glxgears (or openarena, or whatever).
+
+If it fails:
+
+ export CAPSULE_DEBUG=path,search,capsule,mprotect,wrappers,ldcache
+ glxgears 2>&1 | tee glxgears.capsule.log
+
+capsule debug flags: 
+  path     # path manipulation and translation
+  search   # searching for DSOs
+  ldcache  # loading/processing the ld cache
+  capsule  # setting up the proxy capsule
+  mprotect # handling mprotect (for RELRO)
+  wrappers # function wrappers installed in the capsule
+  reloc    # patching capsule symbols into external DSOs
+  elf      # detailed ELF introspection logging
+
+NOTE: If your Mesa is older than 17.x, see the final item regarding patchelf
+in the Limitations.txt file for a workaround.
+
+TODO: add instructions for bwrap instead of chroot here.
diff --git a/subprojects/libcapsule/doc/Strategy.txt b/subprojects/libcapsule/doc/Strategy.txt
new file mode 100644
index 0000000000000000000000000000000000000000..af0f8b788780f685d089db441891155aff193066
--- /dev/null
+++ b/subprojects/libcapsule/doc/Strategy.txt
@@ -0,0 +1,279 @@
+Everything You Never Wanted To Know About Dynamic Linking:
+----------------------------------------------------------------------------
+If you have gotten this far you're probably interested in how
+libcapsule does what it does: There's a lot of ground to cover
+and there are many pieces of the puzzle but we'll try to explain
+them all here.
+
+The background:
+----------------------------------------------------------------------------
+First, a simplified outline of dynamic linking: When a program is
+loaded, its type is determined. If it is ‘dynamically linked’, it
+will contain a list of libraries (DSOs or Dynamically Shared Objects)
+that it needs. Each of these DSOs will in turn contain a list
+of its own dependencies (more DSOs).
+
+The component of your system known as the linker will assemble the
+full list of all the required DSOs, open them, and following a
+somewhat intricate set of rules, copy (map) chunks of these files
+into memory.
+
+It then goes through every loaded DSO and fixes up each one by
+scribbling the relevant addresses into its (the DSO's) lookup
+table so that it can find the functions and variables (symbols)
+it needs to from each of its dependencies.
+
+Once this is done, the program is good to go: The system can
+start executing its instructions, jumping between the loaded
+DSOs (via the lookup tables in each one) as required.
+
+The problem:
+----------------------------------------------------------------------------
+So far so good: This is mostly the right thing to do, and solves
+a lot of problems we won't discuss here - But it does make some
+important assumptions. One these is the following:
+
+ - that every DSO involved that requires a specific DSO is going to be
+   happy with the same copy of the same version of that requirement.
+
+   In other words if a program needs libfoo.so and both the program
+   and libfoo.so require libbar.so, they will both be happy with a
+   single copy of libbar.so v3.14.159 (for example) provided by the
+   linker.
+
+This assumption is mostly true. In general things are happier when
+everything is built on the same set of libraries, but occasionally
+you can end up with a program that has conflicting requirements
+for the same DSO - libcapsule tries to solve that problem.
+
+The approach (the basics):
+----------------------------------------------------------------------------
+There are broadly two parts to the segregated-linker behaviour that we
+require:
+
+The first and more fundamental is mapping in the DSOs we wish to isolate
+in such a way that the linker does not (by default) use their symbols'
+addresses to fill in the lookup tables of the other DSOs we have loaded.
+Fortunately this part of the problem is addresses by the [relatively]
+new dlmopen() C library call. dlmopen() is similar to the dlopen() call,
+except that instead of opening a DSO (and all its dependencies) into the
+default linker namespace it does so into a private one, either creating
+a new namespace if requested, or using an existing namespace. [ There are
+one or two intricacies to this that we've glossed over here: We'll revisit
+this in greater detail later. ] 
+
+The second part is making just the symbols we want available to the rest
+of the DSOs our program consists of, ideally introducing as little overhead
+as possible: There are a couple of approaches we could take to this,
+each with problems of its own.
+
+dlmopen() (the gory details):
+----------------------------------------------------------------------------
+When dlmopen() is called, the algorithm is roughly as follows:
+
+  • find the requested DSO
+    â—¦ if the path to the requested DSO is absolute, look for it there
+    â—¦ if the path is relative (contains but does not begin with '/')
+     then look for it relative to the processes current working directory
+    â—¦ if the path is bare, search for it using the standard linker algorithm:
+      ‣ look in RPATH baked into DSO (if there's no RUNPATH)
+      ‣ then LD_LIBRARY_PATH from environment
+      ‣ then RUNPATH baked into DSO
+      ‣ then contents of ld.so.cache
+      ‣ then /lib
+      ‣ & finally /usr/lib
+  • harvest the DT_NEEDED entries from the DSO
+    â—¦ check if the DT_NEEDED entry is already in _our_ link map
+      (ie have we loaded the library for some other reason already)
+      we do not consider entries in the default link map, or any other
+      private namespaces created by dlmopen().
+    â—¦ if we haven't, open it as for the original DSO above, and repeat.
+    â—¦ if we already have the DSO open, add its link map structure
+      to your list of link maps without opening it again.
+  • once we don't require any more DSOs (we've opened everything)    
+    fix up the lookup tables using the link maps
+
+For the basic use case of opening a DSO without exposing its symbols
+to any other DSOs we already have this is sufficient. However in the
+libcapsule case there's a new wrinkle - we want to open a DSO (and
+most of its dependencies) from an external source. To give a concrete
+example:
+
+Consider an application inside a "runtime" (for our purposes, a mostly
+self-contained set of DSOs, binaries and so forth separated from the main
+system) which wants to use a library from the host OS (which is not
+compatible with the set of DSOs in the runtime, hence libcapsule).
+
+We have made the "real" set of libraries available under /host in the
+runtime (that is, the / of the real system is mounted at /host in the
+runtime, which has its own /).
+
+If we naively dlmopen() a DSO from the /host tree, dlmopen will go
+and find all the DT_NEEDED items required to satisfy it, but from
+/ instead of from /host (since dlmopen knows nothing about /host):
+
+Less than ideal, since in our special case we don't just need to isolate
+one DSO from the rest, but two whole set of DSOs from one another. So what's
+the solution?
+
+The key lies in the algorithm presented above: Note that before opening a
+DSO, we check to see if we already have it in our list of link maps: This
+check is done against the ‘bare’ "libfoo.so.X" style name of the DSO, not
+its full path.
+
+If we manually (and recursively) harvest the list of DSOs from the DT_NEEDED
+entries, and open them in reverse dependency order (ie we start with the
+thing that has no dependencies, then the things that only needed that, and
+so on and so forth) then the dlmopen() call won't go looking for them
+elsewhere.
+
+That's the theory - there are a few more details. For example:
+
+We need to be careful when resolving symlinks since the filesystem we
+are interested in is remapped to /host, not /, so absolute symlinks
+will point to the ‘wrong’ place).
+
+We must manually load the ld.so.cache from /host/etc/… and remap
+all its entries to the right location under /host (same reason as
+the symlink problem above).
+
+But having said that, the basic method works. Success! We can control
+the DSOs loaded to satisfy our target, and isolate them all in a
+private namespace.
+
+Controlling symbol visibility
+----------------------------------------------------------------------------
+Having succeeded in opening a DSO without exposing any of its symbols, we
+now need to make sure the other DSOs (including the main program) use the
+symbols from the DSO we _do_ want to expose.
+
+The simplest (in that it requires the least knowledge of the details of
+how a dynamically linked program works) approach would be to define, in
+a proxy library, for each function we wish to export, a proxy function
+with the same signature as its ‘real’ implementation, which could find
+that address with dlsym() and re-dispatch the call there, then let our
+program link against that (this last easily achieved by giving our proxy
+library the same name as the ‘real’ one).
+
+However, this has a few disadvantages:
+
+  • doesn't handle varargs functions
+  • requires knowledge of the signature of each function
+    â—¦ this makes it much harder to automate proxy generation
+  • mechanism cannot be extended to handle variables
+  • doubles the function call overhead for every call: we have to
+    call our proxy function, which then makes the real call
+
+Ideally we'd like to find a different approach that has none of these
+limitations. To do so we're going to have to examine, in greater detail,
+how the "lookup tables" we've been talking about actually work.
+
+At the heart of all this is something called a Global Offset Table,
+or GOT (the name is possibly slightly misleading - it's not the table
+that's global, but the offsets).
+
+Each DSO contains a GOT, which consists of an array of entries, each
+containing (after dynamic linking has completed) the real address of
+a given function (or other object, but we're concerned with functions
+for now).
+
+Each DSO also contains a Procedure Linkage Table (NOTE: this is a
+lie, it contains two, but we turn out to care about only one of them).
+
+When an external function is called for the first time the following
+happens:
+
+   → The caller puts the function arguments on the stack.
+     the way in which it does so is determined by the ‘calling convention’,
+     a set of rules for passing arguments to and retrieving return values
+     from a function. Both DSOs involved in a call must agree on the
+     convention in order for this to work.
+
+   → Jump to the PLT entry for the function
+     This is at a fixed offset, so the only thing we need to
+     know to find it is the base address (in memory) of the section of
+     the DSO in which the PLT resides.
+
+   → The PLT entry points to an entry in the GOT.
+     The GOT is in a writable section of memory, but is of a fixed size
+     and has well defined entries of a fixed size also, so again, as long
+     as we know the base address, the PLT entry can always send us to
+     the right place.
+
+   → The GOT entry contains a ‘relocation record’. There are many different
+     types of relocation record - fortunately we only care about one.
+     This relocation record consists of two or three values which can be used
+     to calculate the real address to which execution should jump.
+
+     Having calculated our jump, we transfer execution there.
+     Now the _first_ time we encounter a GOT entry, it actually points
+     to a special function which finds the _real_ function address,
+     SCRIBBLES IT BACK INTO THE RELOCATION RECORD, then jumps to the
+     function proper.
+
+   → The function grabs the arguments from the stack, handles them,
+     pushes its return value (if any) onto the stack (as per the calling
+     convention), then passes control back to its caller.
+
+   → the caller pulls the return value off the stack, and does something
+     with it (or not).
+
+The _next_ time the function is called (from the same DSO), we will jump from
+the GOT directly to the function, without hitting the special resolver.
+
+    First call:                        Subsequent calls:
+
+      ┌───────┬──────────┐               ┌───────┬──────────┐   
+    ┌─┤ DSO A │ call foo │<───────┐    ┌─┤ DSO A │ call foo │<─┐    
+    │ └───────┴──────────┘        │    │ └───────┴──────────┘  │ 
+    └>┌───────┬──────────┐        │    └>┌───────┬──────────┐  │ 
+    ┌─┤ DSO A │ PLT: foo │        │    ┌─┤ DSO A │ PLT: foo │  │ 
+    │ └───────┴──────────┘        │    │ └───────┴──────────┘  │ 
+    └>┌───────┬─────────────────┐ │    └>┌───────┬──────────┐  │ 
+    ┌─┤ DSO A │ GOT: <Resolver> │ │    ┌─┤ DSO A │ GOT: foo │  │ 
+    │ └───────┴───┬─────────────┘ │    │ └───────┴──────────┘  │ 
+    │             ↿ <foo>         │    └>┌───────┬──────────┐  │ 
+    └>┌───────┬───┴──────┐        │      │ DSO B │ foo      ├──┘ 
+    ┌─┤ DSO A │ Resolver │        │      └───────┴──────────┘   
+    │ └───────┴──────────┘        │
+    └>┌───────┬──────────┐        │
+      │ DSO B │ foo      ├────────┘
+      └───────┴──────────┘   
+
+The key to where we jump is the relocation record in the GOT of DSO A
+It's worth noting here that the only things that know the signature
+(ie the argument and return types of foo) are the caller in DSO A and
+foo itself: None of the intermediates know or care. This is promising,
+as not caring about the signature is one of our goals.
+
+An approach that should work, then, is as follows:
+
+  • Prepare a proxy library with the same name as the real DSO
+  • Arrange for said proxy to have a public function for each one
+    we wish to proxy (signature unimportant, since we will never
+    actually call these stubroutines)
+  • In our [proxy] library initialisation stage:
+    â—¦ walk the list of loaded DSOs
+    â—¦ locate the PLT entry for each function we wish to proxy
+    â—¦ fetch the address of the proxied function(s) using dlsym()
+      (this is the real address inside the private namespace)
+    â—¦ follow the PLT to the GOT relocation record (RR)
+    â—¦ scribble our real address into the GOT RR
+
+From now on, in each DSO we've scribbled on, calls to proxied functions
+will jump directly from its GOT to the real function in the private
+namespace, bypassing the resolver stage (and bypassing our proxy library
+entirely).
+
+NOTE: The above is mostly true. There is the case of RELRO linking,
+where the GOT:<Resolver> stage is handled early by the linker/loader,
+resolving all function addresses whether or not they're ever called.
+
+In RELRO linking the GOT memory page(s) are also mprotect()ed to read-only,
+which would prevent us from scribbling on the GOT(s) in question, but
+we can work around this by finding the relevant mprotect()ed pages and
+toggling the write permission bit on them before we start (and flipping
+the bit back when we're done).
+
+We can only currently work around RELRO linking on Linux currently: I'm not
+sure any other ELF systems expose the necessary mprotect meta data to us.
diff --git a/subprojects/libcapsule/doc/capsule-init-project.xml b/subprojects/libcapsule/doc/capsule-init-project.xml
new file mode 100644
index 0000000000000000000000000000000000000000..781a152faafa25e8ba60eedda495bb25c9ae35fe
--- /dev/null
+++ b/subprojects/libcapsule/doc/capsule-init-project.xml
@@ -0,0 +1,192 @@
+<!--
+Copyright © 2017 Collabora Ltd
+
+This file is part of libcapsule.
+
+libcapsule is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as
+published by the Free Software Foundation; either version 2.1 of the
+License, or (at your option) any later version.
+
+libcapsule 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="capsule-init-project">
+
+  <refentryinfo>
+    <title>capsule-init-project</title>
+    <productname>libcapsule</productname>
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Vivek</firstname>
+        <surname>Das Mohapatra</surname>
+      </author>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Simon</firstname>
+        <surname>McVittie</surname>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>capsule-init-project</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo class="manual">User Commands</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>capsule-init-project</refname>
+    <refpurpose>libcapsule proxy library project setup tool</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>capsule-init-project</command>
+      <arg choice="opt">OPTIONS</arg>
+      <arg choice="req" rep="repeat">TARGET</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      <command>capsule-init-project</command> creates a project which
+      uses libcapsule to create a proxy for a library, usually from
+      a foreign filesystem tree.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Arguments</title>
+    <variablelist>
+
+      <varlistentry>
+        <term><option>TARGET</option></term>
+        <listitem>
+          <para>
+            The base name of the target, of the form libFOO.so.X (eg libz.so.1).
+            It can optionally be combined with a version number in the
+            form libFOO.so.X/VERSION (e.g. libz.so.1/1.2.3).
+          </para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+    <variablelist>
+
+      <varlistentry>
+        <term><option>--capsule-pkgdatadir=PATH</option></term>
+        <listitem>
+          <para>Use supporting files from PATH</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--capsule-symbols-tool=PATH</option></term>
+        <listitem>
+          <para>Use replacement capsule-symbols(1)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--capsule-version-tool=PATH</option></term>
+        <listitem>
+          <para>Use replacement capsule-version(1)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--search-tree=PATH</option></term>
+        <listitem>
+          <para>
+            Find libraries to be proxied in this chroot,
+            sysroot or container at compile-time.
+          </para>
+          <para>default: TREE or /host if unset</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--set-version=VERSION</option></term>
+        <listitem>
+          <para>
+            Use this as the package version
+            (major, major.minor or major.minor.micro)
+            instead of using capsule-version(1) on the first
+            <option>TARGET</option>
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--symbols-from-dir=PATH</option></term>
+        <listitem>
+          <para>
+            Use symbols files in this directory instead of the
+            output of capsule-symbols(1)
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--runtime-tree=PATH</option></term>
+        <listitem>
+          <para>
+            Find libraries to be proxied in this chroot, sysroot or
+            container at runtime. The default is /host.
+          </para>
+          <para>
+            If libcapsule was compiled with glibc 2.17 or later and the
+            process is not privileged, this can be overridden at runtime
+            by setting a SONAME-specific environment variable (for
+            example CAPSULE_LIBGL_SO_1_PREFIX for libGL.so.1) or by
+            setting the generic CAPSULE_PREFIX environment variable.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--package-name=PACKAGE</option></term>
+        <listitem>
+          <para>
+            Use this name for the generated Autotools package,
+            instead of libFOO-proxy
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--destination=PATH</option></term>
+        <listitem>
+          <para>
+            Use this name for the generated directory,
+            instead of libFOO-proxy or the argument of
+            <option>--package-name</option>
+          </para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1><title>See also</title>
+  <para>
+    <citerefentry>
+      <refentrytitle>capsule-mkstublib</refentrytitle>
+      <manvolnum>1</manvolnum>
+    </citerefentry>
+  </para>
+  </refsect1>
+</refentry>
diff --git a/subprojects/libcapsule/doc/capsule-mkstublib.xml b/subprojects/libcapsule/doc/capsule-mkstublib.xml
new file mode 100644
index 0000000000000000000000000000000000000000..56185ab66b862e0d70d15b6bd653afc6c6dfba48
--- /dev/null
+++ b/subprojects/libcapsule/doc/capsule-mkstublib.xml
@@ -0,0 +1,251 @@
+<!--
+Copyright © 2017 Collabora Ltd
+
+This file is part of libcapsule.
+
+libcapsule is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as
+published by the Free Software Foundation; either version 2.1 of the
+License, or (at your option) any later version.
+
+libcapsule 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="capsule-mkstublib">
+
+  <refentryinfo>
+    <title>capsule-mkstublib</title>
+    <productname>libcapsule</productname>
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Vivek</firstname>
+        <surname>Das Mohapatra</surname>
+      </author>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Simon</firstname>
+        <surname>McVittie</surname>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>capsule-mkstublib</refentrytitle>
+    <manvolnum>1</manvolnum>
+    <refmiscinfo class="manual">User Commands</refmiscinfo>
+  </refmeta>
+
+  <refnamediv>
+    <refname>capsule-mkstublib</refname>
+
+    <refpurpose>
+      Command used by the libcapsule build system to generate proxy
+      library source files.
+    </refpurpose>
+
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>capsule-mkstublib</command>
+      <arg choice="opt">OPTIONS</arg>
+      <arg choice="plain">TARGET</arg>
+      <arg choice="plain">EXCLUDES</arg>
+      <arg choice="plain">EXPORTS</arg>
+      <arg choice="plain">OUTPUT</arg>
+      <arg choice="opt">DSO-VERSION<arg choice="opt">TREE</arg></arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+    <para>
+      <command>capsule-mkstublib</command> generates the source code
+      for a libcapsule proxy library from a standard set of inputs
+      (described below).
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Arguments</title>
+    <variablelist>
+
+      <varlistentry>
+        <term><option>TARGET</option></term>
+        <listitem>
+          <para>
+            The SONAME of the target, of the form libFOO.so.X (eg libz.so.1)
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>EXCLUDES</option></term>
+        <listitem>
+          <para>
+            A file containing a list of SONAMEs (one on each line) not to
+            include in the libcapsule proxy: DSOs listed here will not
+            be loaded from the foreign tree and must come from the
+            normal set searched by the linker.
+          </para>
+          <para>
+            For example if libz.so.1 is listed here, and TARGET depends
+            on libz.so.1 then it will be run-time linked against the
+            copy of libz from / - not any copies which are found under TREE
+          </para>
+          <para>
+            This file is empty by default in new prjects.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>EXPORTS</option></term>
+        <listitem>
+          <para>
+            A file containing a list of SONAMEs (one on each line) whose
+            symbols should be exported.
+          </para>
+          <para>Symbols from TARGET are always exported.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>OUTPUT</option></term>
+        <listitem>
+          <para>
+            Output filename
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>DSO-VERSION</option></term>
+        <listitem>
+          <para>
+            The version of the target library our proxy will pretend to be,
+            in the format MAJOR:MINOR:SUB
+          </para>
+          <para>
+            The format is the same format as consumed by libtool.
+            Only MAJOR must be present if this argument is supplied.
+          </para>
+          <para>
+            The default value is determined by parsing the soname of the
+            library.
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>TREE</option></term>
+        <listitem>
+          <para>
+            The mount point of the foreign filesystem tree from which TARGET
+            and its dependencies will be acquired.
+          </para>
+          <para>
+            This value can be overridden by --runtime-tree and --search-tree.
+          </para>
+          <para>
+            The default is the soname-specific environment variable (eg
+            CAPSULE_LIBGL_SO_1_PREFIX for libGL.so.1), or the CAPSULE_PREFIX
+            environment variable, or /host. The environment variables are
+            ignored if the process is privileged, or if libcapsule was
+            compiled with glibc older than 2.17.
+          </para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+    
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+    <variablelist>
+
+      <varlistentry>
+        <term><option>--capsule-symbols-tool=PATH</option></term>
+        <listitem>
+          <para>Use replacement capsule-symbols(1)</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--symbols-from=PATH</option></term>
+        <listitem>
+          <para>
+            Use this symbols file instead of the output of capsule-symbols(1)
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--[no]-update-symbols</option></term>
+        <listitem>
+          <para>
+            Do or don't update the exported symbol list if EXPORTS is
+            updated [default: do]
+          </para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--search-tree=PATH</option></term>
+        <listitem>
+          <para>
+            Find libraries to be proxied in this chroot,
+            sysroot or container at compile-time.
+          </para>
+          <para>default: TREE or / if unset</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--runtime-tree=PATH</option></term>
+        <listitem>
+          <para>
+            Find libraries to be proxied in this chroot, sysroot or
+            container at runtime. The default is /host.
+          </para>
+          <para>
+            If libcapsule was compiled with glibc 2.17 or later and the
+            process is not privileged, this can be overridden at runtime
+            by setting a SONAME-specific environment variable (for
+            example CAPSULE_LIBGL_SO_1_PREFIX for libGL.so.1) or by
+            setting the generic CAPSULE_PREFIX environment variable.
+          </para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Bugs</title>
+    <para>
+      The order of arguments is a bit pathological: This is for
+      historical reasons. Since the command is not expected to be
+      invoked outside of libcapsule's build scripts this is not
+      considered a major problem.
+    </para>
+  </refsect1>
+  
+  <refsect1>
+    <title>See also</title>
+    <para>
+      <citerefentry>
+        <refentrytitle>capsule-init-project</refentrytitle>
+        <manvolnum>1</manvolnum>
+      </citerefentry>
+    </para>
+  </refsect1>
+</refentry>
diff --git a/subprojects/libcapsule/doc/devhelp2man.xslt b/subprojects/libcapsule/doc/devhelp2man.xslt
new file mode 100644
index 0000000000000000000000000000000000000000..974bf144c484e78c67ced153e1ea9ebecfa403b8
--- /dev/null
+++ b/subprojects/libcapsule/doc/devhelp2man.xslt
@@ -0,0 +1,225 @@
+<!--
+Copyright © 2017 Collabora Ltd
+
+This file is part of libcapsule.
+
+libcapsule is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as
+published by the Free Software Foundation; either version 2.1 of the
+License, or (at your option) any later version.
+
+libcapsule 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 Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public
+License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<xsl:stylesheet version = '1.0'
+                xmlns:xi="http://www.w3.org/2003/XInclude"
+                xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
+                xmlns:saxon="http://icl.com/saxon"
+                xmlns:xslt="http://xml.apache.org/xslt"
+                xmlns:string="http://exslt.org/strings"
+                xmlns:ext="http://exslt.org/common"
+                extension-element-prefixes="saxon"
+                exclude-result-prefixes="xi">
+  <xsl:strip-space elements="*" />
+  <xsl:output omit-xml-declaration="yes"
+              indent="yes"
+              encoding="utf-8"
+              xslt:indent-amount="2"/>
+
+  <xsl:template match="node()|@*" mode="description">
+    <xsl:apply-templates/>
+  </xsl:template>
+
+  <xsl:template name="paramdesc" match="refsect2[@role='function']"
+                mode="paramdesc">
+    <xsl:param name="matched" select="indexterm/primary"/>
+    <xsl:if test="$matched = $target">
+      <xsl:element name="title">Parameters:</xsl:element>
+      <xsl:element name="variablelist">
+        <xsl:for-each select="refsect3[@role='parameters']//row">
+          <xsl:element name="varlistentry">
+            <xsl:element name="term">
+              <xsl:element name="code">
+                <xsl:value-of select="concat(entry[@role='parameter_name'], ': ')"/>
+              </xsl:element>
+            </xsl:element>
+            <xsl:element name="listitem">
+              <xsl:element name="para">
+                <xsl:value-of select="entry[@role='parameter_description']"/>
+              </xsl:element>
+            </xsl:element>
+          </xsl:element>
+        </xsl:for-each>
+      </xsl:element>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template name="retdesc" match="refsect2[@role='function']"
+                mode="retdesc">
+    <xsl:param name="matched" select="indexterm/primary"/>
+    <xsl:if test="$matched = $target">
+      <xsl:for-each select="refsect3[@role='returns']/*">
+        <xsl:copy-of select="."/>
+      </xsl:for-each>
+    </xsl:if>
+  </xsl:template>
+  <!-- //////////////////////////////////////////////////////// -->
+
+  <xsl:template match="node()|@*" name="ignore">
+    <xsl:apply-templates/>
+  </xsl:template>
+
+  <xsl:template match="entry[@role='function_name']/link" name="refnamediv">
+    <xsl:param name="matched" select="."/>
+    <xsl:if test="$matched = $target">
+      <xsl:element name="refname">
+        <xsl:value-of select="."/>
+      </xsl:element>
+      <xsl:apply-templates/>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template match="refsynopsisdiv/synopsis/link" mode="refnamediv">
+    <xsl:param name="matched" select="."/>
+    <xsl:if test="$matched = $target">
+      <xsl:element name="refname">
+        <xsl:value-of select="."/>
+      </xsl:element>
+      <xsl:apply-templates/>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template name="paramdef" match="parameter" mode="funcsig">
+    <xsl:param name="tok"   select="string:tokenize(., ' ')"/>
+    <xsl:param name="last"  select="ext:node-set($tok)[last()]"/>
+    <xsl:param name="name"  select="string:tokenize($last, '*')"/>
+    <xsl:param name="stars" select="substring-before($last, $name)"/>
+    <xsl:element name="paramdef">
+      <xsl:for-each select="ext:node-set($tok)[position() != last()]">
+        <xsl:value-of select="concat(., ' ')"/>
+      </xsl:for-each>
+      <xsl:value-of select="$stars"/>
+      <xsl:element name="parameter">
+        <xsl:value-of select="$name"/>
+      </xsl:element>
+    </xsl:element>
+  </xsl:template>
+
+  <xsl:template name="funcproto" match="refsect2[@role='function']">
+    <xsl:param name="matched" select="indexterm/primary"/>
+    <xsl:if test="$matched = $target">
+      <xsl:element name="funcprototype">
+        <xsl:element name="funcdef">
+          <xsl:value-of
+              select="concat(substring-before(programlisting,'&#xa;'), ' ')"/>
+          <xsl:element name="function">
+            <xsl:value-of select="indexterm/primary"/>
+          </xsl:element>
+        </xsl:element>
+        <xsl:apply-templates select="programlisting" mode="funcsig"/>
+      </xsl:element>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template name="voiddesc" match="refsect2[@role='function']"
+                mode="voiddesc">
+    <xsl:param name="matched" select="indexterm/primary"/>
+    <xsl:if test="$matched = $target">
+      <xsl:for-each select="para">
+        <xsl:copy-of select="."/>
+      </xsl:for-each>
+    </xsl:if>
+  </xsl:template>
+
+  <xsl:template match="/refentry" name="top">
+    <xsl:element name="refentry">
+      <xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
+      <xsl:element name="refentryinfo">
+        <xsl:element name="productname">libcapsule</xsl:element>
+        <xsl:element name="authorgroup">
+          <xsl:element name="contrib">Author</xsl:element>
+          <xsl:element name="firstname">Vivek</xsl:element>
+          <xsl:element name="surname">Das Mohapatra</xsl:element>
+        </xsl:element>
+        <xsl:element name="authorgroup">
+          <xsl:element name="contrib">Author</xsl:element>
+          <xsl:element name="firstname">Simon</xsl:element>
+          <xsl:element name="surname">McVittie</xsl:element>
+        </xsl:element>
+      </xsl:element>
+      <xsl:element name="refmeta">
+        <xsl:element name="refentrytitle">
+          <!-- xsl:value-of select="/refentry/refmeta/refentrytitle"/ -->
+          <xsl:value-of select="$target"/>
+        </xsl:element>
+        <xsl:element name="manvolnum">
+          <xsl:value-of select="/refentry/refmeta/manvolnum"/>
+        </xsl:element>
+        <xsl:element name="refmiscinfo">
+          <xsl:attribute name="class">manual</xsl:attribute>
+          <xsl:value-of select="normalize-space(refmeta/refmiscinfo)"/>
+        </xsl:element>
+      </xsl:element>
+      <xsl:element name="refnamediv">
+        <xsl:choose>
+          <xsl:when test="refsect1[@role='functions_proto']">
+            <xsl:apply-templates select="refsect1[@role='functions_proto']"/>
+          </xsl:when>
+          <xsl:otherwise>
+            <xsl:apply-templates select="refsynopsisdiv[@role='synopsis']" mode="refnamediv"/>
+          </xsl:otherwise>
+        </xsl:choose>
+      </xsl:element>
+      <xsl:element name="refsynopsisdiv">
+        <xsl:element name="funcsynopsis">
+          <xsl:element name="funcsynopsisinfo">
+            <xsl:value-of select="$fsinfo"/>
+          </xsl:element>
+        </xsl:element>
+        <xsl:apply-templates select="refsect1[@role='details']"/>
+      </xsl:element>
+      <xsl:element name="refsect1">
+        <xsl:element name="title">Description:</xsl:element>
+        <xsl:apply-templates
+            select="refsect1[@role='details' and contains(@id,'functions_details')]/refsect2"
+            mode="voiddesc"/>
+        <xsl:element name="refsect2">
+          <xsl:apply-templates
+              select="refsect1[@role='details' and contains(@id,'functions_details')]/refsect2"
+                               mode="paramdesc"/>
+        </xsl:element>
+        <xsl:element name="refsect2">
+          <xsl:apply-templates
+              select="refsect1[@role='details' and contains(@id,'functions_details')]/refsect2"
+              mode="retdesc"/>
+        </xsl:element>
+      </xsl:element>
+
+      <xsl:element name="refsect1">
+        <xsl:element name="title">See Also:</xsl:element>
+          <xsl:element name="para">
+            <xsl:for-each select="refsect1[@role='details']/refsect2[@role='function']/indexterm/primary">
+              <xsl:element name="link">
+                <xsl:element name="function">
+                  <xsl:value-of select="concat(.,'(3)')"/>
+                </xsl:element>
+              </xsl:element>
+              <xsl:choose>
+                <xsl:when test="position() != last()">
+                  <xsl:text>, </xsl:text>
+                </xsl:when>
+              </xsl:choose>
+            </xsl:for-each>
+          </xsl:element>
+      </xsl:element>
+
+    </xsl:element>
+  </xsl:template>
+
+</xsl:stylesheet>
diff --git a/subprojects/libcapsule/documentation.mk b/subprojects/libcapsule/documentation.mk
new file mode 100644
index 0000000000000000000000000000000000000000..d9b784f0cd675ea9d9caca42cb0cb6ba191ce4a9
--- /dev/null
+++ b/subprojects/libcapsule/documentation.mk
@@ -0,0 +1,59 @@
+# Copyright © 2017 Collabora Ltd
+
+# This file is part of libcapsule.
+
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+
+# libcapsule 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 Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#
+# ============================================================================
+# standalone man pages from docbook source:
+XSLTPROC_FLAGS = \
+        --nonet \
+        --stringparam man.output.quietly 1 \
+        --stringparam funcsynopsis.style ansi \
+        --stringparam man.th.extra1.suppress 1 \
+        --stringparam man.authors.section.enabled 1 \
+        --stringparam man.copyright.section.enabled 0
+
+XSLT_DOMAIN = docbook.sourceforge.net
+XSLT_MAN = http://$(XSLT_DOMAIN)/release/xsl/current/manpages/docbook.xsl
+XSLT_CAPARGS = --stringparam fsinfo "$$(printf "\#define _GNU_SOURCE\012\#include <capsule/capsule.h>")" --stringparam target
+XSLTPROC_STD = $(XSLTPROC) $(XSLTPROC_FLAGS)
+
+man_MANS =
+
+if HAVE_XSLTPROC
+%.1: doc/%.xml
+	$(AM_V_GEN) $(XSLTPROC_STD) $(XSLT_MAN) $<
+
+man_MANS   += capsule-init-project.1 capsule-mkstublib.1
+endif # HAVE_XSLTPROC
+
+if ENABLE_GTK_DOC
+
+xml/capsule.xml: docs
+
+%.3.xml: xml/capsule.xml doc/devhelp2man.xslt documentation.mk
+	$(AM_V_GEN) $(XSLTPROC_STD) $(XSLT_CAPARGS) $* $(srcdir)/doc/devhelp2man.xslt $< > $@
+
+%.3: %.3.xml documentation.mk
+	$(AM_V_GEN) $(XSLTPROC_STD) $(XSLT_MAN) $<
+
+man_MANS   += capsule_init.3
+man_MANS   += capsule_shim_dlopen.3
+man_MANS   += capsule_external_dlsym.3
+
+endif # ENABLE_GTK_DOC
+
+CLEANFILES += $(man_MANS)
diff --git a/subprojects/libcapsule/examples/.gitignore b/subprojects/libcapsule/examples/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..bca62216bdb700810ee6ec5cf67bfd115d53ea86
--- /dev/null
+++ b/subprojects/libcapsule/examples/.gitignore
@@ -0,0 +1,21 @@
+*.a
+*.la
+*.lo
+*.o
+.deps/
+.dirstamp
+.libs/
+/Makefile
+/Makefile.in
+/aclocal.m4
+/autom4te.cache/
+/build-aux/
+/capsule-shim.mk
+/config.log
+/config.status
+/configure
+/libtool
+/m4/
+/preconfigure.log
+/shim/*.so.*.c
+/shim/*.so.*.map
diff --git a/subprojects/libcapsule/examples/shim/libEGL.so.1.excluded b/subprojects/libcapsule/examples/shim/libEGL.so.1.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libEGL.so.1.shared b/subprojects/libcapsule/examples/shim/libEGL.so.1.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libEGL.so.1.symbols b/subprojects/libcapsule/examples/shim/libEGL.so.1.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..1c3b189f714340f3de71a4b9fb5a20ba5666973d
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libEGL.so.1.symbols
@@ -0,0 +1,46 @@
+eglBindAPI 
+eglBindTexImage 
+eglChooseConfig 
+eglClientWaitSync 
+eglCopyBuffers 
+eglCreateContext 
+eglCreateImage 
+eglCreatePbufferFromClientBuffer 
+eglCreatePbufferSurface 
+eglCreatePixmapSurface 
+eglCreatePlatformPixmapSurface 
+eglCreatePlatformPixmapSurfaceEXT 
+eglCreatePlatformWindowSurface 
+eglCreatePlatformWindowSurfaceEXT 
+eglCreateSync 
+eglCreateWindowSurface 
+eglDestroyContext 
+eglDestroyImage 
+eglDestroySurface 
+eglDestroySync 
+eglGetConfigAttrib 
+eglGetConfigs 
+eglGetCurrentContext 
+eglGetCurrentDisplay 
+eglGetCurrentSurface 
+eglGetDisplay 
+eglGetError 
+eglGetPlatformDisplay 
+eglGetProcAddress 
+eglGetSyncAttrib 
+eglInitialize 
+eglMakeCurrent 
+eglQueryAPI 
+eglQueryContext 
+eglQueryString 
+eglQuerySurface 
+eglReleaseTexImage 
+eglReleaseThread 
+eglSurfaceAttrib 
+eglSwapBuffers 
+eglSwapInterval 
+eglTerminate 
+eglWaitClient 
+eglWaitGL 
+eglWaitNative 
+eglWaitSync 
diff --git a/subprojects/libcapsule/examples/shim/libEGL.so.1.symbols.updated-for b/subprojects/libcapsule/examples/shim/libEGL.so.1.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGL.so.1.excluded b/subprojects/libcapsule/examples/shim/libGL.so.1.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGL.so.1.shared b/subprojects/libcapsule/examples/shim/libGL.so.1.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGL.so.1.symbols b/subprojects/libcapsule/examples/shim/libGL.so.1.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..b9693fae76a2eca7c276ca1d51f1c774b5204a95
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libGL.so.1.symbols
@@ -0,0 +1,3330 @@
+glAccum 
+glAccumxOES 
+glActiveProgramEXT 
+glActiveShaderProgram 
+glActiveShaderProgramEXT 
+glActiveStencilFaceEXT 
+glActiveTexture 
+glActiveTextureARB 
+glActiveVaryingNV 
+glAlphaFragmentOp1ATI 
+glAlphaFragmentOp2ATI 
+glAlphaFragmentOp3ATI 
+glAlphaFunc 
+glAlphaFuncQCOM 
+glAlphaFuncx 
+glAlphaFuncxOES 
+glApplyFramebufferAttachmentCMAAINTEL 
+glApplyTextureEXT 
+glAreProgramsResidentNV 
+glAreTexturesResident 
+glAreTexturesResidentEXT 
+glArrayElement 
+glArrayElementEXT 
+glArrayObjectATI 
+glAsyncMarkerSGIX 
+glAttachObjectARB 
+glAttachShader 
+glBegin 
+glBeginConditionalRender 
+glBeginConditionalRenderNV 
+glBeginConditionalRenderNVX 
+glBeginFragmentShaderATI 
+glBeginOcclusionQueryNV 
+glBeginPerfMonitorAMD 
+glBeginPerfQueryINTEL 
+glBeginQuery 
+glBeginQueryARB 
+glBeginQueryEXT 
+glBeginQueryIndexed 
+glBeginTransformFeedback 
+glBeginTransformFeedbackEXT 
+glBeginTransformFeedbackNV 
+glBeginVertexShaderEXT 
+glBeginVideoCaptureNV 
+glBindANCCaptureStreamBufferNVX 
+glBindAttribLocation 
+glBindAttribLocationARB 
+glBindBuffer 
+glBindBufferARB 
+glBindBufferBase 
+glBindBufferBaseEXT 
+glBindBufferBaseNV 
+glBindBufferOffsetEXT 
+glBindBufferOffsetNV 
+glBindBufferRange 
+glBindBufferRangeEXT 
+glBindBufferRangeNV 
+glBindBuffersBase 
+glBindBuffersRange 
+glBindFragDataLocation 
+glBindFragDataLocationEXT 
+glBindFragDataLocationIndexed 
+glBindFragDataLocationIndexedEXT 
+glBindFragmentShaderATI 
+glBindFramebuffer 
+glBindFramebufferEXT 
+glBindFramebufferOES 
+glBindImageTexture 
+glBindImageTextureEXT 
+glBindImageTextures 
+glBindLightParameterEXT 
+glBindMaterialParameterEXT 
+glBindMultiTextureEXT 
+glBindParameterEXT 
+glBindProgramARB 
+glBindProgramNV 
+glBindProgramPipeline 
+glBindProgramPipelineEXT 
+glBindRenderbuffer 
+glBindRenderbufferEXT 
+glBindRenderbufferOES 
+glBindSampler 
+glBindSamplers 
+glBindTexGenParameterEXT 
+glBindTexture 
+glBindTextureEXT 
+glBindTextureUnit 
+glBindTextureUnitParameterEXT 
+glBindTextures 
+glBindTransformFeedback 
+glBindTransformFeedbackEXT 
+glBindTransformFeedbackNV 
+glBindVertexArray 
+glBindVertexArrayAPPLE 
+glBindVertexArrayOES 
+glBindVertexBuffer 
+glBindVertexBuffers 
+glBindVertexShaderEXT 
+glBindVideoCaptureStreamBufferNV 
+glBindVideoCaptureStreamTextureNV 
+glBinormal3bEXT 
+glBinormal3bvEXT 
+glBinormal3dEXT 
+glBinormal3dvEXT 
+glBinormal3fEXT 
+glBinormal3fvEXT 
+glBinormal3iEXT 
+glBinormal3ivEXT 
+glBinormal3sEXT 
+glBinormal3svEXT 
+glBinormalPointerEXT 
+glBitmap 
+glBitmapxOES 
+glBlendBarrier 
+glBlendBarrierKHR 
+glBlendBarrierNV 
+glBlendColor 
+glBlendColorEXT 
+glBlendColorxOES 
+glBlendEquation 
+glBlendEquationEXT 
+glBlendEquationIndexedAMD 
+glBlendEquationOES 
+glBlendEquationSeparate 
+glBlendEquationSeparateEXT 
+glBlendEquationSeparateIndexedAMD 
+glBlendEquationSeparateOES 
+glBlendEquationSeparatei 
+glBlendEquationSeparateiARB 
+glBlendEquationSeparateiEXT 
+glBlendEquationSeparateiOES 
+glBlendEquationi 
+glBlendEquationiARB 
+glBlendEquationiEXT 
+glBlendEquationiOES 
+glBlendFunc 
+glBlendFuncIndexedAMD 
+glBlendFuncSeparate 
+glBlendFuncSeparateEXT 
+glBlendFuncSeparateINGR 
+glBlendFuncSeparateIndexedAMD 
+glBlendFuncSeparateOES 
+glBlendFuncSeparatei 
+glBlendFuncSeparateiARB 
+glBlendFuncSeparateiEXT 
+glBlendFuncSeparateiOES 
+glBlendFunci 
+glBlendFunciARB 
+glBlendFunciEXT 
+glBlendFunciOES 
+glBlendParameteriNV 
+glBlitFramebuffer 
+glBlitFramebufferANGLE 
+glBlitFramebufferEXT 
+glBlitFramebufferNV 
+glBlitNamedFramebuffer 
+glBufferAddressRangeNV 
+glBufferData 
+glBufferDataARB 
+glBufferDataSysmem 
+glBufferPageCommitmentARB 
+glBufferParameteriAPPLE 
+glBufferStorage 
+glBufferStorageEXT 
+glBufferSubData 
+glBufferSubDataARB 
+glCallCommandListNV 
+glCallList 
+glCallLists 
+glCheckFramebufferStatus 
+glCheckFramebufferStatusEXT 
+glCheckFramebufferStatusOES 
+glCheckNamedFramebufferStatus 
+glCheckNamedFramebufferStatusEXT 
+glClampColor 
+glClampColorARB 
+glClear 
+glClearAccum 
+glClearAccumxOES 
+glClearBufferData 
+glClearBufferSubData 
+glClearBufferfi 
+glClearBufferfv 
+glClearBufferiv 
+glClearBufferuiv 
+glClearColor 
+glClearColorIiEXT 
+glClearColorIuiEXT 
+glClearColorx 
+glClearColorxOES 
+glClearDepth 
+glClearDepthdNV 
+glClearDepthf 
+glClearDepthfOES 
+glClearDepthx 
+glClearDepthxOES 
+glClearIndex 
+glClearNamedBufferData 
+glClearNamedBufferDataEXT 
+glClearNamedBufferSubData 
+glClearNamedBufferSubDataEXT 
+glClearNamedFramebufferfi 
+glClearNamedFramebufferfv 
+glClearNamedFramebufferiv 
+glClearNamedFramebufferuiv 
+glClearStencil 
+glClearTexImage 
+glClearTexSubImage 
+glClientActiveTexture 
+glClientActiveTextureARB 
+glClientActiveVertexStreamATI 
+glClientAttribDefaultEXT 
+glClientWaitSync 
+glClientWaitSyncAPPLE 
+glClientWaitSyncValueuiNVX 
+glClipControl 
+glClipPlane 
+glClipPlanef 
+glClipPlanefIMG 
+glClipPlanefOES 
+glClipPlanex 
+glClipPlanexIMG 
+glClipPlanexOES 
+glColor3b 
+glColor3bv 
+glColor3d 
+glColor3dv 
+glColor3f 
+glColor3fVertex3fSUN 
+glColor3fVertex3fvSUN 
+glColor3fv 
+glColor3hNV 
+glColor3hvNV 
+glColor3i 
+glColor3iv 
+glColor3s 
+glColor3sv 
+glColor3ub 
+glColor3ubv 
+glColor3ui 
+glColor3uiv 
+glColor3us 
+glColor3usv 
+glColor3x 
+glColor3xOES 
+glColor3xvOES 
+glColor4b 
+glColor4bv 
+glColor4d 
+glColor4dv 
+glColor4f 
+glColor4fNormal3fVertex3fSUN 
+glColor4fNormal3fVertex3fvSUN 
+glColor4fv 
+glColor4hNV 
+glColor4hvNV 
+glColor4i 
+glColor4iv 
+glColor4s 
+glColor4sv 
+glColor4ub 
+glColor4ubVertex2fSUN 
+glColor4ubVertex2fvSUN 
+glColor4ubVertex3fSUN 
+glColor4ubVertex3fvSUN 
+glColor4ubv 
+glColor4ui 
+glColor4uiv 
+glColor4us 
+glColor4usv 
+glColor4x 
+glColor4xOES 
+glColor4xvOES 
+glColorFormatNV 
+glColorFragmentOp1ATI 
+glColorFragmentOp2ATI 
+glColorFragmentOp3ATI 
+glColorMask 
+glColorMaskIndexedEXT 
+glColorMaski 
+glColorMaskiEXT 
+glColorMaskiOES 
+glColorMaterial 
+glColorP3ui 
+glColorP3uiv 
+glColorP4ui 
+glColorP4uiv 
+glColorPointer 
+glColorPointerEXT 
+glColorPointerListIBM 
+glColorPointervINTEL 
+glColorSubTable 
+glColorSubTableEXT 
+glColorTable 
+glColorTableEXT 
+glColorTableParameterfv 
+glColorTableParameterfvSGI 
+glColorTableParameteriv 
+glColorTableParameterivSGI 
+glColorTableSGI 
+glCombinerInputNV 
+glCombinerOutputNV 
+glCombinerParameterfNV 
+glCombinerParameterfvNV 
+glCombinerParameteriNV 
+glCombinerParameterivNV 
+glCombinerStageParameterfvNV 
+glCommandListSegmentsNV 
+glCompileCommandListNV 
+glCompileShader 
+glCompileShaderARB 
+glCompileShaderIncludeARB 
+glCompressedMultiTexImage1DEXT 
+glCompressedMultiTexImage2DEXT 
+glCompressedMultiTexImage3DEXT 
+glCompressedMultiTexSubImage1DEXT 
+glCompressedMultiTexSubImage2DEXT 
+glCompressedMultiTexSubImage3DEXT 
+glCompressedTexImage1D 
+glCompressedTexImage1DARB 
+glCompressedTexImage2D 
+glCompressedTexImage2DARB 
+glCompressedTexImage3D 
+glCompressedTexImage3DARB 
+glCompressedTexImage3DNV 
+glCompressedTexImage3DOES 
+glCompressedTexSubImage1D 
+glCompressedTexSubImage1DARB 
+glCompressedTexSubImage2D 
+glCompressedTexSubImage2DARB 
+glCompressedTexSubImage3D 
+glCompressedTexSubImage3DARB 
+glCompressedTexSubImage3DNV 
+glCompressedTexSubImage3DOES 
+glCompressedTextureImage1DEXT 
+glCompressedTextureImage2DEXT 
+glCompressedTextureImage3DEXT 
+glCompressedTextureSubImage1D 
+glCompressedTextureSubImage1DEXT 
+glCompressedTextureSubImage2D 
+glCompressedTextureSubImage2DEXT 
+glCompressedTextureSubImage3D 
+glCompressedTextureSubImage3DEXT 
+glConservativeRasterParameterfNV 
+glConvolutionFilter1D 
+glConvolutionFilter1DEXT 
+glConvolutionFilter2D 
+glConvolutionFilter2DEXT 
+glConvolutionParameterf 
+glConvolutionParameterfEXT 
+glConvolutionParameterfv 
+glConvolutionParameterfvEXT 
+glConvolutionParameteri 
+glConvolutionParameteriEXT 
+glConvolutionParameteriv 
+glConvolutionParameterivEXT 
+glConvolutionParameterxOES 
+glConvolutionParameterxvOES 
+glCopyBufferSubData 
+glCopyBufferSubDataNV 
+glCopyColorSubTable 
+glCopyColorSubTableEXT 
+glCopyColorTable 
+glCopyColorTableSGI 
+glCopyConvolutionFilter1D 
+glCopyConvolutionFilter1DEXT 
+glCopyConvolutionFilter2D 
+glCopyConvolutionFilter2DEXT 
+glCopyImageSubData 
+glCopyImageSubDataEXT 
+glCopyImageSubDataNV 
+glCopyImageSubDataOES 
+glCopyMultiTexImage1DEXT 
+glCopyMultiTexImage2DEXT 
+glCopyMultiTexSubImage1DEXT 
+glCopyMultiTexSubImage2DEXT 
+glCopyMultiTexSubImage3DEXT 
+glCopyNamedBufferSubData 
+glCopyPathNV 
+glCopyPixels 
+glCopyTexImage1D 
+glCopyTexImage1DEXT 
+glCopyTexImage2D 
+glCopyTexImage2DEXT 
+glCopyTexSubImage1D 
+glCopyTexSubImage1DEXT 
+glCopyTexSubImage2D 
+glCopyTexSubImage2DEXT 
+glCopyTexSubImage3D 
+glCopyTexSubImage3DEXT 
+glCopyTexSubImage3DNV 
+glCopyTexSubImage3DOES 
+glCopyTextureImage1DEXT 
+glCopyTextureImage2DEXT 
+glCopyTextureLevelsAPPLE 
+glCopyTextureSubImage1D 
+glCopyTextureSubImage1DEXT 
+glCopyTextureSubImage2D 
+glCopyTextureSubImage2DEXT 
+glCopyTextureSubImage3D 
+glCopyTextureSubImage3DEXT 
+glCoverFillPathInstancedNV 
+glCoverFillPathNV 
+glCoverStrokePathInstancedNV 
+glCoverStrokePathNV 
+glCoverageMaskNV 
+glCoverageModulationNV 
+glCoverageModulationTableNV 
+glCoverageOperationNV 
+glCreateBuffers 
+glCreateCommandListsNV 
+glCreateFramebuffers 
+glCreatePerfQueryINTEL 
+glCreateProgram 
+glCreateProgramObjectARB 
+glCreateProgramPipelines 
+glCreateQueries 
+glCreateRenderbuffers 
+glCreateSamplers 
+glCreateShader 
+glCreateShaderObjectARB 
+glCreateShaderProgramEXT 
+glCreateShaderProgramv 
+glCreateShaderProgramvEXT 
+glCreateStatesNV 
+glCreateSyncFromCLeventARB 
+glCreateTextures 
+glCreateTransformFeedbacks 
+glCreateVertexArrays 
+glCullFace 
+glCullParameterdvEXT 
+glCullParameterfvEXT 
+glCurrentPaletteMatrixARB 
+glCurrentPaletteMatrixOES 
+glDebugMessageCallback 
+glDebugMessageCallbackAMD 
+glDebugMessageCallbackARB 
+glDebugMessageCallbackKHR 
+glDebugMessageCallbackOES 
+glDebugMessageControl 
+glDebugMessageControlARB 
+glDebugMessageControlKHR 
+glDebugMessageControlOES 
+glDebugMessageEnableAMD 
+glDebugMessageInsert 
+glDebugMessageInsertAMD 
+glDebugMessageInsertARB 
+glDebugMessageInsertKHR 
+glDebugMessageInsertOES 
+glDeformSGIX 
+glDeformationMap3dSGIX 
+glDeformationMap3fSGIX 
+glDeleteAsyncMarkersSGIX 
+glDeleteBuffers 
+glDeleteBuffersARB 
+glDeleteCommandListsNV 
+glDeleteFencesAPPLE 
+glDeleteFencesNV 
+glDeleteFragmentShaderATI 
+glDeleteFramebuffers 
+glDeleteFramebuffersEXT 
+glDeleteFramebuffersOES 
+glDeleteLists 
+glDeleteNamedStringARB 
+glDeleteNamesAMD 
+glDeleteObjectARB 
+glDeleteOcclusionQueriesNV 
+glDeletePathsNV 
+glDeletePerfMonitorsAMD 
+glDeletePerfQueryINTEL 
+glDeleteProgram 
+glDeleteProgramPipelines 
+glDeleteProgramPipelinesEXT 
+glDeleteProgramsARB 
+glDeleteProgramsNV 
+glDeleteQueries 
+glDeleteQueriesARB 
+glDeleteQueriesEXT 
+glDeleteRenderbuffers 
+glDeleteRenderbuffersEXT 
+glDeleteRenderbuffersOES 
+glDeleteSamplers 
+glDeleteShader 
+glDeleteStatesNV 
+glDeleteSync 
+glDeleteSyncAPPLE 
+glDeleteTextures 
+glDeleteTexturesEXT 
+glDeleteTransformFeedbacks 
+glDeleteTransformFeedbacksEXT 
+glDeleteTransformFeedbacksNV 
+glDeleteVertexArrays 
+glDeleteVertexArraysAPPLE 
+glDeleteVertexArraysOES 
+glDeleteVertexShaderEXT 
+glDepthBoundsEXT 
+glDepthBoundsdNV 
+glDepthFunc 
+glDepthMask 
+glDepthRange 
+glDepthRangeArraydvNV 
+glDepthRangeArrayfvNV 
+glDepthRangeArrayv 
+glDepthRangeIndexed 
+glDepthRangeIndexeddNV 
+glDepthRangeIndexedfNV 
+glDepthRangedNV 
+glDepthRangef 
+glDepthRangefOES 
+glDepthRangex 
+glDepthRangexOES 
+glDetachObjectARB 
+glDetachShader 
+glDetailTexFuncSGIS 
+glDevtoolsCorrelationIdNVX 
+glDevtoolsInvokeStreamedCallbackNVX 
+glDisable 
+glDisableClientState 
+glDisableClientStateIndexedEXT 
+glDisableClientStateiEXT 
+glDisableDriverControlQCOM 
+glDisableIndexedEXT 
+glDisableVariantClientStateEXT 
+glDisableVertexArrayAttrib 
+glDisableVertexArrayAttribEXT 
+glDisableVertexArrayEXT 
+glDisableVertexAttribAPPLE 
+glDisableVertexAttribArray 
+glDisableVertexAttribArrayARB 
+glDisablei 
+glDisableiEXT 
+glDisableiNV 
+glDisableiOES 
+glDiscardFramebufferEXT 
+glDispatchCompute 
+glDispatchComputeGroupSizeARB 
+glDispatchComputeIndirect 
+glDrawArrays 
+glDrawArraysEXT 
+glDrawArraysIndirect 
+glDrawArraysInstanced 
+glDrawArraysInstancedANGLE 
+glDrawArraysInstancedARB 
+glDrawArraysInstancedBaseInstance 
+glDrawArraysInstancedBaseInstanceEXT 
+glDrawArraysInstancedEXT 
+glDrawArraysInstancedNV 
+glDrawBuffer 
+glDrawBuffers 
+glDrawBuffersARB 
+glDrawBuffersATI 
+glDrawBuffersEXT 
+glDrawBuffersIndexedEXT 
+glDrawBuffersNV 
+glDrawCommandsAddressNV 
+glDrawCommandsNV 
+glDrawCommandsStatesAddressNV 
+glDrawCommandsStatesNV 
+glDrawElementArrayAPPLE 
+glDrawElementArrayATI 
+glDrawElements 
+glDrawElementsBaseVertex 
+glDrawElementsBaseVertexEXT 
+glDrawElementsBaseVertexOES 
+glDrawElementsIndirect 
+glDrawElementsInstanced 
+glDrawElementsInstancedANGLE 
+glDrawElementsInstancedARB 
+glDrawElementsInstancedBaseInstance 
+glDrawElementsInstancedBaseInstanceEXT 
+glDrawElementsInstancedBaseVertex 
+glDrawElementsInstancedBaseVertexBaseInstance 
+glDrawElementsInstancedBaseVertexBaseInstanceEXT 
+glDrawElementsInstancedBaseVertexEXT 
+glDrawElementsInstancedBaseVertexOES 
+glDrawElementsInstancedEXT 
+glDrawElementsInstancedNV 
+glDrawMeshArraysSUN 
+glDrawMeshNV 
+glDrawPixels 
+glDrawRangeElementArrayAPPLE 
+glDrawRangeElementArrayATI 
+glDrawRangeElements 
+glDrawRangeElementsBaseVertex 
+glDrawRangeElementsBaseVertexEXT 
+glDrawRangeElementsBaseVertexOES 
+glDrawRangeElementsEXT 
+glDrawTexfOES 
+glDrawTexfvOES 
+glDrawTexiOES 
+glDrawTexivOES 
+glDrawTexsOES 
+glDrawTexsvOES 
+glDrawTextureNV 
+glDrawTexxOES 
+glDrawTexxvOES 
+glDrawTransformFeedback 
+glDrawTransformFeedbackEXT 
+glDrawTransformFeedbackInstanced 
+glDrawTransformFeedbackNV 
+glDrawTransformFeedbackStream 
+glDrawTransformFeedbackStreamInstanced 
+glEGLImageTargetRenderbufferStorageOES 
+glEGLImageTargetTexture2DOES 
+glEdgeFlag 
+glEdgeFlagFormatNV 
+glEdgeFlagPointer 
+glEdgeFlagPointerEXT 
+glEdgeFlagPointerListIBM 
+glEdgeFlagv 
+glElementPointerAPPLE 
+glElementPointerATI 
+glEnable 
+glEnableClientState 
+glEnableClientStateIndexedEXT 
+glEnableClientStateiEXT 
+glEnableDriverControlQCOM 
+glEnableIndexedEXT 
+glEnableVariantClientStateEXT 
+glEnableVertexArrayAttrib 
+glEnableVertexArrayAttribEXT 
+glEnableVertexArrayEXT 
+glEnableVertexAttribAPPLE 
+glEnableVertexAttribArray 
+glEnableVertexAttribArrayARB 
+glEnablei 
+glEnableiEXT 
+glEnableiNV 
+glEnableiOES 
+glEnd 
+glEndConditionalRender 
+glEndConditionalRenderNV 
+glEndConditionalRenderNVX 
+glEndFragmentShaderATI 
+glEndList 
+glEndOcclusionQueryNV 
+glEndPerfMonitorAMD 
+glEndPerfQueryINTEL 
+glEndQuery 
+glEndQueryARB 
+glEndQueryEXT 
+glEndQueryIndexed 
+glEndTilingQCOM 
+glEndTransformFeedback 
+glEndTransformFeedbackEXT 
+glEndTransformFeedbackNV 
+glEndVertexShaderEXT 
+glEndVideoCaptureNV 
+glEvalCoord1d 
+glEvalCoord1dv 
+glEvalCoord1f 
+glEvalCoord1fv 
+glEvalCoord1xOES 
+glEvalCoord1xvOES 
+glEvalCoord2d 
+glEvalCoord2dv 
+glEvalCoord2f 
+glEvalCoord2fv 
+glEvalCoord2xOES 
+glEvalCoord2xvOES 
+glEvalMapsNV 
+glEvalMesh1 
+glEvalMesh2 
+glEvalPoint1 
+glEvalPoint2 
+glEvaluateDepthValuesARB 
+glExecuteProgramNV 
+glExtGetBufferPointervQCOM 
+glExtGetBuffersQCOM 
+glExtGetFramebuffersQCOM 
+glExtGetProgramBinarySourceQCOM 
+glExtGetProgramsQCOM 
+glExtGetRenderbuffersQCOM 
+glExtGetShadersQCOM 
+glExtGetTexLevelParameterivQCOM 
+glExtGetTexSubImageQCOM 
+glExtGetTexturesQCOM 
+glExtIsProgramBinaryQCOM 
+glExtTexObjectStateOverrideiQCOM 
+glExtractComponentEXT 
+glFeedbackBuffer 
+glFeedbackBufferxOES 
+glFenceSync 
+glFenceSyncAPPLE 
+glFenceValueuiNVX 
+glFinalCombinerInputNV 
+glFinish 
+glFinishAsyncSGIX 
+glFinishFenceAPPLE 
+glFinishFenceNV 
+glFinishObjectAPPLE 
+glFinishTextureSUNX 
+glFlush 
+glFlushMappedBufferRange 
+glFlushMappedBufferRangeAPPLE 
+glFlushMappedBufferRangeEXT 
+glFlushMappedNamedBufferRange 
+glFlushMappedNamedBufferRangeEXT 
+glFlushPixelDataRangeNV 
+glFlushRasterSGIX 
+glFlushStaticDataIBM 
+glFlushVertexArrayRangeAPPLE 
+glFlushVertexArrayRangeNV 
+glFogCoordFormatNV 
+glFogCoordPointer 
+glFogCoordPointerEXT 
+glFogCoordPointerListIBM 
+glFogCoordd 
+glFogCoorddEXT 
+glFogCoorddv 
+glFogCoorddvEXT 
+glFogCoordf 
+glFogCoordfEXT 
+glFogCoordfv 
+glFogCoordfvEXT 
+glFogCoordhNV 
+glFogCoordhvNV 
+glFogFuncSGIS 
+glFogf 
+glFogfv 
+glFogi 
+glFogiv 
+glFogx 
+glFogxOES 
+glFogxv 
+glFogxvOES 
+glFragmentColorMaterialSGIX 
+glFragmentCoverageColorNV 
+glFragmentLightModelfSGIX 
+glFragmentLightModelfvSGIX 
+glFragmentLightModeliSGIX 
+glFragmentLightModelivSGIX 
+glFragmentLightfSGIX 
+glFragmentLightfvSGIX 
+glFragmentLightiSGIX 
+glFragmentLightivSGIX 
+glFragmentMaterialfSGIX 
+glFragmentMaterialfvSGIX 
+glFragmentMaterialiSGIX 
+glFragmentMaterialivSGIX 
+glFrameTerminatorGREMEDY 
+glFrameZoomSGIX 
+glFramebufferDrawBufferEXT 
+glFramebufferDrawBuffersEXT 
+glFramebufferParameteri 
+glFramebufferReadBufferEXT 
+glFramebufferRenderbuffer 
+glFramebufferRenderbufferEXT 
+glFramebufferRenderbufferOES 
+glFramebufferSampleLocationsfvARB 
+glFramebufferSampleLocationsfvNV 
+glFramebufferTexture 
+glFramebufferTexture1D 
+glFramebufferTexture1DEXT 
+glFramebufferTexture2D 
+glFramebufferTexture2DEXT 
+glFramebufferTexture2DMultisampleEXT 
+glFramebufferTexture2DMultisampleIMG 
+glFramebufferTexture2DOES 
+glFramebufferTexture3D 
+glFramebufferTexture3DEXT 
+glFramebufferTexture3DOES 
+glFramebufferTextureARB 
+glFramebufferTextureEXT 
+glFramebufferTextureFaceARB 
+glFramebufferTextureFaceEXT 
+glFramebufferTextureLayer 
+glFramebufferTextureLayerARB 
+glFramebufferTextureLayerEXT 
+glFramebufferTextureLayerNV 
+glFramebufferTextureMultisampleMultiviewOVR 
+glFramebufferTextureMultiviewOVR 
+glFramebufferTextureOES 
+glFreeObjectBufferATI 
+glFrontFace 
+glFrustum 
+glFrustumf 
+glFrustumfOES 
+glFrustumx 
+glFrustumxOES 
+glGenAsyncMarkersSGIX 
+glGenBuffers 
+glGenBuffersARB 
+glGenFencesAPPLE 
+glGenFencesNV 
+glGenFragmentShadersATI 
+glGenFramebuffers 
+glGenFramebuffersEXT 
+glGenFramebuffersOES 
+glGenLists 
+glGenNamesAMD 
+glGenOcclusionQueriesNV 
+glGenPathsNV 
+glGenPerfMonitorsAMD 
+glGenProgramPipelines 
+glGenProgramPipelinesEXT 
+glGenProgramsARB 
+glGenProgramsNV 
+glGenQueries 
+glGenQueriesARB 
+glGenQueriesEXT 
+glGenRenderbuffers 
+glGenRenderbuffersEXT 
+glGenRenderbuffersOES 
+glGenSamplers 
+glGenSymbolsEXT 
+glGenTextures 
+glGenTexturesEXT 
+glGenTransformFeedbacks 
+glGenTransformFeedbacksEXT 
+glGenTransformFeedbacksNV 
+glGenVertexArrays 
+glGenVertexArraysAPPLE 
+glGenVertexArraysOES 
+glGenVertexShadersEXT 
+glGenerateMipmap 
+glGenerateMipmapEXT 
+glGenerateMipmapOES 
+glGenerateMultiTexMipmapEXT 
+glGenerateTextureMipmap 
+glGenerateTextureMipmapEXT 
+glGetActiveAtomicCounterBufferiv 
+glGetActiveAttrib 
+glGetActiveAttribARB 
+glGetActiveSubroutineName 
+glGetActiveSubroutineUniformName 
+glGetActiveSubroutineUniformiv 
+glGetActiveUniform 
+glGetActiveUniformARB 
+glGetActiveUniformBlockName 
+glGetActiveUniformBlockiv 
+glGetActiveUniformName 
+glGetActiveUniformsiv 
+glGetActiveVaryingNV 
+glGetArrayObjectfvATI 
+glGetArrayObjectivATI 
+glGetAttachedObjectsARB 
+glGetAttachedShaders 
+glGetAttribLocation 
+glGetAttribLocationARB 
+glGetBooleanIndexedvEXT 
+glGetBooleani_v 
+glGetBooleanv 
+glGetBufferParameteri64v 
+glGetBufferParameteriv 
+glGetBufferParameterivARB 
+glGetBufferParameterui64vNV 
+glGetBufferPointerv 
+glGetBufferPointervARB 
+glGetBufferPointervOES 
+glGetBufferSubData 
+glGetBufferSubDataARB 
+glGetClipPlane 
+glGetClipPlanef 
+glGetClipPlanefOES 
+glGetClipPlanex 
+glGetClipPlanexOES 
+glGetColorTable 
+glGetColorTableEXT 
+glGetColorTableParameterfv 
+glGetColorTableParameterfvEXT 
+glGetColorTableParameterfvSGI 
+glGetColorTableParameteriv 
+glGetColorTableParameterivEXT 
+glGetColorTableParameterivSGI 
+glGetColorTableSGI 
+glGetCombinerInputParameterfvNV 
+glGetCombinerInputParameterivNV 
+glGetCombinerOutputParameterfvNV 
+glGetCombinerOutputParameterivNV 
+glGetCombinerStageParameterfvNV 
+glGetCommandHeaderNV 
+glGetCompressedMultiTexImageEXT 
+glGetCompressedTexImage 
+glGetCompressedTexImageARB 
+glGetCompressedTexImageNV 
+glGetCompressedTextureImage 
+glGetCompressedTextureImageEXT 
+glGetCompressedTextureSubImage 
+glGetConvolutionFilter 
+glGetConvolutionFilterEXT 
+glGetConvolutionParameterfv 
+glGetConvolutionParameterfvEXT 
+glGetConvolutionParameteriv 
+glGetConvolutionParameterivEXT 
+glGetConvolutionParameterxvOES 
+glGetCoverageModulationTableNV 
+glGetDebugMessageLog 
+glGetDebugMessageLogAMD 
+glGetDebugMessageLogARB 
+glGetDebugMessageLogKHR 
+glGetDebugMessageLogOES 
+glGetDetailTexFuncSGIS 
+glGetDoubleIndexedvEXT 
+glGetDoublei_v 
+glGetDoublei_vEXT 
+glGetDoublev 
+glGetDriverControlStringQCOM 
+glGetDriverControlsQCOM 
+glGetError 
+glGetFenceivNV 
+glGetFinalCombinerInputParameterfvNV 
+glGetFinalCombinerInputParameterivNV 
+glGetFirstPerfQueryIdINTEL 
+glGetFixedv 
+glGetFixedvOES 
+glGetFloatIndexedvEXT 
+glGetFloati_v 
+glGetFloati_vEXT 
+glGetFloati_vNV 
+glGetFloatv 
+glGetFogFuncSGIS 
+glGetFragDataIndex 
+glGetFragDataIndexEXT 
+glGetFragDataLocation 
+glGetFragDataLocationEXT 
+glGetFragmentLightfvSGIX 
+glGetFragmentLightivSGIX 
+glGetFragmentMaterialfvSGIX 
+glGetFragmentMaterialivSGIX 
+glGetFramebufferAttachmentParameteriv 
+glGetFramebufferAttachmentParameterivEXT 
+glGetFramebufferAttachmentParameterivOES 
+glGetFramebufferParameteriv 
+glGetFramebufferParameterivEXT 
+glGetGraphicsResetStatus 
+glGetGraphicsResetStatusARB 
+glGetGraphicsResetStatusEXT 
+glGetGraphicsResetStatusKHR 
+glGetHandleARB 
+glGetHistogram 
+glGetHistogramEXT 
+glGetHistogramParameterfv 
+glGetHistogramParameterfvEXT 
+glGetHistogramParameteriv 
+glGetHistogramParameterivEXT 
+glGetHistogramParameterxvOES 
+glGetImageHandleARB 
+glGetImageHandleNV 
+glGetImageTransformParameterfvHP 
+glGetImageTransformParameterivHP 
+glGetInfoLogARB 
+glGetInstrumentsSGIX 
+glGetInteger64i_v 
+glGetInteger64v 
+glGetInteger64vAPPLE 
+glGetIntegerIndexedvEXT 
+glGetIntegeri_v 
+glGetIntegeri_vEXT 
+glGetIntegerui64i_vNV 
+glGetIntegerui64vNV 
+glGetIntegerv 
+glGetInternalformatSampleivNV 
+glGetInternalformati64v 
+glGetInternalformativ 
+glGetInvariantBooleanvEXT 
+glGetInvariantFloatvEXT 
+glGetInvariantIntegervEXT 
+glGetLightfv 
+glGetLightiv 
+glGetLightxOES 
+glGetLightxv 
+glGetLightxvOES 
+glGetListParameterfvSGIX 
+glGetListParameterivSGIX 
+glGetLocalConstantBooleanvEXT 
+glGetLocalConstantFloatvEXT 
+glGetLocalConstantIntegervEXT 
+glGetMapAttribParameterfvNV 
+glGetMapAttribParameterivNV 
+glGetMapControlPointsNV 
+glGetMapParameterfvNV 
+glGetMapParameterivNV 
+glGetMapdv 
+glGetMapfv 
+glGetMapiv 
+glGetMapxvOES 
+glGetMaterialfv 
+glGetMaterialiv 
+glGetMaterialxOES 
+glGetMaterialxv 
+glGetMaterialxvOES 
+glGetMinmax 
+glGetMinmaxEXT 
+glGetMinmaxParameterfv 
+glGetMinmaxParameterfvEXT 
+glGetMinmaxParameteriv 
+glGetMinmaxParameterivEXT 
+glGetMultiTexEnvfvEXT 
+glGetMultiTexEnvivEXT 
+glGetMultiTexGendvEXT 
+glGetMultiTexGenfvEXT 
+glGetMultiTexGenivEXT 
+glGetMultiTexImageEXT 
+glGetMultiTexLevelParameterfvEXT 
+glGetMultiTexLevelParameterivEXT 
+glGetMultiTexParameterIivEXT 
+glGetMultiTexParameterIuivEXT 
+glGetMultiTexParameterfvEXT 
+glGetMultiTexParameterivEXT 
+glGetMultisamplefv 
+glGetMultisamplefvNV 
+glGetNamedBufferParameteri64v 
+glGetNamedBufferParameteri64vEXT 
+glGetNamedBufferParameteriv 
+glGetNamedBufferParameterivEXT 
+glGetNamedBufferParameterui64vNV 
+glGetNamedBufferPointerv 
+glGetNamedBufferPointervEXT 
+glGetNamedBufferSubData 
+glGetNamedBufferSubDataEXT 
+glGetNamedFramebufferAttachmentParameteriv 
+glGetNamedFramebufferAttachmentParameterivEXT 
+glGetNamedFramebufferParameteriv 
+glGetNamedFramebufferParameterivEXT 
+glGetNamedProgramLocalParameterIivEXT 
+glGetNamedProgramLocalParameterIuivEXT 
+glGetNamedProgramLocalParameterdvEXT 
+glGetNamedProgramLocalParameterfvEXT 
+glGetNamedProgramStringEXT 
+glGetNamedProgramivEXT 
+glGetNamedRenderbufferParameteriv 
+glGetNamedRenderbufferParameterivEXT 
+glGetNamedStringARB 
+glGetNamedStringivARB 
+glGetNextPerfQueryIdINTEL 
+glGetObjectBufferfvATI 
+glGetObjectBufferivATI 
+glGetObjectLabel 
+glGetObjectLabelEXT 
+glGetObjectLabelKHR 
+glGetObjectLabelOES 
+glGetObjectParameterfvARB 
+glGetObjectParameterivAPPLE 
+glGetObjectParameterivARB 
+glGetObjectPtrLabel 
+glGetObjectPtrLabelKHR 
+glGetObjectPtrLabelOES 
+glGetOcclusionQueryivNV 
+glGetOcclusionQueryuivNV 
+glGetPathColorGenfvNV 
+glGetPathColorGenivNV 
+glGetPathCommandsNV 
+glGetPathCoordsNV 
+glGetPathDashArrayNV 
+glGetPathLengthNV 
+glGetPathMetricRangeNV 
+glGetPathMetricsNV 
+glGetPathParameterfvNV 
+glGetPathParameterivNV 
+glGetPathSpacingNV 
+glGetPathTexGenfvNV 
+glGetPathTexGenivNV 
+glGetPerfCounterInfoINTEL 
+glGetPerfMonitorCounterDataAMD 
+glGetPerfMonitorCounterInfoAMD 
+glGetPerfMonitorCounterStringAMD 
+glGetPerfMonitorCountersAMD 
+glGetPerfMonitorGroupStringAMD 
+glGetPerfMonitorGroupsAMD 
+glGetPerfQueryDataINTEL 
+glGetPerfQueryIdByNameINTEL 
+glGetPerfQueryInfoINTEL 
+glGetPixelMapfv 
+glGetPixelMapuiv 
+glGetPixelMapusv 
+glGetPixelMapxv 
+glGetPixelTexGenParameterfvSGIS 
+glGetPixelTexGenParameterivSGIS 
+glGetPixelTransformParameterfvEXT 
+glGetPixelTransformParameterivEXT 
+glGetPointerIndexedvEXT 
+glGetPointeri_vEXT 
+glGetPointerv 
+glGetPointervEXT 
+glGetPointervKHR 
+glGetPointervOES 
+glGetPolygonStipple 
+glGetProgramBinary 
+glGetProgramBinaryOES 
+glGetProgramEnvParameterIivNV 
+glGetProgramEnvParameterIuivNV 
+glGetProgramEnvParameterdvARB 
+glGetProgramEnvParameterfvARB 
+glGetProgramInfoLog 
+glGetProgramInterfaceiv 
+glGetProgramLocalParameterIivNV 
+glGetProgramLocalParameterIuivNV 
+glGetProgramLocalParameterdvARB 
+glGetProgramLocalParameterfvARB 
+glGetProgramNamedParameterdvNV 
+glGetProgramNamedParameterfvNV 
+glGetProgramParameterdvNV 
+glGetProgramParameterfvNV 
+glGetProgramPipelineInfoLog 
+glGetProgramPipelineInfoLogEXT 
+glGetProgramPipelineiv 
+glGetProgramPipelineivEXT 
+glGetProgramResourceIndex 
+glGetProgramResourceLocation 
+glGetProgramResourceLocationIndex 
+glGetProgramResourceLocationIndexEXT 
+glGetProgramResourceName 
+glGetProgramResourcefvNV 
+glGetProgramResourceiv 
+glGetProgramStageiv 
+glGetProgramStringARB 
+glGetProgramStringNV 
+glGetProgramSubroutineParameteruivNV 
+glGetProgramiv 
+glGetProgramivARB 
+glGetProgramivNV 
+glGetQueryBufferObjecti64v 
+glGetQueryBufferObjectiv 
+glGetQueryBufferObjectui64v 
+glGetQueryBufferObjectuiv 
+glGetQueryIndexediv 
+glGetQueryObjecti64v 
+glGetQueryObjecti64vEXT 
+glGetQueryObjectiv 
+glGetQueryObjectivARB 
+glGetQueryObjectivEXT 
+glGetQueryObjectui64v 
+glGetQueryObjectui64vEXT 
+glGetQueryObjectui64vNV 
+glGetQueryObjectuiv 
+glGetQueryObjectuivARB 
+glGetQueryObjectuivEXT 
+glGetQueryiv 
+glGetQueryivARB 
+glGetQueryivEXT 
+glGetRenderbufferParameteriv 
+glGetRenderbufferParameterivEXT 
+glGetRenderbufferParameterivOES 
+glGetSamplerParameterIiv 
+glGetSamplerParameterIivEXT 
+glGetSamplerParameterIivOES 
+glGetSamplerParameterIuiv 
+glGetSamplerParameterIuivEXT 
+glGetSamplerParameterIuivOES 
+glGetSamplerParameterfv 
+glGetSamplerParameteriv 
+glGetSeparableFilter 
+glGetSeparableFilterEXT 
+glGetShaderInfoLog 
+glGetShaderPrecisionFormat 
+glGetShaderSource 
+glGetShaderSourceARB 
+glGetShaderiv 
+glGetSharpenTexFuncSGIS 
+glGetStageIndexNV 
+glGetString 
+glGetStringi 
+glGetSubroutineIndex 
+glGetSubroutineUniformLocation 
+glGetSynciv 
+glGetSyncivAPPLE 
+glGetSyncv64NVX 
+glGetTexBumpParameterfvATI 
+glGetTexBumpParameterivATI 
+glGetTexEnvfv 
+glGetTexEnviv 
+glGetTexEnvxv 
+glGetTexEnvxvOES 
+glGetTexFilterFuncSGIS 
+glGetTexGendv 
+glGetTexGenfv 
+glGetTexGenfvOES 
+glGetTexGeniv 
+glGetTexGenivOES 
+glGetTexGenxvOES 
+glGetTexImage 
+glGetTexImageNV 
+glGetTexLevelParameterfv 
+glGetTexLevelParameterfvNV 
+glGetTexLevelParameteriv 
+glGetTexLevelParameterivNV 
+glGetTexLevelParameterxvOES 
+glGetTexParameterIiv 
+glGetTexParameterIivEXT 
+glGetTexParameterIivOES 
+glGetTexParameterIuiv 
+glGetTexParameterIuivEXT 
+glGetTexParameterIuivOES 
+glGetTexParameterPointervAPPLE 
+glGetTexParameterfv 
+glGetTexParameteriv 
+glGetTexParameterxv 
+glGetTexParameterxvOES 
+glGetTextureHandleARB 
+glGetTextureHandleNV 
+glGetTextureImage 
+glGetTextureImageEXT 
+glGetTextureLevelParameterfv 
+glGetTextureLevelParameterfvEXT 
+glGetTextureLevelParameteriv 
+glGetTextureLevelParameterivEXT 
+glGetTextureParameterIiv 
+glGetTextureParameterIivEXT 
+glGetTextureParameterIuiv 
+glGetTextureParameterIuivEXT 
+glGetTextureParameterfv 
+glGetTextureParameterfvEXT 
+glGetTextureParameteriv 
+glGetTextureParameterivEXT 
+glGetTextureSamplerHandleARB 
+glGetTextureSamplerHandleNV 
+glGetTextureSubImage 
+glGetTrackMatrixivNV 
+glGetTransformFeedbackVarying 
+glGetTransformFeedbackVaryingEXT 
+glGetTransformFeedbackVaryingNV 
+glGetTransformFeedbacki64_v 
+glGetTransformFeedbacki_v 
+glGetTransformFeedbackiv 
+glGetTranslatedShaderSourceANGLE 
+glGetUniformBlockIndex 
+glGetUniformBufferSizeEXT 
+glGetUniformIndices 
+glGetUniformLocation 
+glGetUniformLocationARB 
+glGetUniformOffsetEXT 
+glGetUniformSubroutineuiv 
+glGetUniformdv 
+glGetUniformfv 
+glGetUniformfvARB 
+glGetUniformi64vARB 
+glGetUniformi64vNV 
+glGetUniformiv 
+glGetUniformivARB 
+glGetUniformui64vARB 
+glGetUniformui64vNV 
+glGetUniformuiv 
+glGetUniformuivEXT 
+glGetVariantArrayObjectfvATI 
+glGetVariantArrayObjectivATI 
+glGetVariantBooleanvEXT 
+glGetVariantFloatvEXT 
+glGetVariantIntegervEXT 
+glGetVariantPointervEXT 
+glGetVaryingLocationNV 
+glGetVertexArrayIndexed64iv 
+glGetVertexArrayIndexediv 
+glGetVertexArrayIntegeri_vEXT 
+glGetVertexArrayIntegervEXT 
+glGetVertexArrayPointeri_vEXT 
+glGetVertexArrayPointervEXT 
+glGetVertexArrayiv 
+glGetVertexAttribArrayObjectfvATI 
+glGetVertexAttribArrayObjectivATI 
+glGetVertexAttribIiv 
+glGetVertexAttribIivEXT 
+glGetVertexAttribIuiv 
+glGetVertexAttribIuivEXT 
+glGetVertexAttribLdv 
+glGetVertexAttribLdvEXT 
+glGetVertexAttribLi64vNV 
+glGetVertexAttribLui64vARB 
+glGetVertexAttribLui64vNV 
+glGetVertexAttribPointerv 
+glGetVertexAttribPointervARB 
+glGetVertexAttribPointervNV 
+glGetVertexAttribdv 
+glGetVertexAttribdvARB 
+glGetVertexAttribdvNV 
+glGetVertexAttribfv 
+glGetVertexAttribfvARB 
+glGetVertexAttribfvNV 
+glGetVertexAttribiv 
+glGetVertexAttribivARB 
+glGetVertexAttribivNV 
+glGetVideoCaptureANCStreamivNVX 
+glGetVideoCaptureStreamdvNV 
+glGetVideoCaptureStreamfvNV 
+glGetVideoCaptureStreamivNV 
+glGetVideoCaptureivNV 
+glGetVideoi64vNV 
+glGetVideoivNV 
+glGetVideoui64vNV 
+glGetVideouivNV 
+glGetnColorTable 
+glGetnColorTableARB 
+glGetnCompressedTexImage 
+glGetnCompressedTexImageARB 
+glGetnConvolutionFilter 
+glGetnConvolutionFilterARB 
+glGetnHistogram 
+glGetnHistogramARB 
+glGetnMapdv 
+glGetnMapdvARB 
+glGetnMapfv 
+glGetnMapfvARB 
+glGetnMapiv 
+glGetnMapivARB 
+glGetnMinmax 
+glGetnMinmaxARB 
+glGetnPixelMapfv 
+glGetnPixelMapfvARB 
+glGetnPixelMapuiv 
+glGetnPixelMapuivARB 
+glGetnPixelMapusv 
+glGetnPixelMapusvARB 
+glGetnPolygonStipple 
+glGetnPolygonStippleARB 
+glGetnSeparableFilter 
+glGetnSeparableFilterARB 
+glGetnTexImage 
+glGetnTexImageARB 
+glGetnUniformdv 
+glGetnUniformdvARB 
+glGetnUniformfv 
+glGetnUniformfvARB 
+glGetnUniformfvEXT 
+glGetnUniformfvKHR 
+glGetnUniformi64vARB 
+glGetnUniformiv 
+glGetnUniformivARB 
+glGetnUniformivEXT 
+glGetnUniformivKHR 
+glGetnUniformui64vARB 
+glGetnUniformuiv 
+glGetnUniformuivARB 
+glGetnUniformuivKHR 
+glGlobalAlphaFactorbSUN 
+glGlobalAlphaFactordSUN 
+glGlobalAlphaFactorfSUN 
+glGlobalAlphaFactoriSUN 
+glGlobalAlphaFactorsSUN 
+glGlobalAlphaFactorubSUN 
+glGlobalAlphaFactoruiSUN 
+glGlobalAlphaFactorusSUN 
+glGpuSyncAcquireNVX 
+glGpuSyncCreateNVX 
+glGpuSyncDestroyNVX 
+glGpuSyncReleaseNVX 
+glHint 
+glHintPGI 
+glHistogram 
+glHistogramEXT 
+glIglooInterfaceSGIX 
+glImageTransformParameterfHP 
+glImageTransformParameterfvHP 
+glImageTransformParameteriHP 
+glImageTransformParameterivHP 
+glImportSyncEXT 
+glIndexFormatNV 
+glIndexFuncEXT 
+glIndexMask 
+glIndexMaterialEXT 
+glIndexPointer 
+glIndexPointerEXT 
+glIndexPointerListIBM 
+glIndexd 
+glIndexdv 
+glIndexf 
+glIndexfv 
+glIndexi 
+glIndexiv 
+glIndexs 
+glIndexsv 
+glIndexub 
+glIndexubv 
+glIndexxOES 
+glIndexxvOES 
+glInitNames 
+glInsertComponentEXT 
+glInsertEventMarkerEXT 
+glInstrumentsBufferSGIX 
+glInterleavedArrays 
+glInterpolatePathsNV 
+glInvalidateBufferData 
+glInvalidateBufferSubData 
+glInvalidateFramebuffer 
+glInvalidateNamedFramebufferData 
+glInvalidateNamedFramebufferSubData 
+glInvalidateSubFramebuffer 
+glInvalidateTexImage 
+glInvalidateTexSubImage 
+glIsAsyncMarkerSGIX 
+glIsBuffer 
+glIsBufferARB 
+glIsBufferResidentNV 
+glIsCommandListNV 
+glIsEnabled 
+glIsEnabledIndexedEXT 
+glIsEnabledi 
+glIsEnablediEXT 
+glIsEnablediNV 
+glIsEnablediOES 
+glIsFenceAPPLE 
+glIsFenceNV 
+glIsFramebuffer 
+glIsFramebufferEXT 
+glIsFramebufferOES 
+glIsImageHandleResidentARB 
+glIsImageHandleResidentNV 
+glIsList 
+glIsNameAMD 
+glIsNamedBufferResidentNV 
+glIsNamedStringARB 
+glIsObjectBufferATI 
+glIsOcclusionQueryNV 
+glIsPathNV 
+glIsPointInFillPathNV 
+glIsPointInStrokePathNV 
+glIsProgram 
+glIsProgramARB 
+glIsProgramNV 
+glIsProgramPipeline 
+glIsProgramPipelineEXT 
+glIsQuery 
+glIsQueryARB 
+glIsQueryEXT 
+glIsRenderbuffer 
+glIsRenderbufferEXT 
+glIsRenderbufferOES 
+glIsSampler 
+glIsShader 
+glIsStateNV 
+glIsSync 
+glIsSyncAPPLE 
+glIsTexture 
+glIsTextureEXT 
+glIsTextureHandleResidentARB 
+glIsTextureHandleResidentNV 
+glIsTransformFeedback 
+glIsTransformFeedbackEXT 
+glIsTransformFeedbackNV 
+glIsVariantEnabledEXT 
+glIsVertexArray 
+glIsVertexArrayAPPLE 
+glIsVertexArrayOES 
+glIsVertexAttribEnabledAPPLE 
+glLabelObjectEXT 
+glLightEnviSGIX 
+glLightModelf 
+glLightModelfv 
+glLightModeli 
+glLightModeliv 
+glLightModelx 
+glLightModelxOES 
+glLightModelxv 
+glLightModelxvOES 
+glLightf 
+glLightfv 
+glLighti 
+glLightiv 
+glLightx 
+glLightxOES 
+glLightxv 
+glLightxvOES 
+glLineStipple 
+glLineWidth 
+glLineWidthx 
+glLineWidthxOES 
+glLinkProgram 
+glLinkProgramARB 
+glListBase 
+glListDrawCommandsStatesClientNV 
+glListParameterfSGIX 
+glListParameterfvSGIX 
+glListParameteriSGIX 
+glListParameterivSGIX 
+glLoadIdentity 
+glLoadIdentityDeformationMapSGIX 
+glLoadMatrixd 
+glLoadMatrixf 
+glLoadMatrixx 
+glLoadMatrixxOES 
+glLoadName 
+glLoadPaletteFromModelViewMatrixOES 
+glLoadProgramNV 
+glLoadTransformEXT 
+glLoadTransposeMatrixd 
+glLoadTransposeMatrixdARB 
+glLoadTransposeMatrixf 
+glLoadTransposeMatrixfARB 
+glLoadTransposeMatrixxOES 
+glLockArraysEXT 
+glLogicOp 
+glMakeBufferNonResidentNV 
+glMakeBufferResidentNV 
+glMakeImageHandleNonResidentARB 
+glMakeImageHandleNonResidentNV 
+glMakeImageHandleResidentARB 
+glMakeImageHandleResidentNV 
+glMakeNamedBufferNonResidentNV 
+glMakeNamedBufferResidentNV 
+glMakeTextureHandleNonResidentARB 
+glMakeTextureHandleNonResidentNV 
+glMakeTextureHandleResidentARB 
+glMakeTextureHandleResidentNV 
+glMap1d 
+glMap1f 
+glMap1xOES 
+glMap2d 
+glMap2f 
+glMap2xOES 
+glMapBuffer 
+glMapBufferARB 
+glMapBufferOES 
+glMapBufferRange 
+glMapBufferRangeEXT 
+glMapControlPointsNV 
+glMapGrid1d 
+glMapGrid1f 
+glMapGrid1xOES 
+glMapGrid2d 
+glMapGrid2f 
+glMapGrid2xOES 
+glMapNamedBuffer 
+glMapNamedBufferEXT 
+glMapNamedBufferRange 
+glMapNamedBufferRangeEXT 
+glMapObjectBufferATI 
+glMapParameterfvNV 
+glMapParameterivNV 
+glMapTexture2DINTEL 
+glMapVertexAttrib1dAPPLE 
+glMapVertexAttrib1fAPPLE 
+glMapVertexAttrib2dAPPLE 
+glMapVertexAttrib2fAPPLE 
+glMaterialf 
+glMaterialfv 
+glMateriali 
+glMaterialiv 
+glMaterialx 
+glMaterialxOES 
+glMaterialxv 
+glMaterialxvOES 
+glMatrixFrustumEXT 
+glMatrixIndexPointerARB 
+glMatrixIndexPointerOES 
+glMatrixIndexubvARB 
+glMatrixIndexuivARB 
+glMatrixIndexusvARB 
+glMatrixLoad3x2fNV 
+glMatrixLoad3x3fNV 
+glMatrixLoadIdentityEXT 
+glMatrixLoadTranspose3x3fNV 
+glMatrixLoadTransposedEXT 
+glMatrixLoadTransposefEXT 
+glMatrixLoaddEXT 
+glMatrixLoadfEXT 
+glMatrixMode 
+glMatrixMult3x2fNV 
+glMatrixMult3x3fNV 
+glMatrixMultTranspose3x3fNV 
+glMatrixMultTransposedEXT 
+glMatrixMultTransposefEXT 
+glMatrixMultdEXT 
+glMatrixMultfEXT 
+glMatrixOrthoEXT 
+glMatrixPopEXT 
+glMatrixPushEXT 
+glMatrixRotatedEXT 
+glMatrixRotatefEXT 
+glMatrixScaledEXT 
+glMatrixScalefEXT 
+glMatrixTranslatedEXT 
+glMatrixTranslatefEXT 
+glMaxShaderCompilerThreadsARB 
+glMemoryBarrier 
+glMemoryBarrierByRegion 
+glMemoryBarrierEXT 
+glMinSampleShading 
+glMinSampleShadingARB 
+glMinSampleShadingOES 
+glMinmax 
+glMinmaxEXT 
+glMultMatrixd 
+glMultMatrixf 
+glMultMatrixx 
+glMultMatrixxOES 
+glMultTransformEXT 
+glMultTransposeMatrixd 
+glMultTransposeMatrixdARB 
+glMultTransposeMatrixf 
+glMultTransposeMatrixfARB 
+glMultTransposeMatrixxOES 
+glMultiDrawArrays 
+glMultiDrawArraysEXT 
+glMultiDrawArraysIndirect 
+glMultiDrawArraysIndirectAMD 
+glMultiDrawArraysIndirectBindlessCountNV 
+glMultiDrawArraysIndirectBindlessNV 
+glMultiDrawArraysIndirectCountARB 
+glMultiDrawArraysIndirectEXT 
+glMultiDrawElementArrayAPPLE 
+glMultiDrawElements 
+glMultiDrawElementsBaseVertex 
+glMultiDrawElementsBaseVertexEXT 
+glMultiDrawElementsBaseVertexOES 
+glMultiDrawElementsEXT 
+glMultiDrawElementsIndirect 
+glMultiDrawElementsIndirectAMD 
+glMultiDrawElementsIndirectBindlessCountNV 
+glMultiDrawElementsIndirectBindlessNV 
+glMultiDrawElementsIndirectCountARB 
+glMultiDrawElementsIndirectEXT 
+glMultiDrawRangeElementArrayAPPLE 
+glMultiModeDrawArraysIBM 
+glMultiModeDrawElementsIBM 
+glMultiTexBufferEXT 
+glMultiTexCoord1bOES 
+glMultiTexCoord1bvOES 
+glMultiTexCoord1d 
+glMultiTexCoord1dARB 
+glMultiTexCoord1dv 
+glMultiTexCoord1dvARB 
+glMultiTexCoord1f 
+glMultiTexCoord1fARB 
+glMultiTexCoord1fv 
+glMultiTexCoord1fvARB 
+glMultiTexCoord1hNV 
+glMultiTexCoord1hvNV 
+glMultiTexCoord1i 
+glMultiTexCoord1iARB 
+glMultiTexCoord1iv 
+glMultiTexCoord1ivARB 
+glMultiTexCoord1s 
+glMultiTexCoord1sARB 
+glMultiTexCoord1sv 
+glMultiTexCoord1svARB 
+glMultiTexCoord1x 
+glMultiTexCoord1xOES 
+glMultiTexCoord1xv 
+glMultiTexCoord1xvOES 
+glMultiTexCoord2bOES 
+glMultiTexCoord2bvOES 
+glMultiTexCoord2d 
+glMultiTexCoord2dARB 
+glMultiTexCoord2dv 
+glMultiTexCoord2dvARB 
+glMultiTexCoord2f 
+glMultiTexCoord2fARB 
+glMultiTexCoord2fv 
+glMultiTexCoord2fvARB 
+glMultiTexCoord2hNV 
+glMultiTexCoord2hvNV 
+glMultiTexCoord2i 
+glMultiTexCoord2iARB 
+glMultiTexCoord2iv 
+glMultiTexCoord2ivARB 
+glMultiTexCoord2s 
+glMultiTexCoord2sARB 
+glMultiTexCoord2sv 
+glMultiTexCoord2svARB 
+glMultiTexCoord2x 
+glMultiTexCoord2xOES 
+glMultiTexCoord2xv 
+glMultiTexCoord2xvOES 
+glMultiTexCoord3bOES 
+glMultiTexCoord3bvOES 
+glMultiTexCoord3d 
+glMultiTexCoord3dARB 
+glMultiTexCoord3dv 
+glMultiTexCoord3dvARB 
+glMultiTexCoord3f 
+glMultiTexCoord3fARB 
+glMultiTexCoord3fv 
+glMultiTexCoord3fvARB 
+glMultiTexCoord3hNV 
+glMultiTexCoord3hvNV 
+glMultiTexCoord3i 
+glMultiTexCoord3iARB 
+glMultiTexCoord3iv 
+glMultiTexCoord3ivARB 
+glMultiTexCoord3s 
+glMultiTexCoord3sARB 
+glMultiTexCoord3sv 
+glMultiTexCoord3svARB 
+glMultiTexCoord3x 
+glMultiTexCoord3xOES 
+glMultiTexCoord3xv 
+glMultiTexCoord3xvOES 
+glMultiTexCoord4bOES 
+glMultiTexCoord4bvOES 
+glMultiTexCoord4d 
+glMultiTexCoord4dARB 
+glMultiTexCoord4dv 
+glMultiTexCoord4dvARB 
+glMultiTexCoord4f 
+glMultiTexCoord4fARB 
+glMultiTexCoord4fv 
+glMultiTexCoord4fvARB 
+glMultiTexCoord4hNV 
+glMultiTexCoord4hvNV 
+glMultiTexCoord4i 
+glMultiTexCoord4iARB 
+glMultiTexCoord4iv 
+glMultiTexCoord4ivARB 
+glMultiTexCoord4s 
+glMultiTexCoord4sARB 
+glMultiTexCoord4sv 
+glMultiTexCoord4svARB 
+glMultiTexCoord4x 
+glMultiTexCoord4xOES 
+glMultiTexCoord4xv 
+glMultiTexCoord4xvOES 
+glMultiTexCoordP1ui 
+glMultiTexCoordP1uiv 
+glMultiTexCoordP2ui 
+glMultiTexCoordP2uiv 
+glMultiTexCoordP3ui 
+glMultiTexCoordP3uiv 
+glMultiTexCoordP4ui 
+glMultiTexCoordP4uiv 
+glMultiTexCoordPointerEXT 
+glMultiTexEnvfEXT 
+glMultiTexEnvfvEXT 
+glMultiTexEnviEXT 
+glMultiTexEnvivEXT 
+glMultiTexGendEXT 
+glMultiTexGendvEXT 
+glMultiTexGenfEXT 
+glMultiTexGenfvEXT 
+glMultiTexGeniEXT 
+glMultiTexGenivEXT 
+glMultiTexImage1DEXT 
+glMultiTexImage2DEXT 
+glMultiTexImage3DEXT 
+glMultiTexParameterIivEXT 
+glMultiTexParameterIuivEXT 
+glMultiTexParameterfEXT 
+glMultiTexParameterfvEXT 
+glMultiTexParameteriEXT 
+glMultiTexParameterivEXT 
+glMultiTexRenderbufferEXT 
+glMultiTexSubImage1DEXT 
+glMultiTexSubImage2DEXT 
+glMultiTexSubImage3DEXT 
+glNVENCInterOpFunctionNVX 
+glNamedBufferData 
+glNamedBufferDataEXT 
+glNamedBufferPageCommitmentARB 
+glNamedBufferPageCommitmentEXT 
+glNamedBufferStorage 
+glNamedBufferStorageEXT 
+glNamedBufferSubData 
+glNamedBufferSubDataEXT 
+glNamedCopyBufferSubDataEXT 
+glNamedFramebufferDrawBuffer 
+glNamedFramebufferDrawBuffers 
+glNamedFramebufferParameteri 
+glNamedFramebufferParameteriEXT 
+glNamedFramebufferReadBuffer 
+glNamedFramebufferRenderbuffer 
+glNamedFramebufferRenderbufferEXT 
+glNamedFramebufferSampleLocationsfvARB 
+glNamedFramebufferSampleLocationsfvNV 
+glNamedFramebufferTexture 
+glNamedFramebufferTexture1DEXT 
+glNamedFramebufferTexture2DEXT 
+glNamedFramebufferTexture3DEXT 
+glNamedFramebufferTextureEXT 
+glNamedFramebufferTextureFaceEXT 
+glNamedFramebufferTextureLayer 
+glNamedFramebufferTextureLayerEXT 
+glNamedProgramLocalParameter4dEXT 
+glNamedProgramLocalParameter4dvEXT 
+glNamedProgramLocalParameter4fEXT 
+glNamedProgramLocalParameter4fvEXT 
+glNamedProgramLocalParameterI4iEXT 
+glNamedProgramLocalParameterI4ivEXT 
+glNamedProgramLocalParameterI4uiEXT 
+glNamedProgramLocalParameterI4uivEXT 
+glNamedProgramLocalParameters4fvEXT 
+glNamedProgramLocalParametersI4ivEXT 
+glNamedProgramLocalParametersI4uivEXT 
+glNamedProgramStringEXT 
+glNamedRenderbufferStorage 
+glNamedRenderbufferStorageEXT 
+glNamedRenderbufferStorageMultisample 
+glNamedRenderbufferStorageMultisampleCoverageEXT 
+glNamedRenderbufferStorageMultisampleEXT 
+glNamedStringARB 
+glNewList 
+glNewObjectBufferATI 
+glNormal3b 
+glNormal3bv 
+glNormal3d 
+glNormal3dv 
+glNormal3f 
+glNormal3fVertex3fSUN 
+glNormal3fVertex3fvSUN 
+glNormal3fv 
+glNormal3hNV 
+glNormal3hvNV 
+glNormal3i 
+glNormal3iv 
+glNormal3s 
+glNormal3sv 
+glNormal3x 
+glNormal3xOES 
+glNormal3xvOES 
+glNormalFormatNV 
+glNormalP3ui 
+glNormalP3uiv 
+glNormalPointer 
+glNormalPointerEXT 
+glNormalPointerListIBM 
+glNormalPointervINTEL 
+glNormalStream3bATI 
+glNormalStream3bvATI 
+glNormalStream3dATI 
+glNormalStream3dvATI 
+glNormalStream3fATI 
+glNormalStream3fvATI 
+glNormalStream3iATI 
+glNormalStream3ivATI 
+glNormalStream3sATI 
+glNormalStream3svATI 
+glObjectLabel 
+glObjectLabelKHR 
+glObjectLabelOES 
+glObjectPtrLabel 
+glObjectPtrLabelKHR 
+glObjectPtrLabelOES 
+glObjectPurgeableAPPLE 
+glObjectUnpurgeableAPPLE 
+glOrtho 
+glOrthof 
+glOrthofOES 
+glOrthox 
+glOrthoxOES 
+glPNTrianglesfATI 
+glPNTrianglesiATI 
+glPassTexCoordATI 
+glPassThrough 
+glPassThroughxOES 
+glPatchParameterfv 
+glPatchParameterfvEXT 
+glPatchParameterfvNV 
+glPatchParameteri 
+glPatchParameteriEXT 
+glPatchParameteriNV 
+glPatchParameteriOES 
+glPathColorGenNV 
+glPathCommandsNV 
+glPathCoordsNV 
+glPathCoverDepthFuncNV 
+glPathDashArrayNV 
+glPathFogGenNV 
+glPathGlyphIndexArrayNV 
+glPathGlyphIndexRangeNV 
+glPathGlyphRangeNV 
+glPathGlyphsNV 
+glPathMemoryGlyphIndexArrayNV 
+glPathParameterfNV 
+glPathParameterfvNV 
+glPathParameteriNV 
+glPathParameterivNV 
+glPathStencilDepthOffsetNV 
+glPathStencilFuncNV 
+glPathStringNV 
+glPathSubCommandsNV 
+glPathSubCoordsNV 
+glPathTexGenNV 
+glPauseTransformFeedback 
+glPauseTransformFeedbackEXT 
+glPauseTransformFeedbackNV 
+glPixelDataRangeNV 
+glPixelMapfv 
+glPixelMapuiv 
+glPixelMapusv 
+glPixelMapx 
+glPixelStoref 
+glPixelStorei 
+glPixelStorex 
+glPixelTexGenParameterfSGIS 
+glPixelTexGenParameterfvSGIS 
+glPixelTexGenParameteriSGIS 
+glPixelTexGenParameterivSGIS 
+glPixelTexGenSGIX 
+glPixelTransferf 
+glPixelTransferi 
+glPixelTransferxOES 
+glPixelTransformParameterfEXT 
+glPixelTransformParameterfvEXT 
+glPixelTransformParameteriEXT 
+glPixelTransformParameterivEXT 
+glPixelZoom 
+glPixelZoomxOES 
+glPointAlongPathNV 
+glPointParameterf 
+glPointParameterfARB 
+glPointParameterfEXT 
+glPointParameterfSGIS 
+glPointParameterfv 
+glPointParameterfvARB 
+glPointParameterfvEXT 
+glPointParameterfvSGIS 
+glPointParameteri 
+glPointParameteriNV 
+glPointParameteriv 
+glPointParameterivNV 
+glPointParameterx 
+glPointParameterxOES 
+glPointParameterxv 
+glPointParameterxvOES 
+glPointSize 
+glPointSizePointerOES 
+glPointSizex 
+glPointSizexOES 
+glPollAsyncSGIX 
+glPollInstrumentsSGIX 
+glPolygonMode 
+glPolygonModeNV 
+glPolygonOffset 
+glPolygonOffsetClampEXT 
+glPolygonOffsetEXT 
+glPolygonOffsetx 
+glPolygonOffsetxOES 
+glPolygonStipple 
+glPopAttrib 
+glPopClientAttrib 
+glPopDebugGroup 
+glPopDebugGroupKHR 
+glPopDebugGroupOES 
+glPopGroupMarkerEXT 
+glPopMatrix 
+glPopName 
+glPresentFrameDualFillNV 
+glPresentFrameKeyedNV 
+glPrimitiveBoundingBox 
+glPrimitiveBoundingBoxARB 
+glPrimitiveBoundingBoxEXT 
+glPrimitiveBoundingBoxOES 
+glPrimitiveRestart 
+glPrimitiveRestartIndex 
+glPrimitiveRestartIndexNV 
+glPrimitiveRestartNV 
+glPrioritizeTextures 
+glPrioritizeTexturesEXT 
+glPrioritizeTexturesxOES 
+glProgramBinary 
+glProgramBinaryOES 
+glProgramBufferParametersIivNV 
+glProgramBufferParametersIuivNV 
+glProgramBufferParametersfvNV 
+glProgramEnvParameter4dARB 
+glProgramEnvParameter4dvARB 
+glProgramEnvParameter4fARB 
+glProgramEnvParameter4fvARB 
+glProgramEnvParameterI4iNV 
+glProgramEnvParameterI4ivNV 
+glProgramEnvParameterI4uiNV 
+glProgramEnvParameterI4uivNV 
+glProgramEnvParameters4fvEXT 
+glProgramEnvParametersI4ivNV 
+glProgramEnvParametersI4uivNV 
+glProgramLocalParameter4dARB 
+glProgramLocalParameter4dvARB 
+glProgramLocalParameter4fARB 
+glProgramLocalParameter4fvARB 
+glProgramLocalParameterI4iNV 
+glProgramLocalParameterI4ivNV 
+glProgramLocalParameterI4uiNV 
+glProgramLocalParameterI4uivNV 
+glProgramLocalParameters4fvEXT 
+glProgramLocalParametersI4ivNV 
+glProgramLocalParametersI4uivNV 
+glProgramNamedParameter4dNV 
+glProgramNamedParameter4dvNV 
+glProgramNamedParameter4fNV 
+glProgramNamedParameter4fvNV 
+glProgramParameter4dNV 
+glProgramParameter4dvNV 
+glProgramParameter4fNV 
+glProgramParameter4fvNV 
+glProgramParameteri 
+glProgramParameteriARB 
+glProgramParameteriEXT 
+glProgramParameters4dvNV 
+glProgramParameters4fvNV 
+glProgramPathFragmentInputGenNV 
+glProgramStringARB 
+glProgramSubroutineParametersuivNV 
+glProgramUniform1d 
+glProgramUniform1dEXT 
+glProgramUniform1dv 
+glProgramUniform1dvEXT 
+glProgramUniform1f 
+glProgramUniform1fEXT 
+glProgramUniform1fv 
+glProgramUniform1fvEXT 
+glProgramUniform1i 
+glProgramUniform1i64ARB 
+glProgramUniform1i64NV 
+glProgramUniform1i64vARB 
+glProgramUniform1i64vNV 
+glProgramUniform1iEXT 
+glProgramUniform1iv 
+glProgramUniform1ivEXT 
+glProgramUniform1ui 
+glProgramUniform1ui64ARB 
+glProgramUniform1ui64NV 
+glProgramUniform1ui64vARB 
+glProgramUniform1ui64vNV 
+glProgramUniform1uiEXT 
+glProgramUniform1uiv 
+glProgramUniform1uivEXT 
+glProgramUniform2d 
+glProgramUniform2dEXT 
+glProgramUniform2dv 
+glProgramUniform2dvEXT 
+glProgramUniform2f 
+glProgramUniform2fEXT 
+glProgramUniform2fv 
+glProgramUniform2fvEXT 
+glProgramUniform2i 
+glProgramUniform2i64ARB 
+glProgramUniform2i64NV 
+glProgramUniform2i64vARB 
+glProgramUniform2i64vNV 
+glProgramUniform2iEXT 
+glProgramUniform2iv 
+glProgramUniform2ivEXT 
+glProgramUniform2ui 
+glProgramUniform2ui64ARB 
+glProgramUniform2ui64NV 
+glProgramUniform2ui64vARB 
+glProgramUniform2ui64vNV 
+glProgramUniform2uiEXT 
+glProgramUniform2uiv 
+glProgramUniform2uivEXT 
+glProgramUniform3d 
+glProgramUniform3dEXT 
+glProgramUniform3dv 
+glProgramUniform3dvEXT 
+glProgramUniform3f 
+glProgramUniform3fEXT 
+glProgramUniform3fv 
+glProgramUniform3fvEXT 
+glProgramUniform3i 
+glProgramUniform3i64ARB 
+glProgramUniform3i64NV 
+glProgramUniform3i64vARB 
+glProgramUniform3i64vNV 
+glProgramUniform3iEXT 
+glProgramUniform3iv 
+glProgramUniform3ivEXT 
+glProgramUniform3ui 
+glProgramUniform3ui64ARB 
+glProgramUniform3ui64NV 
+glProgramUniform3ui64vARB 
+glProgramUniform3ui64vNV 
+glProgramUniform3uiEXT 
+glProgramUniform3uiv 
+glProgramUniform3uivEXT 
+glProgramUniform4d 
+glProgramUniform4dEXT 
+glProgramUniform4dv 
+glProgramUniform4dvEXT 
+glProgramUniform4f 
+glProgramUniform4fEXT 
+glProgramUniform4fv 
+glProgramUniform4fvEXT 
+glProgramUniform4i 
+glProgramUniform4i64ARB 
+glProgramUniform4i64NV 
+glProgramUniform4i64vARB 
+glProgramUniform4i64vNV 
+glProgramUniform4iEXT 
+glProgramUniform4iv 
+glProgramUniform4ivEXT 
+glProgramUniform4ui 
+glProgramUniform4ui64ARB 
+glProgramUniform4ui64NV 
+glProgramUniform4ui64vARB 
+glProgramUniform4ui64vNV 
+glProgramUniform4uiEXT 
+glProgramUniform4uiv 
+glProgramUniform4uivEXT 
+glProgramUniformHandleui64ARB 
+glProgramUniformHandleui64NV 
+glProgramUniformHandleui64vARB 
+glProgramUniformHandleui64vNV 
+glProgramUniformMatrix2dv 
+glProgramUniformMatrix2dvEXT 
+glProgramUniformMatrix2fv 
+glProgramUniformMatrix2fvEXT 
+glProgramUniformMatrix2x3dv 
+glProgramUniformMatrix2x3dvEXT 
+glProgramUniformMatrix2x3fv 
+glProgramUniformMatrix2x3fvEXT 
+glProgramUniformMatrix2x4dv 
+glProgramUniformMatrix2x4dvEXT 
+glProgramUniformMatrix2x4fv 
+glProgramUniformMatrix2x4fvEXT 
+glProgramUniformMatrix3dv 
+glProgramUniformMatrix3dvEXT 
+glProgramUniformMatrix3fv 
+glProgramUniformMatrix3fvEXT 
+glProgramUniformMatrix3x2dv 
+glProgramUniformMatrix3x2dvEXT 
+glProgramUniformMatrix3x2fv 
+glProgramUniformMatrix3x2fvEXT 
+glProgramUniformMatrix3x4dv 
+glProgramUniformMatrix3x4dvEXT 
+glProgramUniformMatrix3x4fv 
+glProgramUniformMatrix3x4fvEXT 
+glProgramUniformMatrix4dv 
+glProgramUniformMatrix4dvEXT 
+glProgramUniformMatrix4fv 
+glProgramUniformMatrix4fvEXT 
+glProgramUniformMatrix4x2dv 
+glProgramUniformMatrix4x2dvEXT 
+glProgramUniformMatrix4x2fv 
+glProgramUniformMatrix4x2fvEXT 
+glProgramUniformMatrix4x3dv 
+glProgramUniformMatrix4x3dvEXT 
+glProgramUniformMatrix4x3fv 
+glProgramUniformMatrix4x3fvEXT 
+glProgramUniformui64NV 
+glProgramUniformui64vNV 
+glProgramVertexLimitNV 
+glProvokingVertex 
+glProvokingVertexEXT 
+glPushAttrib 
+glPushClientAttrib 
+glPushClientAttribDefaultEXT 
+glPushDebugGroup 
+glPushDebugGroupKHR 
+glPushDebugGroupOES 
+glPushGroupMarkerEXT 
+glPushMatrix 
+glPushName 
+glQueryCounter 
+glQueryCounterEXT 
+glQueryCounterNV 
+glQueryMatrixxOES 
+glQueryObjectParameteruiAMD 
+glRasterPos2d 
+glRasterPos2dv 
+glRasterPos2f 
+glRasterPos2fv 
+glRasterPos2i 
+glRasterPos2iv 
+glRasterPos2s 
+glRasterPos2sv 
+glRasterPos2xOES 
+glRasterPos2xvOES 
+glRasterPos3d 
+glRasterPos3dv 
+glRasterPos3f 
+glRasterPos3fv 
+glRasterPos3i 
+glRasterPos3iv 
+glRasterPos3s 
+glRasterPos3sv 
+glRasterPos3xOES 
+glRasterPos3xvOES 
+glRasterPos4d 
+glRasterPos4dv 
+glRasterPos4f 
+glRasterPos4fv 
+glRasterPos4i 
+glRasterPos4iv 
+glRasterPos4s 
+glRasterPos4sv 
+glRasterPos4xOES 
+glRasterPos4xvOES 
+glRasterSamplesEXT 
+glReadBuffer 
+glReadBufferIndexedEXT 
+glReadBufferNV 
+glReadInstrumentsSGIX 
+glReadPixels 
+glReadnPixels 
+glReadnPixelsARB 
+glReadnPixelsEXT 
+glReadnPixelsKHR 
+glRectd 
+glRectdv 
+glRectf 
+glRectfv 
+glRecti 
+glRectiv 
+glRects 
+glRectsv 
+glRectxOES 
+glRectxvOES 
+glReferencePlaneSGIX 
+glReleaseShaderCompiler 
+glRenderMode 
+glRenderbufferStorage 
+glRenderbufferStorageEXT 
+glRenderbufferStorageMultisample 
+glRenderbufferStorageMultisampleANGLE 
+glRenderbufferStorageMultisampleAPPLE 
+glRenderbufferStorageMultisampleCoverageNV 
+glRenderbufferStorageMultisampleEXT 
+glRenderbufferStorageMultisampleIMG 
+glRenderbufferStorageMultisampleNV 
+glRenderbufferStorageOES 
+glReplacementCodePointerSUN 
+glReplacementCodeubSUN 
+glReplacementCodeubvSUN 
+glReplacementCodeuiColor3fVertex3fSUN 
+glReplacementCodeuiColor3fVertex3fvSUN 
+glReplacementCodeuiColor4fNormal3fVertex3fSUN 
+glReplacementCodeuiColor4fNormal3fVertex3fvSUN 
+glReplacementCodeuiColor4ubVertex3fSUN 
+glReplacementCodeuiColor4ubVertex3fvSUN 
+glReplacementCodeuiNormal3fVertex3fSUN 
+glReplacementCodeuiNormal3fVertex3fvSUN 
+glReplacementCodeuiSUN 
+glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fSUN 
+glReplacementCodeuiTexCoord2fColor4fNormal3fVertex3fvSUN 
+glReplacementCodeuiTexCoord2fNormal3fVertex3fSUN 
+glReplacementCodeuiTexCoord2fNormal3fVertex3fvSUN 
+glReplacementCodeuiTexCoord2fVertex3fSUN 
+glReplacementCodeuiTexCoord2fVertex3fvSUN 
+glReplacementCodeuiVertex3fSUN 
+glReplacementCodeuiVertex3fvSUN 
+glReplacementCodeuivSUN 
+glReplacementCodeusSUN 
+glReplacementCodeusvSUN 
+glRequestResidentProgramsNV 
+glResetHistogram 
+glResetHistogramEXT 
+glResetMinmax 
+glResetMinmaxEXT 
+glResizeBuffersMESA 
+glResolveDepthValuesNV 
+glResolveMultisampleFramebufferAPPLE 
+glResumeTransformFeedback 
+glResumeTransformFeedbackEXT 
+glResumeTransformFeedbackNV 
+glRotated 
+glRotatef 
+glRotatex 
+glRotatexOES 
+glSampleCoverage 
+glSampleCoverageARB 
+glSampleCoveragex 
+glSampleCoveragexOES 
+glSampleMapATI 
+glSampleMaskEXT 
+glSampleMaskIndexedNV 
+glSampleMaskSGIS 
+glSampleMaski 
+glSamplePatternEXT 
+glSamplePatternSGIS 
+glSamplerParameterIiv 
+glSamplerParameterIivEXT 
+glSamplerParameterIivOES 
+glSamplerParameterIuiv 
+glSamplerParameterIuivEXT 
+glSamplerParameterIuivOES 
+glSamplerParameterf 
+glSamplerParameterfv 
+glSamplerParameteri 
+glSamplerParameteriv 
+glScaled 
+glScalef 
+glScalex 
+glScalexOES 
+glScissor 
+glScissorArrayv 
+glScissorArrayvNV 
+glScissorIndexed 
+glScissorIndexedNV 
+glScissorIndexedv 
+glScissorIndexedvNV 
+glSecondaryColor3b 
+glSecondaryColor3bEXT 
+glSecondaryColor3bv 
+glSecondaryColor3bvEXT 
+glSecondaryColor3d 
+glSecondaryColor3dEXT 
+glSecondaryColor3dv 
+glSecondaryColor3dvEXT 
+glSecondaryColor3f 
+glSecondaryColor3fEXT 
+glSecondaryColor3fv 
+glSecondaryColor3fvEXT 
+glSecondaryColor3hNV 
+glSecondaryColor3hvNV 
+glSecondaryColor3i 
+glSecondaryColor3iEXT 
+glSecondaryColor3iv 
+glSecondaryColor3ivEXT 
+glSecondaryColor3s 
+glSecondaryColor3sEXT 
+glSecondaryColor3sv 
+glSecondaryColor3svEXT 
+glSecondaryColor3ub 
+glSecondaryColor3ubEXT 
+glSecondaryColor3ubv 
+glSecondaryColor3ubvEXT 
+glSecondaryColor3ui 
+glSecondaryColor3uiEXT 
+glSecondaryColor3uiv 
+glSecondaryColor3uivEXT 
+glSecondaryColor3us 
+glSecondaryColor3usEXT 
+glSecondaryColor3usv 
+glSecondaryColor3usvEXT 
+glSecondaryColorFormatNV 
+glSecondaryColorP3ui 
+glSecondaryColorP3uiv 
+glSecondaryColorPointer 
+glSecondaryColorPointerEXT 
+glSecondaryColorPointerListIBM 
+glSelectBuffer 
+glSelectPerfMonitorCountersAMD 
+glSeparableFilter2D 
+glSeparableFilter2DEXT 
+glSetFenceAPPLE 
+glSetFenceNV 
+glSetFragmentShaderConstantATI 
+glSetInvariantEXT 
+glSetLocalConstantEXT 
+glSetMultisamplefvAMD 
+glShadeModel 
+glShaderBinary 
+glShaderOp1EXT 
+glShaderOp2EXT 
+glShaderOp3EXT 
+glShaderSource 
+glShaderSourceARB 
+glShaderStorageBlockBinding 
+glSharpenTexFuncSGIS 
+glSpriteParameterfSGIX 
+glSpriteParameterfvSGIX 
+glSpriteParameteriSGIX 
+glSpriteParameterivSGIX 
+glStartInstrumentsSGIX 
+glStartTilingQCOM 
+glStateCaptureNV 
+glStencilClearTagEXT 
+glStencilFillPathInstancedNV 
+glStencilFillPathNV 
+glStencilFunc 
+glStencilFuncSeparate 
+glStencilFuncSeparateATI 
+glStencilMask 
+glStencilMaskSeparate 
+glStencilOp 
+glStencilOpSeparate 
+glStencilOpSeparateATI 
+glStencilOpValueAMD 
+glStencilStrokePathInstancedNV 
+glStencilStrokePathNV 
+glStencilThenCoverFillPathInstancedNV 
+glStencilThenCoverFillPathNV 
+glStencilThenCoverStrokePathInstancedNV 
+glStencilThenCoverStrokePathNV 
+glStopInstrumentsSGIX 
+glStringMarkerGREMEDY 
+glSubpixelPrecisionBiasNV 
+glSwizzleEXT 
+glSyncTextureINTEL 
+glTagSampleBufferSGIX 
+glTangent3bEXT 
+glTangent3bvEXT 
+glTangent3dEXT 
+glTangent3dvEXT 
+glTangent3fEXT 
+glTangent3fvEXT 
+glTangent3iEXT 
+glTangent3ivEXT 
+glTangent3sEXT 
+glTangent3svEXT 
+glTangentPointerEXT 
+glTbufferMask3DFX 
+glTessellationFactorAMD 
+glTessellationModeAMD 
+glTestFenceAPPLE 
+glTestFenceNV 
+glTestObjectAPPLE 
+glTexBuffer 
+glTexBufferARB 
+glTexBufferEXT 
+glTexBufferOES 
+glTexBufferRange 
+glTexBufferRangeEXT 
+glTexBufferRangeOES 
+glTexBumpParameterfvATI 
+glTexBumpParameterivATI 
+glTexCoord1bOES 
+glTexCoord1bvOES 
+glTexCoord1d 
+glTexCoord1dv 
+glTexCoord1f 
+glTexCoord1fv 
+glTexCoord1hNV 
+glTexCoord1hvNV 
+glTexCoord1i 
+glTexCoord1iv 
+glTexCoord1s 
+glTexCoord1sv 
+glTexCoord1xOES 
+glTexCoord1xvOES 
+glTexCoord2bOES 
+glTexCoord2bvOES 
+glTexCoord2d 
+glTexCoord2dv 
+glTexCoord2f 
+glTexCoord2fColor3fVertex3fSUN 
+glTexCoord2fColor3fVertex3fvSUN 
+glTexCoord2fColor4fNormal3fVertex3fSUN 
+glTexCoord2fColor4fNormal3fVertex3fvSUN 
+glTexCoord2fColor4ubVertex3fSUN 
+glTexCoord2fColor4ubVertex3fvSUN 
+glTexCoord2fNormal3fVertex3fSUN 
+glTexCoord2fNormal3fVertex3fvSUN 
+glTexCoord2fVertex3fSUN 
+glTexCoord2fVertex3fvSUN 
+glTexCoord2fv 
+glTexCoord2hNV 
+glTexCoord2hvNV 
+glTexCoord2i 
+glTexCoord2iv 
+glTexCoord2s 
+glTexCoord2sv 
+glTexCoord2xOES 
+glTexCoord2xvOES 
+glTexCoord3bOES 
+glTexCoord3bvOES 
+glTexCoord3d 
+glTexCoord3dv 
+glTexCoord3f 
+glTexCoord3fv 
+glTexCoord3hNV 
+glTexCoord3hvNV 
+glTexCoord3i 
+glTexCoord3iv 
+glTexCoord3s 
+glTexCoord3sv 
+glTexCoord3xOES 
+glTexCoord3xvOES 
+glTexCoord4bOES 
+glTexCoord4bvOES 
+glTexCoord4d 
+glTexCoord4dv 
+glTexCoord4f 
+glTexCoord4fColor4fNormal3fVertex4fSUN 
+glTexCoord4fColor4fNormal3fVertex4fvSUN 
+glTexCoord4fVertex4fSUN 
+glTexCoord4fVertex4fvSUN 
+glTexCoord4fv 
+glTexCoord4hNV 
+glTexCoord4hvNV 
+glTexCoord4i 
+glTexCoord4iv 
+glTexCoord4s 
+glTexCoord4sv 
+glTexCoord4xOES 
+glTexCoord4xvOES 
+glTexCoordFormatNV 
+glTexCoordP1ui 
+glTexCoordP1uiv 
+glTexCoordP2ui 
+glTexCoordP2uiv 
+glTexCoordP3ui 
+glTexCoordP3uiv 
+glTexCoordP4ui 
+glTexCoordP4uiv 
+glTexCoordPointer 
+glTexCoordPointerEXT 
+glTexCoordPointerListIBM 
+glTexCoordPointervINTEL 
+glTexEnvf 
+glTexEnvfv 
+glTexEnvi 
+glTexEnviv 
+glTexEnvx 
+glTexEnvxOES 
+glTexEnvxv 
+glTexEnvxvOES 
+glTexFilterFuncSGIS 
+glTexGend 
+glTexGendv 
+glTexGenf 
+glTexGenfOES 
+glTexGenfv 
+glTexGenfvOES 
+glTexGeni 
+glTexGeniOES 
+glTexGeniv 
+glTexGenivOES 
+glTexGenxOES 
+glTexGenxvOES 
+glTexImage1D 
+glTexImage2D 
+glTexImage2DMultisample 
+glTexImage2DMultisampleCoverageNV 
+glTexImage3D 
+glTexImage3DEXT 
+glTexImage3DMultisample 
+glTexImage3DMultisampleCoverageNV 
+glTexImage3DNV 
+glTexImage3DOES 
+glTexImage4DSGIS 
+glTexPageCommitmentARB 
+glTexPageCommitmentEXT 
+glTexParameterIiv 
+glTexParameterIivEXT 
+glTexParameterIivOES 
+glTexParameterIuiv 
+glTexParameterIuivEXT 
+glTexParameterIuivOES 
+glTexParameterf 
+glTexParameterfv 
+glTexParameteri 
+glTexParameteriv 
+glTexParameterx 
+glTexParameterxOES 
+glTexParameterxv 
+glTexParameterxvOES 
+glTexRenderbufferNV 
+glTexStorage1D 
+glTexStorage1DEXT 
+glTexStorage2D 
+glTexStorage2DEXT 
+glTexStorage2DMultisample 
+glTexStorage3D 
+glTexStorage3DEXT 
+glTexStorage3DMultisample 
+glTexStorage3DMultisampleOES 
+glTexStorageSparseAMD 
+glTexSubImage1D 
+glTexSubImage1DEXT 
+glTexSubImage2D 
+glTexSubImage2DEXT 
+glTexSubImage3D 
+glTexSubImage3DEXT 
+glTexSubImage3DNV 
+glTexSubImage3DOES 
+glTexSubImage4DSGIS 
+glTextureBarrier 
+glTextureBarrierNV 
+glTextureBuffer 
+glTextureBufferEXT 
+glTextureBufferRange 
+glTextureBufferRangeEXT 
+glTextureColorMaskSGIS 
+glTextureImage1DEXT 
+glTextureImage2DEXT 
+glTextureImage2DMultisampleCoverageNV 
+glTextureImage2DMultisampleNV 
+glTextureImage3DEXT 
+glTextureImage3DMultisampleCoverageNV 
+glTextureImage3DMultisampleNV 
+glTextureLightEXT 
+glTextureMaterialEXT 
+glTextureNormalEXT 
+glTexturePageCommitmentEXT 
+glTextureParameterIiv 
+glTextureParameterIivEXT 
+glTextureParameterIuiv 
+glTextureParameterIuivEXT 
+glTextureParameterf 
+glTextureParameterfEXT 
+glTextureParameterfv 
+glTextureParameterfvEXT 
+glTextureParameteri 
+glTextureParameteriEXT 
+glTextureParameteriv 
+glTextureParameterivEXT 
+glTextureRangeAPPLE 
+glTextureRenderbufferEXT 
+glTextureStorage1D 
+glTextureStorage1DEXT 
+glTextureStorage2D 
+glTextureStorage2DEXT 
+glTextureStorage2DMultisample 
+glTextureStorage2DMultisampleEXT 
+glTextureStorage3D 
+glTextureStorage3DEXT 
+glTextureStorage3DMultisample 
+glTextureStorage3DMultisampleEXT 
+glTextureStorageSparseAMD 
+glTextureSubImage1D 
+glTextureSubImage1DEXT 
+glTextureSubImage2D 
+glTextureSubImage2DEXT 
+glTextureSubImage3D 
+glTextureSubImage3DEXT 
+glTextureView 
+glTextureViewEXT 
+glTextureViewOES 
+glTrackMatrixNV 
+glTransformFeedbackAttribsNV 
+glTransformFeedbackBufferBase 
+glTransformFeedbackBufferRange 
+glTransformFeedbackStreamAttribsNV 
+glTransformFeedbackVaryings 
+glTransformFeedbackVaryingsEXT 
+glTransformFeedbackVaryingsNV 
+glTransformPathNV 
+glTranslated 
+glTranslatef 
+glTranslatex 
+glTranslatexOES 
+glUniform1d 
+glUniform1dv 
+glUniform1f 
+glUniform1fARB 
+glUniform1fv 
+glUniform1fvARB 
+glUniform1i 
+glUniform1i64ARB 
+glUniform1i64NV 
+glUniform1i64vARB 
+glUniform1i64vNV 
+glUniform1iARB 
+glUniform1iv 
+glUniform1ivARB 
+glUniform1ui 
+glUniform1ui64ARB 
+glUniform1ui64NV 
+glUniform1ui64vARB 
+glUniform1ui64vNV 
+glUniform1uiEXT 
+glUniform1uiv 
+glUniform1uivEXT 
+glUniform2d 
+glUniform2dv 
+glUniform2f 
+glUniform2fARB 
+glUniform2fv 
+glUniform2fvARB 
+glUniform2i 
+glUniform2i64ARB 
+glUniform2i64NV 
+glUniform2i64vARB 
+glUniform2i64vNV 
+glUniform2iARB 
+glUniform2iv 
+glUniform2ivARB 
+glUniform2ui 
+glUniform2ui64ARB 
+glUniform2ui64NV 
+glUniform2ui64vARB 
+glUniform2ui64vNV 
+glUniform2uiEXT 
+glUniform2uiv 
+glUniform2uivEXT 
+glUniform3d 
+glUniform3dv 
+glUniform3f 
+glUniform3fARB 
+glUniform3fv 
+glUniform3fvARB 
+glUniform3i 
+glUniform3i64ARB 
+glUniform3i64NV 
+glUniform3i64vARB 
+glUniform3i64vNV 
+glUniform3iARB 
+glUniform3iv 
+glUniform3ivARB 
+glUniform3ui 
+glUniform3ui64ARB 
+glUniform3ui64NV 
+glUniform3ui64vARB 
+glUniform3ui64vNV 
+glUniform3uiEXT 
+glUniform3uiv 
+glUniform3uivEXT 
+glUniform4d 
+glUniform4dv 
+glUniform4f 
+glUniform4fARB 
+glUniform4fv 
+glUniform4fvARB 
+glUniform4i 
+glUniform4i64ARB 
+glUniform4i64NV 
+glUniform4i64vARB 
+glUniform4i64vNV 
+glUniform4iARB 
+glUniform4iv 
+glUniform4ivARB 
+glUniform4ui 
+glUniform4ui64ARB 
+glUniform4ui64NV 
+glUniform4ui64vARB 
+glUniform4ui64vNV 
+glUniform4uiEXT 
+glUniform4uiv 
+glUniform4uivEXT 
+glUniformBlockBinding 
+glUniformBufferEXT 
+glUniformHandleui64ARB 
+glUniformHandleui64NV 
+glUniformHandleui64vARB 
+glUniformHandleui64vNV 
+glUniformMatrix2dv 
+glUniformMatrix2fv 
+glUniformMatrix2fvARB 
+glUniformMatrix2x3dv 
+glUniformMatrix2x3fv 
+glUniformMatrix2x3fvNV 
+glUniformMatrix2x4dv 
+glUniformMatrix2x4fv 
+glUniformMatrix2x4fvNV 
+glUniformMatrix3dv 
+glUniformMatrix3fv 
+glUniformMatrix3fvARB 
+glUniformMatrix3x2dv 
+glUniformMatrix3x2fv 
+glUniformMatrix3x2fvNV 
+glUniformMatrix3x4dv 
+glUniformMatrix3x4fv 
+glUniformMatrix3x4fvNV 
+glUniformMatrix4dv 
+glUniformMatrix4fv 
+glUniformMatrix4fvARB 
+glUniformMatrix4x2dv 
+glUniformMatrix4x2fv 
+glUniformMatrix4x2fvNV 
+glUniformMatrix4x3dv 
+glUniformMatrix4x3fv 
+glUniformMatrix4x3fvNV 
+glUniformSubroutinesuiv 
+glUniformui64NV 
+glUniformui64vNV 
+glUnlockArraysEXT 
+glUnmapBuffer 
+glUnmapBufferARB 
+glUnmapBufferOES 
+glUnmapNamedBuffer 
+glUnmapNamedBufferEXT 
+glUnmapObjectBufferATI 
+glUnmapTexture2DINTEL 
+glUpdateObjectBufferATI 
+glUseProgram 
+glUseProgramObjectARB 
+glUseProgramStages 
+glUseProgramStagesEXT 
+glUseShaderProgramEXT 
+glVDPAUFiniNV 
+glVDPAUGetSurfaceivNV 
+glVDPAUInitNV 
+glVDPAUIsSurfaceNV 
+glVDPAUMapSurfacesNV 
+glVDPAURegisterOutputSurfaceNV 
+glVDPAURegisterVideoSurfaceNV 
+glVDPAUSurfaceAccessNV 
+glVDPAUUnmapSurfacesNV 
+glVDPAUUnregisterSurfaceNV 
+glValidBackBufferHintAutodesk 
+glValidateProgram 
+glValidateProgramARB 
+glValidateProgramPipeline 
+glValidateProgramPipelineEXT 
+glVariantArrayObjectATI 
+glVariantPointerEXT 
+glVariantbvEXT 
+glVariantdvEXT 
+glVariantfvEXT 
+glVariantivEXT 
+glVariantsvEXT 
+glVariantubvEXT 
+glVariantuivEXT 
+glVariantusvEXT 
+glVertex2bOES 
+glVertex2bvOES 
+glVertex2d 
+glVertex2dv 
+glVertex2f 
+glVertex2fv 
+glVertex2hNV 
+glVertex2hvNV 
+glVertex2i 
+glVertex2iv 
+glVertex2s 
+glVertex2sv 
+glVertex2xOES 
+glVertex2xvOES 
+glVertex3bOES 
+glVertex3bvOES 
+glVertex3d 
+glVertex3dv 
+glVertex3f 
+glVertex3fv 
+glVertex3hNV 
+glVertex3hvNV 
+glVertex3i 
+glVertex3iv 
+glVertex3s 
+glVertex3sv 
+glVertex3xOES 
+glVertex3xvOES 
+glVertex4bOES 
+glVertex4bvOES 
+glVertex4d 
+glVertex4dv 
+glVertex4f 
+glVertex4fv 
+glVertex4hNV 
+glVertex4hvNV 
+glVertex4i 
+glVertex4iv 
+glVertex4s 
+glVertex4sv 
+glVertex4xOES 
+glVertex4xvOES 
+glVertexArrayAttribBinding 
+glVertexArrayAttribFormat 
+glVertexArrayAttribIFormat 
+glVertexArrayAttribLFormat 
+glVertexArrayBindVertexBufferEXT 
+glVertexArrayBindingDivisor 
+glVertexArrayColorOffsetEXT 
+glVertexArrayEdgeFlagOffsetEXT 
+glVertexArrayElementBuffer 
+glVertexArrayFogCoordOffsetEXT 
+glVertexArrayIndexOffsetEXT 
+glVertexArrayMultiTexCoordOffsetEXT 
+glVertexArrayNormalOffsetEXT 
+glVertexArrayParameteriAPPLE 
+glVertexArrayRangeAPPLE 
+glVertexArrayRangeNV 
+glVertexArraySecondaryColorOffsetEXT 
+glVertexArrayTexCoordOffsetEXT 
+glVertexArrayVertexAttribBindingEXT 
+glVertexArrayVertexAttribDivisorEXT 
+glVertexArrayVertexAttribFormatEXT 
+glVertexArrayVertexAttribIFormatEXT 
+glVertexArrayVertexAttribIOffsetEXT 
+glVertexArrayVertexAttribLFormatEXT 
+glVertexArrayVertexAttribLOffsetEXT 
+glVertexArrayVertexAttribOffsetEXT 
+glVertexArrayVertexBindingDivisorEXT 
+glVertexArrayVertexBuffer 
+glVertexArrayVertexBuffers 
+glVertexArrayVertexOffsetEXT 
+glVertexAttrib1d 
+glVertexAttrib1dARB 
+glVertexAttrib1dNV 
+glVertexAttrib1dv 
+glVertexAttrib1dvARB 
+glVertexAttrib1dvNV 
+glVertexAttrib1f 
+glVertexAttrib1fARB 
+glVertexAttrib1fNV 
+glVertexAttrib1fv 
+glVertexAttrib1fvARB 
+glVertexAttrib1fvNV 
+glVertexAttrib1hNV 
+glVertexAttrib1hvNV 
+glVertexAttrib1s 
+glVertexAttrib1sARB 
+glVertexAttrib1sNV 
+glVertexAttrib1sv 
+glVertexAttrib1svARB 
+glVertexAttrib1svNV 
+glVertexAttrib2d 
+glVertexAttrib2dARB 
+glVertexAttrib2dNV 
+glVertexAttrib2dv 
+glVertexAttrib2dvARB 
+glVertexAttrib2dvNV 
+glVertexAttrib2f 
+glVertexAttrib2fARB 
+glVertexAttrib2fNV 
+glVertexAttrib2fv 
+glVertexAttrib2fvARB 
+glVertexAttrib2fvNV 
+glVertexAttrib2hNV 
+glVertexAttrib2hvNV 
+glVertexAttrib2s 
+glVertexAttrib2sARB 
+glVertexAttrib2sNV 
+glVertexAttrib2sv 
+glVertexAttrib2svARB 
+glVertexAttrib2svNV 
+glVertexAttrib3d 
+glVertexAttrib3dARB 
+glVertexAttrib3dNV 
+glVertexAttrib3dv 
+glVertexAttrib3dvARB 
+glVertexAttrib3dvNV 
+glVertexAttrib3f 
+glVertexAttrib3fARB 
+glVertexAttrib3fNV 
+glVertexAttrib3fv 
+glVertexAttrib3fvARB 
+glVertexAttrib3fvNV 
+glVertexAttrib3hNV 
+glVertexAttrib3hvNV 
+glVertexAttrib3s 
+glVertexAttrib3sARB 
+glVertexAttrib3sNV 
+glVertexAttrib3sv 
+glVertexAttrib3svARB 
+glVertexAttrib3svNV 
+glVertexAttrib4Nbv 
+glVertexAttrib4NbvARB 
+glVertexAttrib4Niv 
+glVertexAttrib4NivARB 
+glVertexAttrib4Nsv 
+glVertexAttrib4NsvARB 
+glVertexAttrib4Nub 
+glVertexAttrib4NubARB 
+glVertexAttrib4Nubv 
+glVertexAttrib4NubvARB 
+glVertexAttrib4Nuiv 
+glVertexAttrib4NuivARB 
+glVertexAttrib4Nusv 
+glVertexAttrib4NusvARB 
+glVertexAttrib4bv 
+glVertexAttrib4bvARB 
+glVertexAttrib4d 
+glVertexAttrib4dARB 
+glVertexAttrib4dNV 
+glVertexAttrib4dv 
+glVertexAttrib4dvARB 
+glVertexAttrib4dvNV 
+glVertexAttrib4f 
+glVertexAttrib4fARB 
+glVertexAttrib4fNV 
+glVertexAttrib4fv 
+glVertexAttrib4fvARB 
+glVertexAttrib4fvNV 
+glVertexAttrib4hNV 
+glVertexAttrib4hvNV 
+glVertexAttrib4iv 
+glVertexAttrib4ivARB 
+glVertexAttrib4s 
+glVertexAttrib4sARB 
+glVertexAttrib4sNV 
+glVertexAttrib4sv 
+glVertexAttrib4svARB 
+glVertexAttrib4svNV 
+glVertexAttrib4ubNV 
+glVertexAttrib4ubv 
+glVertexAttrib4ubvARB 
+glVertexAttrib4ubvNV 
+glVertexAttrib4uiv 
+glVertexAttrib4uivARB 
+glVertexAttrib4usv 
+glVertexAttrib4usvARB 
+glVertexAttribArrayObjectATI 
+glVertexAttribBinding 
+glVertexAttribDivisor 
+glVertexAttribDivisorANGLE 
+glVertexAttribDivisorARB 
+glVertexAttribDivisorEXT 
+glVertexAttribDivisorNV 
+glVertexAttribFormat 
+glVertexAttribFormatNV 
+glVertexAttribI1i 
+glVertexAttribI1iEXT 
+glVertexAttribI1iv 
+glVertexAttribI1ivEXT 
+glVertexAttribI1ui 
+glVertexAttribI1uiEXT 
+glVertexAttribI1uiv 
+glVertexAttribI1uivEXT 
+glVertexAttribI2i 
+glVertexAttribI2iEXT 
+glVertexAttribI2iv 
+glVertexAttribI2ivEXT 
+glVertexAttribI2ui 
+glVertexAttribI2uiEXT 
+glVertexAttribI2uiv 
+glVertexAttribI2uivEXT 
+glVertexAttribI3i 
+glVertexAttribI3iEXT 
+glVertexAttribI3iv 
+glVertexAttribI3ivEXT 
+glVertexAttribI3ui 
+glVertexAttribI3uiEXT 
+glVertexAttribI3uiv 
+glVertexAttribI3uivEXT 
+glVertexAttribI4bv 
+glVertexAttribI4bvEXT 
+glVertexAttribI4i 
+glVertexAttribI4iEXT 
+glVertexAttribI4iv 
+glVertexAttribI4ivEXT 
+glVertexAttribI4sv 
+glVertexAttribI4svEXT 
+glVertexAttribI4ubv 
+glVertexAttribI4ubvEXT 
+glVertexAttribI4ui 
+glVertexAttribI4uiEXT 
+glVertexAttribI4uiv 
+glVertexAttribI4uivEXT 
+glVertexAttribI4usv 
+glVertexAttribI4usvEXT 
+glVertexAttribIFormat 
+glVertexAttribIFormatNV 
+glVertexAttribIPointer 
+glVertexAttribIPointerEXT 
+glVertexAttribL1d 
+glVertexAttribL1dEXT 
+glVertexAttribL1dv 
+glVertexAttribL1dvEXT 
+glVertexAttribL1i64NV 
+glVertexAttribL1i64vNV 
+glVertexAttribL1ui64ARB 
+glVertexAttribL1ui64NV 
+glVertexAttribL1ui64vARB 
+glVertexAttribL1ui64vNV 
+glVertexAttribL2d 
+glVertexAttribL2dEXT 
+glVertexAttribL2dv 
+glVertexAttribL2dvEXT 
+glVertexAttribL2i64NV 
+glVertexAttribL2i64vNV 
+glVertexAttribL2ui64NV 
+glVertexAttribL2ui64vNV 
+glVertexAttribL3d 
+glVertexAttribL3dEXT 
+glVertexAttribL3dv 
+glVertexAttribL3dvEXT 
+glVertexAttribL3i64NV 
+glVertexAttribL3i64vNV 
+glVertexAttribL3ui64NV 
+glVertexAttribL3ui64vNV 
+glVertexAttribL4d 
+glVertexAttribL4dEXT 
+glVertexAttribL4dv 
+glVertexAttribL4dvEXT 
+glVertexAttribL4i64NV 
+glVertexAttribL4i64vNV 
+glVertexAttribL4ui64NV 
+glVertexAttribL4ui64vNV 
+glVertexAttribLFormat 
+glVertexAttribLFormatNV 
+glVertexAttribLPointer 
+glVertexAttribLPointerEXT 
+glVertexAttribP1ui 
+glVertexAttribP1uiv 
+glVertexAttribP2ui 
+glVertexAttribP2uiv 
+glVertexAttribP3ui 
+glVertexAttribP3uiv 
+glVertexAttribP4ui 
+glVertexAttribP4uiv 
+glVertexAttribParameteriAMD 
+glVertexAttribPointer 
+glVertexAttribPointerARB 
+glVertexAttribPointerNV 
+glVertexAttribs1dvNV 
+glVertexAttribs1fvNV 
+glVertexAttribs1hvNV 
+glVertexAttribs1svNV 
+glVertexAttribs2dvNV 
+glVertexAttribs2fvNV 
+glVertexAttribs2hvNV 
+glVertexAttribs2svNV 
+glVertexAttribs3dvNV 
+glVertexAttribs3fvNV 
+glVertexAttribs3hvNV 
+glVertexAttribs3svNV 
+glVertexAttribs4dvNV 
+glVertexAttribs4fvNV 
+glVertexAttribs4hvNV 
+glVertexAttribs4svNV 
+glVertexAttribs4ubvNV 
+glVertexBindingDivisor 
+glVertexBlendARB 
+glVertexBlendEnvfATI 
+glVertexBlendEnviATI 
+glVertexFormatNV 
+glVertexP2ui 
+glVertexP2uiv 
+glVertexP3ui 
+glVertexP3uiv 
+glVertexP4ui 
+glVertexP4uiv 
+glVertexPointer 
+glVertexPointerEXT 
+glVertexPointerListIBM 
+glVertexPointervINTEL 
+glVertexStream1dATI 
+glVertexStream1dvATI 
+glVertexStream1fATI 
+glVertexStream1fvATI 
+glVertexStream1iATI 
+glVertexStream1ivATI 
+glVertexStream1sATI 
+glVertexStream1svATI 
+glVertexStream2dATI 
+glVertexStream2dvATI 
+glVertexStream2fATI 
+glVertexStream2fvATI 
+glVertexStream2iATI 
+glVertexStream2ivATI 
+glVertexStream2sATI 
+glVertexStream2svATI 
+glVertexStream3dATI 
+glVertexStream3dvATI 
+glVertexStream3fATI 
+glVertexStream3fvATI 
+glVertexStream3iATI 
+glVertexStream3ivATI 
+glVertexStream3sATI 
+glVertexStream3svATI 
+glVertexStream4dATI 
+glVertexStream4dvATI 
+glVertexStream4fATI 
+glVertexStream4fvATI 
+glVertexStream4iATI 
+glVertexStream4ivATI 
+glVertexStream4sATI 
+glVertexStream4svATI 
+glVertexWeightPointerEXT 
+glVertexWeightfEXT 
+glVertexWeightfvEXT 
+glVertexWeighthNV 
+glVertexWeighthvNV 
+glVideoCaptureNV 
+glVideoCaptureStreamParameterdvNV 
+glVideoCaptureStreamParameterfvNV 
+glVideoCaptureStreamParameterivNV 
+glViewport 
+glViewportArrayv 
+glViewportArrayvNV 
+glViewportIndexedf 
+glViewportIndexedfNV 
+glViewportIndexedfv 
+glViewportIndexedfvNV 
+glViewportSwizzleNV 
+glWaitSync 
+glWaitSyncAPPLE 
+glWaitSyncValueuiNVX 
+glWeightPathsNV 
+glWeightPointerARB 
+glWeightPointerOES 
+glWeightbvARB 
+glWeightdvARB 
+glWeightfvARB 
+glWeightivARB 
+glWeightsvARB 
+glWeightubvARB 
+glWeightuivARB 
+glWeightusvARB 
+glWindowBackBufferHintAutodesk 
+glWindowPos2d 
+glWindowPos2dARB 
+glWindowPos2dMESA 
+glWindowPos2dv 
+glWindowPos2dvARB 
+glWindowPos2dvMESA 
+glWindowPos2f 
+glWindowPos2fARB 
+glWindowPos2fMESA 
+glWindowPos2fv 
+glWindowPos2fvARB 
+glWindowPos2fvMESA 
+glWindowPos2i 
+glWindowPos2iARB 
+glWindowPos2iMESA 
+glWindowPos2iv 
+glWindowPos2ivARB 
+glWindowPos2ivMESA 
+glWindowPos2s 
+glWindowPos2sARB 
+glWindowPos2sMESA 
+glWindowPos2sv 
+glWindowPos2svARB 
+glWindowPos2svMESA 
+glWindowPos3d 
+glWindowPos3dARB 
+glWindowPos3dMESA 
+glWindowPos3dv 
+glWindowPos3dvARB 
+glWindowPos3dvMESA 
+glWindowPos3f 
+glWindowPos3fARB 
+glWindowPos3fMESA 
+glWindowPos3fv 
+glWindowPos3fvARB 
+glWindowPos3fvMESA 
+glWindowPos3i 
+glWindowPos3iARB 
+glWindowPos3iMESA 
+glWindowPos3iv 
+glWindowPos3ivARB 
+glWindowPos3ivMESA 
+glWindowPos3s 
+glWindowPos3sARB 
+glWindowPos3sMESA 
+glWindowPos3sv 
+glWindowPos3svARB 
+glWindowPos3svMESA 
+glWindowPos4dMESA 
+glWindowPos4dvMESA 
+glWindowPos4fMESA 
+glWindowPos4fvMESA 
+glWindowPos4iMESA 
+glWindowPos4ivMESA 
+glWindowPos4sMESA 
+glWindowPos4svMESA 
+glWriteMaskEXT 
+glXAllocateMemoryNV 
+glXBindChannelToWindowSGIX 
+glXBindHyperpipeSGIX 
+glXBindSwapBarrierNV 
+glXBindSwapBarrierSGIX 
+glXBindTexImageEXT 
+glXBindVideoCaptureDeviceNV 
+glXBindVideoDeviceNV 
+glXBindVideoImageNV 
+glXBlitContextFramebufferAMD 
+glXChannelRectSGIX 
+glXChannelRectSyncSGIX 
+glXChooseFBConfig 
+glXChooseFBConfigSGIX 
+glXChooseVisual 
+glXCopyBufferSubDataNV 
+glXCopyContext 
+glXCopyImageSubDataNV 
+glXCopySubBufferMESA 
+glXCreateAssociatedContextAMD 
+glXCreateAssociatedContextAttribsAMD 
+glXCreateContext 
+glXCreateContextAttribsARB 
+glXCreateContextWithConfigSGIX 
+glXCreateGLXPbufferSGIX 
+glXCreateGLXPixmap 
+glXCreateGLXPixmapMESA 
+glXCreateGLXPixmapWithConfigSGIX 
+glXCreateNewContext 
+glXCreatePbuffer 
+glXCreatePixmap 
+glXCreateWindow 
+glXCushionSGI 
+glXDelayBeforeSwapNV 
+glXDeleteAssociatedContextAMD 
+glXDestroyContext 
+glXDestroyGLXPbufferSGIX 
+glXDestroyGLXPixmap 
+glXDestroyHyperpipeConfigSGIX 
+glXDestroyPbuffer 
+glXDestroyPixmap 
+glXDestroyWindow 
+glXEnumerateVideoCaptureDevicesNV 
+glXEnumerateVideoDevicesNV 
+glXFreeContextEXT 
+glXFreeMemoryNV 
+glXGetAGPOffsetMESA 
+glXGetClientString 
+glXGetConfig 
+glXGetContextGPUIDAMD 
+glXGetContextIDEXT 
+glXGetCurrentAssociatedContextAMD 
+glXGetCurrentContext 
+glXGetCurrentDisplay 
+glXGetCurrentDisplayEXT 
+glXGetCurrentDrawable 
+glXGetCurrentReadDrawable 
+glXGetCurrentReadDrawableSGI 
+glXGetDriverConfig 
+glXGetFBConfigAttrib 
+glXGetFBConfigAttribSGIX 
+glXGetFBConfigFromVisualSGIX 
+glXGetFBConfigs 
+glXGetGPUIDsAMD 
+glXGetGPUInfoAMD 
+glXGetMscRateOML 
+glXGetProcAddress 
+glXGetProcAddressARB 
+glXGetRefreshRateSGI 
+glXGetScreenDriver 
+glXGetSelectedEvent 
+glXGetSelectedEventSGIX 
+glXGetSyncValuesOML 
+glXGetTransparentIndexSUN 
+glXGetVideoDeviceNV 
+glXGetVideoInfoNV 
+glXGetVideoSyncSGI 
+glXGetVisualFromFBConfig 
+glXGetVisualFromFBConfigSGIX 
+glXHyperpipeAttribSGIX 
+glXHyperpipeConfigSGIX 
+glXImportContextEXT 
+glXIsDirect 
+glXJoinSwapGroupNV 
+glXJoinSwapGroupSGIX 
+glXLockVideoCaptureDeviceNV 
+glXMakeAssociatedContextCurrentAMD 
+glXMakeContextCurrent 
+glXMakeCurrent 
+glXMakeCurrentReadSGI 
+glXNamedCopyBufferSubDataNV 
+glXQueryChannelDeltasSGIX 
+glXQueryChannelRectSGIX 
+glXQueryContext 
+glXQueryContextInfoEXT 
+glXQueryCurrentRendererIntegerMESA 
+glXQueryCurrentRendererStringMESA 
+glXQueryDrawable 
+glXQueryExtension 
+glXQueryExtensionsString 
+glXQueryFrameCountNV 
+glXQueryGLXPbufferSGIX 
+glXQueryHyperpipeAttribSGIX 
+glXQueryHyperpipeBestAttribSGIX 
+glXQueryHyperpipeConfigSGIX 
+glXQueryHyperpipeNetworkSGIX 
+glXQueryMaxSwapBarriersSGIX 
+glXQueryMaxSwapGroupsNV 
+glXQueryRendererIntegerMESA 
+glXQueryRendererStringMESA 
+glXQueryServerString 
+glXQuerySwapGroupNV 
+glXQueryVersion 
+glXQueryVideoCaptureDeviceNV 
+glXReleaseBuffersMESA 
+glXReleaseTexImageEXT 
+glXReleaseVideoCaptureDeviceNV 
+glXReleaseVideoDeviceNV 
+glXReleaseVideoImageNV 
+glXResetFrameCountNV 
+glXSelectEvent 
+glXSelectEventSGIX 
+glXSendPbufferToVideoNV 
+glXSet3DfxModeMESA 
+glXSwapBuffers 
+glXSwapBuffersMscOML 
+glXSwapIntervalEXT 
+glXSwapIntervalSGI 
+glXUseXFont 
+glXWaitForMscOML 
+glXWaitForSbcOML 
+glXWaitGL 
+glXWaitVideoSyncSGI 
+glXWaitX 
diff --git a/subprojects/libcapsule/examples/shim/libGL.so.1.symbols.updated-for b/subprojects/libcapsule/examples/shim/libGL.so.1.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGLESv2.so.2.excluded b/subprojects/libcapsule/examples/shim/libGLESv2.so.2.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGLESv2.so.2.shared b/subprojects/libcapsule/examples/shim/libGLESv2.so.2.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGLESv2.so.2.symbols b/subprojects/libcapsule/examples/shim/libGLESv2.so.2.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..3a792998b832d851f495160ad51dad0cb41ed8b7
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libGLESv2.so.2.symbols
@@ -0,0 +1,365 @@
+glActiveShaderProgram 
+glActiveTexture 
+glAttachShader 
+glBeginQuery 
+glBeginTransformFeedback 
+glBindAttribLocation 
+glBindBuffer 
+glBindBufferBase 
+glBindBufferRange 
+glBindFragDataLocationEXT 
+glBindFramebuffer 
+glBindImageTexture 
+glBindProgramPipeline 
+glBindRenderbuffer 
+glBindSampler 
+glBindTexture 
+glBindTransformFeedback 
+glBindVertexArray 
+glBindVertexBuffer 
+glBlendBarrier 
+glBlendColor 
+glBlendEquation 
+glBlendEquationSeparate 
+glBlendEquationSeparatei 
+glBlendEquationi 
+glBlendFunc 
+glBlendFuncSeparate 
+glBlendFuncSeparatei 
+glBlendFunci 
+glBlitFramebuffer 
+glBufferData 
+glBufferSubData 
+glCheckFramebufferStatus 
+glClear 
+glClearBufferfi 
+glClearBufferfv 
+glClearBufferiv 
+glClearBufferuiv 
+glClearColor 
+glClearDepthf 
+glClearStencil 
+glClientWaitSync 
+glColorMask 
+glColorMaski 
+glCompileShader 
+glCompressedTexImage2D 
+glCompressedTexImage3D 
+glCompressedTexSubImage2D 
+glCompressedTexSubImage3D 
+glCopyBufferSubData 
+glCopyImageSubData 
+glCopyTexImage2D 
+glCopyTexSubImage2D 
+glCopyTexSubImage3D 
+glCreateProgram 
+glCreateShader 
+glCreateShaderProgramv 
+glCullFace 
+glDebugMessageCallback 
+glDebugMessageControl 
+glDebugMessageInsert 
+glDeleteBuffers 
+glDeleteFramebuffers 
+glDeleteProgram 
+glDeleteProgramPipelines 
+glDeleteQueries 
+glDeleteRenderbuffers 
+glDeleteSamplers 
+glDeleteShader 
+glDeleteSync 
+glDeleteTextures 
+glDeleteTransformFeedbacks 
+glDeleteVertexArrays 
+glDepthFunc 
+glDepthMask 
+glDepthRangef 
+glDetachShader 
+glDisable 
+glDisableVertexAttribArray 
+glDisablei 
+glDispatchCompute 
+glDispatchComputeIndirect 
+glDrawArrays 
+glDrawArraysIndirect 
+glDrawArraysInstanced 
+glDrawBuffers 
+glDrawElements 
+glDrawElementsBaseVertex 
+glDrawElementsIndirect 
+glDrawElementsInstanced 
+glDrawElementsInstancedBaseVertex 
+glDrawRangeElements 
+glDrawRangeElementsBaseVertex 
+glEnable 
+glEnableVertexAttribArray 
+glEnablei 
+glEndQuery 
+glEndTransformFeedback 
+glFenceSync 
+glFinish 
+glFlush 
+glFlushMappedBufferRange 
+glFramebufferParameteri 
+glFramebufferRenderbuffer 
+glFramebufferTexture 
+glFramebufferTexture2D 
+glFramebufferTextureLayer 
+glFrontFace 
+glGenBuffers 
+glGenFramebuffers 
+glGenProgramPipelines 
+glGenQueries 
+glGenRenderbuffers 
+glGenSamplers 
+glGenTextures 
+glGenTransformFeedbacks 
+glGenVertexArrays 
+glGenerateMipmap 
+glGetActiveAttrib 
+glGetActiveUniform 
+glGetActiveUniformBlockName 
+glGetActiveUniformBlockiv 
+glGetActiveUniformsiv 
+glGetAttachedShaders 
+glGetAttribLocation 
+glGetBooleani_v 
+glGetBooleanv 
+glGetBufferParameteri64v 
+glGetBufferParameteriv 
+glGetBufferPointerv 
+glGetDebugMessageLog 
+glGetError 
+glGetFloatv 
+glGetFragDataLocation 
+glGetFramebufferAttachmentParameteriv 
+glGetFramebufferParameteriv 
+glGetGraphicsResetStatus 
+glGetInteger64i_v 
+glGetInteger64v 
+glGetIntegeri_v 
+glGetIntegerv 
+glGetInternalformativ 
+glGetMultisamplefv 
+glGetObjectLabel 
+glGetObjectPtrLabel 
+glGetPointerv 
+glGetProgramBinary 
+glGetProgramInfoLog 
+glGetProgramInterfaceiv 
+glGetProgramPipelineInfoLog 
+glGetProgramPipelineiv 
+glGetProgramResourceIndex 
+glGetProgramResourceLocation 
+glGetProgramResourceName 
+glGetProgramResourceiv 
+glGetProgramiv 
+glGetQueryObjectuiv 
+glGetQueryiv 
+glGetRenderbufferParameteriv 
+glGetSamplerParameterIiv 
+glGetSamplerParameterIuiv 
+glGetSamplerParameterfv 
+glGetSamplerParameteriv 
+glGetShaderInfoLog 
+glGetShaderPrecisionFormat 
+glGetShaderSource 
+glGetShaderiv 
+glGetString 
+glGetStringi 
+glGetSynciv 
+glGetTexLevelParameterfv 
+glGetTexLevelParameteriv 
+glGetTexParameterIiv 
+glGetTexParameterIivEXT 
+glGetTexParameterIuiv 
+glGetTexParameterIuivEXT 
+glGetTexParameterfv 
+glGetTexParameteriv 
+glGetTransformFeedbackVarying 
+glGetUniformBlockIndex 
+glGetUniformIndices 
+glGetUniformLocation 
+glGetUniformfv 
+glGetUniformiv 
+glGetUniformuiv 
+glGetVertexAttribIiv 
+glGetVertexAttribIuiv 
+glGetVertexAttribPointerv 
+glGetVertexAttribfv 
+glGetVertexAttribiv 
+glGetnUniformfv 
+glGetnUniformiv 
+glGetnUniformuiv 
+glHint 
+glInvalidateFramebuffer 
+glInvalidateSubFramebuffer 
+glIsBuffer 
+glIsEnabled 
+glIsEnabledi 
+glIsFramebuffer 
+glIsProgram 
+glIsProgramPipeline 
+glIsQuery 
+glIsRenderbuffer 
+glIsSampler 
+glIsShader 
+glIsSync 
+glIsTexture 
+glIsTransformFeedback 
+glIsVertexArray 
+glLineWidth 
+glLinkProgram 
+glMapBufferRange 
+glMemoryBarrier 
+glMemoryBarrierByRegion 
+glMinSampleShading 
+glMultiDrawArraysEXT 
+glMultiDrawElementsEXT 
+glObjectLabel 
+glObjectPtrLabel 
+glPatchParameteri 
+glPauseTransformFeedback 
+glPixelStorei 
+glPolygonOffset 
+glPopDebugGroup 
+glPrimitiveBoundingBox 
+glProgramBinary 
+glProgramParameteri 
+glProgramUniform1f 
+glProgramUniform1fv 
+glProgramUniform1i 
+glProgramUniform1iv 
+glProgramUniform1ui 
+glProgramUniform1uiv 
+glProgramUniform2f 
+glProgramUniform2fv 
+glProgramUniform2i 
+glProgramUniform2iv 
+glProgramUniform2ui 
+glProgramUniform2uiv 
+glProgramUniform3f 
+glProgramUniform3fv 
+glProgramUniform3i 
+glProgramUniform3iv 
+glProgramUniform3ui 
+glProgramUniform3uiv 
+glProgramUniform4f 
+glProgramUniform4fv 
+glProgramUniform4i 
+glProgramUniform4iv 
+glProgramUniform4ui 
+glProgramUniform4uiv 
+glProgramUniformMatrix2fv 
+glProgramUniformMatrix2x3fv 
+glProgramUniformMatrix2x4fv 
+glProgramUniformMatrix3fv 
+glProgramUniformMatrix3x2fv 
+glProgramUniformMatrix3x4fv 
+glProgramUniformMatrix4fv 
+glProgramUniformMatrix4x2fv 
+glProgramUniformMatrix4x3fv 
+glPushDebugGroup 
+glReadBuffer 
+glReadPixels 
+glReadnPixels 
+glReleaseShaderCompiler 
+glRenderbufferStorage 
+glRenderbufferStorageMultisample 
+glResumeTransformFeedback 
+glSampleCoverage 
+glSampleMaski 
+glSamplerParameterIiv 
+glSamplerParameterIuiv 
+glSamplerParameterf 
+glSamplerParameterfv 
+glSamplerParameteri 
+glSamplerParameteriv 
+glScissor 
+glShaderBinary 
+glShaderSource 
+glStencilFunc 
+glStencilFuncSeparate 
+glStencilMask 
+glStencilMaskSeparate 
+glStencilOp 
+glStencilOpSeparate 
+glTexBuffer 
+glTexBufferRange 
+glTexImage2D 
+glTexImage3D 
+glTexParameterIiv 
+glTexParameterIivEXT 
+glTexParameterIuiv 
+glTexParameterIuivEXT 
+glTexParameterf 
+glTexParameterfv 
+glTexParameteri 
+glTexParameteriv 
+glTexStorage2D 
+glTexStorage2DMultisample 
+glTexStorage3D 
+glTexStorage3DMultisample 
+glTexSubImage2D 
+glTexSubImage3D 
+glTransformFeedbackVaryings 
+glUniform1f 
+glUniform1fv 
+glUniform1i 
+glUniform1iv 
+glUniform1ui 
+glUniform1uiv 
+glUniform2f 
+glUniform2fv 
+glUniform2i 
+glUniform2iv 
+glUniform2ui 
+glUniform2uiv 
+glUniform3f 
+glUniform3fv 
+glUniform3i 
+glUniform3iv 
+glUniform3ui 
+glUniform3uiv 
+glUniform4f 
+glUniform4fv 
+glUniform4i 
+glUniform4iv 
+glUniform4ui 
+glUniform4uiv 
+glUniformBlockBinding 
+glUniformMatrix2fv 
+glUniformMatrix2x3fv 
+glUniformMatrix2x4fv 
+glUniformMatrix3fv 
+glUniformMatrix3x2fv 
+glUniformMatrix3x4fv 
+glUniformMatrix4fv 
+glUniformMatrix4x2fv 
+glUniformMatrix4x3fv 
+glUnmapBuffer 
+glUseProgram 
+glUseProgramStages 
+glValidateProgram 
+glValidateProgramPipeline 
+glVertexAttrib1f 
+glVertexAttrib1fv 
+glVertexAttrib2f 
+glVertexAttrib2fv 
+glVertexAttrib3f 
+glVertexAttrib3fv 
+glVertexAttrib4f 
+glVertexAttrib4fv 
+glVertexAttribBinding 
+glVertexAttribDivisor 
+glVertexAttribFormat 
+glVertexAttribI4i 
+glVertexAttribI4iv 
+glVertexAttribI4ui 
+glVertexAttribI4uiv 
+glVertexAttribIFormat 
+glVertexAttribIPointer 
+glVertexAttribPointer 
+glVertexBindingDivisor 
+glViewport 
+glWaitSync 
diff --git a/subprojects/libcapsule/examples/shim/libGLESv2.so.2.symbols.updated-for b/subprojects/libcapsule/examples/shim/libGLESv2.so.2.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGLX.so.0.excluded b/subprojects/libcapsule/examples/shim/libGLX.so.0.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGLX.so.0.shared b/subprojects/libcapsule/examples/shim/libGLX.so.0.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libGLX.so.0.symbols b/subprojects/libcapsule/examples/shim/libGLX.so.0.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..1577c60fac36ba790841e0c914a8492ca1304d76
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libGLX.so.0.symbols
@@ -0,0 +1,42 @@
+__GLXGL_CORE_FUNCTIONS 
+__glXGLLoadGLXFunction 
+glXChooseFBConfig 
+glXChooseVisual 
+glXCopyContext 
+glXCreateContext 
+glXCreateGLXPixmap 
+glXCreateNewContext 
+glXCreatePbuffer 
+glXCreatePixmap 
+glXCreateWindow 
+glXDestroyContext 
+glXDestroyGLXPixmap 
+glXDestroyPbuffer 
+glXDestroyPixmap 
+glXDestroyWindow 
+glXGetClientString 
+glXGetConfig 
+glXGetCurrentContext 
+glXGetCurrentDisplay 
+glXGetCurrentDrawable 
+glXGetCurrentReadDrawable 
+glXGetFBConfigAttrib 
+glXGetFBConfigs 
+glXGetProcAddress 
+glXGetProcAddressARB 
+glXGetSelectedEvent 
+glXGetVisualFromFBConfig 
+glXIsDirect 
+glXMakeContextCurrent 
+glXMakeCurrent 
+glXQueryContext 
+glXQueryDrawable 
+glXQueryExtension 
+glXQueryExtensionsString 
+glXQueryServerString 
+glXQueryVersion 
+glXSelectEvent 
+glXSwapBuffers 
+glXUseXFont 
+glXWaitGL 
+glXWaitX 
diff --git a/subprojects/libcapsule/examples/shim/libGLX.so.0.symbols.updated-for b/subprojects/libcapsule/examples/shim/libGLX.so.0.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libOpenGL.so.0.excluded b/subprojects/libcapsule/examples/shim/libOpenGL.so.0.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libOpenGL.so.0.shared b/subprojects/libcapsule/examples/shim/libOpenGL.so.0.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libOpenGL.so.0.symbols b/subprojects/libcapsule/examples/shim/libOpenGL.so.0.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..6f21479973fe17a93dfe167e334b75d427dbfa52
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libOpenGL.so.0.symbols
@@ -0,0 +1,1044 @@
+glAccum 
+glActiveShaderProgram 
+glActiveTexture 
+glAlphaFunc 
+glAreTexturesResident 
+glArrayElement 
+glAttachShader 
+glBegin 
+glBeginConditionalRender 
+glBeginQuery 
+glBeginQueryIndexed 
+glBeginTransformFeedback 
+glBindAttribLocation 
+glBindBuffer 
+glBindBufferBase 
+glBindBufferRange 
+glBindBuffersBase 
+glBindBuffersRange 
+glBindFragDataLocation 
+glBindFragDataLocationIndexed 
+glBindFramebuffer 
+glBindImageTexture 
+glBindImageTextures 
+glBindProgramPipeline 
+glBindRenderbuffer 
+glBindSampler 
+glBindSamplers 
+glBindTexture 
+glBindTextureUnit 
+glBindTextures 
+glBindTransformFeedback 
+glBindVertexArray 
+glBindVertexBuffer 
+glBindVertexBuffers 
+glBitmap 
+glBlendColor 
+glBlendEquation 
+glBlendEquationSeparate 
+glBlendEquationSeparatei 
+glBlendEquationi 
+glBlendFunc 
+glBlendFuncSeparate 
+glBlendFuncSeparatei 
+glBlendFunci 
+glBlitFramebuffer 
+glBlitNamedFramebuffer 
+glBufferData 
+glBufferStorage 
+glBufferSubData 
+glCallList 
+glCallLists 
+glCheckFramebufferStatus 
+glCheckNamedFramebufferStatus 
+glClampColor 
+glClear 
+glClearAccum 
+glClearBufferData 
+glClearBufferSubData 
+glClearBufferfi 
+glClearBufferfv 
+glClearBufferiv 
+glClearBufferuiv 
+glClearColor 
+glClearDepth 
+glClearDepthf 
+glClearIndex 
+glClearNamedBufferData 
+glClearNamedBufferSubData 
+glClearNamedFramebufferfi 
+glClearNamedFramebufferfv 
+glClearNamedFramebufferiv 
+glClearNamedFramebufferuiv 
+glClearStencil 
+glClearTexImage 
+glClearTexSubImage 
+glClientActiveTexture 
+glClientWaitSync 
+glClipControl 
+glClipPlane 
+glColor3b 
+glColor3bv 
+glColor3d 
+glColor3dv 
+glColor3f 
+glColor3fv 
+glColor3i 
+glColor3iv 
+glColor3s 
+glColor3sv 
+glColor3ub 
+glColor3ubv 
+glColor3ui 
+glColor3uiv 
+glColor3us 
+glColor3usv 
+glColor4b 
+glColor4bv 
+glColor4d 
+glColor4dv 
+glColor4f 
+glColor4fv 
+glColor4i 
+glColor4iv 
+glColor4s 
+glColor4sv 
+glColor4ub 
+glColor4ubv 
+glColor4ui 
+glColor4uiv 
+glColor4us 
+glColor4usv 
+glColorMask 
+glColorMaski 
+glColorMaterial 
+glColorP3ui 
+glColorP3uiv 
+glColorP4ui 
+glColorP4uiv 
+glColorPointer 
+glCompileShader 
+glCompressedTexImage1D 
+glCompressedTexImage2D 
+glCompressedTexImage3D 
+glCompressedTexSubImage1D 
+glCompressedTexSubImage2D 
+glCompressedTexSubImage3D 
+glCompressedTextureSubImage1D 
+glCompressedTextureSubImage2D 
+glCompressedTextureSubImage3D 
+glCopyBufferSubData 
+glCopyImageSubData 
+glCopyNamedBufferSubData 
+glCopyPixels 
+glCopyTexImage1D 
+glCopyTexImage2D 
+glCopyTexSubImage1D 
+glCopyTexSubImage2D 
+glCopyTexSubImage3D 
+glCopyTextureSubImage1D 
+glCopyTextureSubImage2D 
+glCopyTextureSubImage3D 
+glCreateBuffers 
+glCreateFramebuffers 
+glCreateProgram 
+glCreateProgramPipelines 
+glCreateQueries 
+glCreateRenderbuffers 
+glCreateSamplers 
+glCreateShader 
+glCreateShaderProgramv 
+glCreateTextures 
+glCreateTransformFeedbacks 
+glCreateVertexArrays 
+glCullFace 
+glDebugMessageCallback 
+glDebugMessageControl 
+glDebugMessageInsert 
+glDeleteBuffers 
+glDeleteFramebuffers 
+glDeleteLists 
+glDeleteProgram 
+glDeleteProgramPipelines 
+glDeleteQueries 
+glDeleteRenderbuffers 
+glDeleteSamplers 
+glDeleteShader 
+glDeleteSync 
+glDeleteTextures 
+glDeleteTransformFeedbacks 
+glDeleteVertexArrays 
+glDepthFunc 
+glDepthMask 
+glDepthRange 
+glDepthRangeArrayv 
+glDepthRangeIndexed 
+glDepthRangef 
+glDetachShader 
+glDisable 
+glDisableClientState 
+glDisableVertexArrayAttrib 
+glDisableVertexAttribArray 
+glDisablei 
+glDispatchCompute 
+glDispatchComputeIndirect 
+glDrawArrays 
+glDrawArraysIndirect 
+glDrawArraysInstanced 
+glDrawArraysInstancedBaseInstance 
+glDrawBuffer 
+glDrawBuffers 
+glDrawElements 
+glDrawElementsBaseVertex 
+glDrawElementsIndirect 
+glDrawElementsInstanced 
+glDrawElementsInstancedBaseInstance 
+glDrawElementsInstancedBaseVertex 
+glDrawElementsInstancedBaseVertexBaseInstance 
+glDrawPixels 
+glDrawRangeElements 
+glDrawRangeElementsBaseVertex 
+glDrawTransformFeedback 
+glDrawTransformFeedbackInstanced 
+glDrawTransformFeedbackStream 
+glDrawTransformFeedbackStreamInstanced 
+glEdgeFlag 
+glEdgeFlagPointer 
+glEdgeFlagv 
+glEnable 
+glEnableClientState 
+glEnableVertexArrayAttrib 
+glEnableVertexAttribArray 
+glEnablei 
+glEnd 
+glEndConditionalRender 
+glEndList 
+glEndQuery 
+glEndQueryIndexed 
+glEndTransformFeedback 
+glEvalCoord1d 
+glEvalCoord1dv 
+glEvalCoord1f 
+glEvalCoord1fv 
+glEvalCoord2d 
+glEvalCoord2dv 
+glEvalCoord2f 
+glEvalCoord2fv 
+glEvalMesh1 
+glEvalMesh2 
+glEvalPoint1 
+glEvalPoint2 
+glFeedbackBuffer 
+glFenceSync 
+glFinish 
+glFlush 
+glFlushMappedBufferRange 
+glFlushMappedNamedBufferRange 
+glFogCoordPointer 
+glFogCoordd 
+glFogCoorddv 
+glFogCoordf 
+glFogCoordfv 
+glFogf 
+glFogfv 
+glFogi 
+glFogiv 
+glFramebufferParameteri 
+glFramebufferRenderbuffer 
+glFramebufferTexture 
+glFramebufferTexture1D 
+glFramebufferTexture2D 
+glFramebufferTexture3D 
+glFramebufferTextureLayer 
+glFrontFace 
+glFrustum 
+glGenBuffers 
+glGenFramebuffers 
+glGenLists 
+glGenProgramPipelines 
+glGenQueries 
+glGenRenderbuffers 
+glGenSamplers 
+glGenTextures 
+glGenTransformFeedbacks 
+glGenVertexArrays 
+glGenerateMipmap 
+glGenerateTextureMipmap 
+glGetActiveAtomicCounterBufferiv 
+glGetActiveAttrib 
+glGetActiveSubroutineName 
+glGetActiveSubroutineUniformName 
+glGetActiveSubroutineUniformiv 
+glGetActiveUniform 
+glGetActiveUniformBlockName 
+glGetActiveUniformBlockiv 
+glGetActiveUniformName 
+glGetActiveUniformsiv 
+glGetAttachedShaders 
+glGetAttribLocation 
+glGetBooleani_v 
+glGetBooleanv 
+glGetBufferParameteri64v 
+glGetBufferParameteriv 
+glGetBufferPointerv 
+glGetBufferSubData 
+glGetClipPlane 
+glGetCompressedTexImage 
+glGetCompressedTextureImage 
+glGetCompressedTextureSubImage 
+glGetDebugMessageLog 
+glGetDoublei_v 
+glGetDoublev 
+glGetError 
+glGetFloati_v 
+glGetFloatv 
+glGetFragDataIndex 
+glGetFragDataLocation 
+glGetFramebufferAttachmentParameteriv 
+glGetFramebufferParameteriv 
+glGetGraphicsResetStatus 
+glGetInteger64i_v 
+glGetInteger64v 
+glGetIntegeri_v 
+glGetIntegerv 
+glGetInternalformati64v 
+glGetInternalformativ 
+glGetLightfv 
+glGetLightiv 
+glGetMapdv 
+glGetMapfv 
+glGetMapiv 
+glGetMaterialfv 
+glGetMaterialiv 
+glGetMultisamplefv 
+glGetNamedBufferParameteri64v 
+glGetNamedBufferParameteriv 
+glGetNamedBufferPointerv 
+glGetNamedBufferSubData 
+glGetNamedFramebufferAttachmentParameteriv 
+glGetNamedFramebufferParameteriv 
+glGetNamedRenderbufferParameteriv 
+glGetObjectLabel 
+glGetObjectPtrLabel 
+glGetPixelMapfv 
+glGetPixelMapuiv 
+glGetPixelMapusv 
+glGetPointerv 
+glGetPolygonStipple 
+glGetProgramBinary 
+glGetProgramInfoLog 
+glGetProgramInterfaceiv 
+glGetProgramPipelineInfoLog 
+glGetProgramPipelineiv 
+glGetProgramResourceIndex 
+glGetProgramResourceLocation 
+glGetProgramResourceLocationIndex 
+glGetProgramResourceName 
+glGetProgramResourceiv 
+glGetProgramStageiv 
+glGetProgramiv 
+glGetQueryBufferObjecti64v 
+glGetQueryBufferObjectiv 
+glGetQueryBufferObjectui64v 
+glGetQueryBufferObjectuiv 
+glGetQueryIndexediv 
+glGetQueryObjecti64v 
+glGetQueryObjectiv 
+glGetQueryObjectui64v 
+glGetQueryObjectuiv 
+glGetQueryiv 
+glGetRenderbufferParameteriv 
+glGetSamplerParameterIiv 
+glGetSamplerParameterIuiv 
+glGetSamplerParameterfv 
+glGetSamplerParameteriv 
+glGetShaderInfoLog 
+glGetShaderPrecisionFormat 
+glGetShaderSource 
+glGetShaderiv 
+glGetString 
+glGetStringi 
+glGetSubroutineIndex 
+glGetSubroutineUniformLocation 
+glGetSynciv 
+glGetTexEnvfv 
+glGetTexEnviv 
+glGetTexGendv 
+glGetTexGenfv 
+glGetTexGeniv 
+glGetTexImage 
+glGetTexLevelParameterfv 
+glGetTexLevelParameteriv 
+glGetTexParameterIiv 
+glGetTexParameterIuiv 
+glGetTexParameterfv 
+glGetTexParameteriv 
+glGetTextureImage 
+glGetTextureLevelParameterfv 
+glGetTextureLevelParameteriv 
+glGetTextureParameterIiv 
+glGetTextureParameterIuiv 
+glGetTextureParameterfv 
+glGetTextureParameteriv 
+glGetTextureSubImage 
+glGetTransformFeedbackVarying 
+glGetTransformFeedbacki64_v 
+glGetTransformFeedbacki_v 
+glGetTransformFeedbackiv 
+glGetUniformBlockIndex 
+glGetUniformIndices 
+glGetUniformLocation 
+glGetUniformSubroutineuiv 
+glGetUniformdv 
+glGetUniformfv 
+glGetUniformiv 
+glGetUniformuiv 
+glGetVertexArrayIndexed64iv 
+glGetVertexArrayIndexediv 
+glGetVertexArrayiv 
+glGetVertexAttribIiv 
+glGetVertexAttribIuiv 
+glGetVertexAttribLdv 
+glGetVertexAttribPointerv 
+glGetVertexAttribdv 
+glGetVertexAttribfv 
+glGetVertexAttribiv 
+glGetnColorTable 
+glGetnCompressedTexImage 
+glGetnConvolutionFilter 
+glGetnHistogram 
+glGetnMapdv 
+glGetnMapfv 
+glGetnMapiv 
+glGetnMinmax 
+glGetnPixelMapfv 
+glGetnPixelMapuiv 
+glGetnPixelMapusv 
+glGetnPolygonStipple 
+glGetnSeparableFilter 
+glGetnTexImage 
+glGetnUniformdv 
+glGetnUniformfv 
+glGetnUniformiv 
+glGetnUniformuiv 
+glHint 
+glIndexMask 
+glIndexPointer 
+glIndexd 
+glIndexdv 
+glIndexf 
+glIndexfv 
+glIndexi 
+glIndexiv 
+glIndexs 
+glIndexsv 
+glIndexub 
+glIndexubv 
+glInitNames 
+glInterleavedArrays 
+glInvalidateBufferData 
+glInvalidateBufferSubData 
+glInvalidateFramebuffer 
+glInvalidateNamedFramebufferData 
+glInvalidateNamedFramebufferSubData 
+glInvalidateSubFramebuffer 
+glInvalidateTexImage 
+glInvalidateTexSubImage 
+glIsBuffer 
+glIsEnabled 
+glIsEnabledi 
+glIsFramebuffer 
+glIsList 
+glIsProgram 
+glIsProgramPipeline 
+glIsQuery 
+glIsRenderbuffer 
+glIsSampler 
+glIsShader 
+glIsSync 
+glIsTexture 
+glIsTransformFeedback 
+glIsVertexArray 
+glLightModelf 
+glLightModelfv 
+glLightModeli 
+glLightModeliv 
+glLightf 
+glLightfv 
+glLighti 
+glLightiv 
+glLineStipple 
+glLineWidth 
+glLinkProgram 
+glListBase 
+glLoadIdentity 
+glLoadMatrixd 
+glLoadMatrixf 
+glLoadName 
+glLoadTransposeMatrixd 
+glLoadTransposeMatrixf 
+glLogicOp 
+glMap1d 
+glMap1f 
+glMap2d 
+glMap2f 
+glMapBuffer 
+glMapBufferRange 
+glMapGrid1d 
+glMapGrid1f 
+glMapGrid2d 
+glMapGrid2f 
+glMapNamedBuffer 
+glMapNamedBufferRange 
+glMaterialf 
+glMaterialfv 
+glMateriali 
+glMaterialiv 
+glMatrixMode 
+glMemoryBarrier 
+glMemoryBarrierByRegion 
+glMinSampleShading 
+glMultMatrixd 
+glMultMatrixf 
+glMultTransposeMatrixd 
+glMultTransposeMatrixf 
+glMultiDrawArrays 
+glMultiDrawArraysIndirect 
+glMultiDrawElements 
+glMultiDrawElementsBaseVertex 
+glMultiDrawElementsIndirect 
+glMultiTexCoord1d 
+glMultiTexCoord1dv 
+glMultiTexCoord1f 
+glMultiTexCoord1fv 
+glMultiTexCoord1i 
+glMultiTexCoord1iv 
+glMultiTexCoord1s 
+glMultiTexCoord1sv 
+glMultiTexCoord2d 
+glMultiTexCoord2dv 
+glMultiTexCoord2f 
+glMultiTexCoord2fv 
+glMultiTexCoord2i 
+glMultiTexCoord2iv 
+glMultiTexCoord2s 
+glMultiTexCoord2sv 
+glMultiTexCoord3d 
+glMultiTexCoord3dv 
+glMultiTexCoord3f 
+glMultiTexCoord3fv 
+glMultiTexCoord3i 
+glMultiTexCoord3iv 
+glMultiTexCoord3s 
+glMultiTexCoord3sv 
+glMultiTexCoord4d 
+glMultiTexCoord4dv 
+glMultiTexCoord4f 
+glMultiTexCoord4fv 
+glMultiTexCoord4i 
+glMultiTexCoord4iv 
+glMultiTexCoord4s 
+glMultiTexCoord4sv 
+glMultiTexCoordP1ui 
+glMultiTexCoordP1uiv 
+glMultiTexCoordP2ui 
+glMultiTexCoordP2uiv 
+glMultiTexCoordP3ui 
+glMultiTexCoordP3uiv 
+glMultiTexCoordP4ui 
+glMultiTexCoordP4uiv 
+glNamedBufferData 
+glNamedBufferStorage 
+glNamedBufferSubData 
+glNamedFramebufferDrawBuffer 
+glNamedFramebufferDrawBuffers 
+glNamedFramebufferParameteri 
+glNamedFramebufferReadBuffer 
+glNamedFramebufferRenderbuffer 
+glNamedFramebufferTexture 
+glNamedFramebufferTextureLayer 
+glNamedRenderbufferStorage 
+glNamedRenderbufferStorageMultisample 
+glNewList 
+glNormal3b 
+glNormal3bv 
+glNormal3d 
+glNormal3dv 
+glNormal3f 
+glNormal3fv 
+glNormal3i 
+glNormal3iv 
+glNormal3s 
+glNormal3sv 
+glNormalP3ui 
+glNormalP3uiv 
+glNormalPointer 
+glObjectLabel 
+glObjectPtrLabel 
+glOrtho 
+glPassThrough 
+glPatchParameterfv 
+glPatchParameteri 
+glPauseTransformFeedback 
+glPixelMapfv 
+glPixelMapuiv 
+glPixelMapusv 
+glPixelStoref 
+glPixelStorei 
+glPixelTransferf 
+glPixelTransferi 
+glPixelZoom 
+glPointParameterf 
+glPointParameterfv 
+glPointParameteri 
+glPointParameteriv 
+glPointSize 
+glPolygonMode 
+glPolygonOffset 
+glPolygonStipple 
+glPopAttrib 
+glPopClientAttrib 
+glPopDebugGroup 
+glPopMatrix 
+glPopName 
+glPrimitiveRestartIndex 
+glPrioritizeTextures 
+glProgramBinary 
+glProgramParameteri 
+glProgramUniform1d 
+glProgramUniform1dv 
+glProgramUniform1f 
+glProgramUniform1fv 
+glProgramUniform1i 
+glProgramUniform1iv 
+glProgramUniform1ui 
+glProgramUniform1uiv 
+glProgramUniform2d 
+glProgramUniform2dv 
+glProgramUniform2f 
+glProgramUniform2fv 
+glProgramUniform2i 
+glProgramUniform2iv 
+glProgramUniform2ui 
+glProgramUniform2uiv 
+glProgramUniform3d 
+glProgramUniform3dv 
+glProgramUniform3f 
+glProgramUniform3fv 
+glProgramUniform3i 
+glProgramUniform3iv 
+glProgramUniform3ui 
+glProgramUniform3uiv 
+glProgramUniform4d 
+glProgramUniform4dv 
+glProgramUniform4f 
+glProgramUniform4fv 
+glProgramUniform4i 
+glProgramUniform4iv 
+glProgramUniform4ui 
+glProgramUniform4uiv 
+glProgramUniformMatrix2dv 
+glProgramUniformMatrix2fv 
+glProgramUniformMatrix2x3dv 
+glProgramUniformMatrix2x3fv 
+glProgramUniformMatrix2x4dv 
+glProgramUniformMatrix2x4fv 
+glProgramUniformMatrix3dv 
+glProgramUniformMatrix3fv 
+glProgramUniformMatrix3x2dv 
+glProgramUniformMatrix3x2fv 
+glProgramUniformMatrix3x4dv 
+glProgramUniformMatrix3x4fv 
+glProgramUniformMatrix4dv 
+glProgramUniformMatrix4fv 
+glProgramUniformMatrix4x2dv 
+glProgramUniformMatrix4x2fv 
+glProgramUniformMatrix4x3dv 
+glProgramUniformMatrix4x3fv 
+glProvokingVertex 
+glPushAttrib 
+glPushClientAttrib 
+glPushDebugGroup 
+glPushMatrix 
+glPushName 
+glQueryCounter 
+glRasterPos2d 
+glRasterPos2dv 
+glRasterPos2f 
+glRasterPos2fv 
+glRasterPos2i 
+glRasterPos2iv 
+glRasterPos2s 
+glRasterPos2sv 
+glRasterPos3d 
+glRasterPos3dv 
+glRasterPos3f 
+glRasterPos3fv 
+glRasterPos3i 
+glRasterPos3iv 
+glRasterPos3s 
+glRasterPos3sv 
+glRasterPos4d 
+glRasterPos4dv 
+glRasterPos4f 
+glRasterPos4fv 
+glRasterPos4i 
+glRasterPos4iv 
+glRasterPos4s 
+glRasterPos4sv 
+glReadBuffer 
+glReadPixels 
+glReadnPixels 
+glRectd 
+glRectdv 
+glRectf 
+glRectfv 
+glRecti 
+glRectiv 
+glRects 
+glRectsv 
+glReleaseShaderCompiler 
+glRenderMode 
+glRenderbufferStorage 
+glRenderbufferStorageMultisample 
+glResumeTransformFeedback 
+glRotated 
+glRotatef 
+glSampleCoverage 
+glSampleMaski 
+glSamplerParameterIiv 
+glSamplerParameterIuiv 
+glSamplerParameterf 
+glSamplerParameterfv 
+glSamplerParameteri 
+glSamplerParameteriv 
+glScaled 
+glScalef 
+glScissor 
+glScissorArrayv 
+glScissorIndexed 
+glScissorIndexedv 
+glSecondaryColor3b 
+glSecondaryColor3bv 
+glSecondaryColor3d 
+glSecondaryColor3dv 
+glSecondaryColor3f 
+glSecondaryColor3fv 
+glSecondaryColor3i 
+glSecondaryColor3iv 
+glSecondaryColor3s 
+glSecondaryColor3sv 
+glSecondaryColor3ub 
+glSecondaryColor3ubv 
+glSecondaryColor3ui 
+glSecondaryColor3uiv 
+glSecondaryColor3us 
+glSecondaryColor3usv 
+glSecondaryColorP3ui 
+glSecondaryColorP3uiv 
+glSecondaryColorPointer 
+glSelectBuffer 
+glShadeModel 
+glShaderBinary 
+glShaderSource 
+glShaderStorageBlockBinding 
+glStencilFunc 
+glStencilFuncSeparate 
+glStencilMask 
+glStencilMaskSeparate 
+glStencilOp 
+glStencilOpSeparate 
+glTexBuffer 
+glTexBufferRange 
+glTexCoord1d 
+glTexCoord1dv 
+glTexCoord1f 
+glTexCoord1fv 
+glTexCoord1i 
+glTexCoord1iv 
+glTexCoord1s 
+glTexCoord1sv 
+glTexCoord2d 
+glTexCoord2dv 
+glTexCoord2f 
+glTexCoord2fv 
+glTexCoord2i 
+glTexCoord2iv 
+glTexCoord2s 
+glTexCoord2sv 
+glTexCoord3d 
+glTexCoord3dv 
+glTexCoord3f 
+glTexCoord3fv 
+glTexCoord3i 
+glTexCoord3iv 
+glTexCoord3s 
+glTexCoord3sv 
+glTexCoord4d 
+glTexCoord4dv 
+glTexCoord4f 
+glTexCoord4fv 
+glTexCoord4i 
+glTexCoord4iv 
+glTexCoord4s 
+glTexCoord4sv 
+glTexCoordP1ui 
+glTexCoordP1uiv 
+glTexCoordP2ui 
+glTexCoordP2uiv 
+glTexCoordP3ui 
+glTexCoordP3uiv 
+glTexCoordP4ui 
+glTexCoordP4uiv 
+glTexCoordPointer 
+glTexEnvf 
+glTexEnvfv 
+glTexEnvi 
+glTexEnviv 
+glTexGend 
+glTexGendv 
+glTexGenf 
+glTexGenfv 
+glTexGeni 
+glTexGeniv 
+glTexImage1D 
+glTexImage2D 
+glTexImage2DMultisample 
+glTexImage3D 
+glTexImage3DMultisample 
+glTexParameterIiv 
+glTexParameterIuiv 
+glTexParameterf 
+glTexParameterfv 
+glTexParameteri 
+glTexParameteriv 
+glTexStorage1D 
+glTexStorage2D 
+glTexStorage2DMultisample 
+glTexStorage3D 
+glTexStorage3DMultisample 
+glTexSubImage1D 
+glTexSubImage2D 
+glTexSubImage3D 
+glTextureBarrier 
+glTextureBuffer 
+glTextureBufferRange 
+glTextureParameterIiv 
+glTextureParameterIuiv 
+glTextureParameterf 
+glTextureParameterfv 
+glTextureParameteri 
+glTextureParameteriv 
+glTextureStorage1D 
+glTextureStorage2D 
+glTextureStorage2DMultisample 
+glTextureStorage3D 
+glTextureStorage3DMultisample 
+glTextureSubImage1D 
+glTextureSubImage2D 
+glTextureSubImage3D 
+glTextureView 
+glTransformFeedbackBufferBase 
+glTransformFeedbackBufferRange 
+glTransformFeedbackVaryings 
+glTranslated 
+glTranslatef 
+glUniform1d 
+glUniform1dv 
+glUniform1f 
+glUniform1fv 
+glUniform1i 
+glUniform1iv 
+glUniform1ui 
+glUniform1uiv 
+glUniform2d 
+glUniform2dv 
+glUniform2f 
+glUniform2fv 
+glUniform2i 
+glUniform2iv 
+glUniform2ui 
+glUniform2uiv 
+glUniform3d 
+glUniform3dv 
+glUniform3f 
+glUniform3fv 
+glUniform3i 
+glUniform3iv 
+glUniform3ui 
+glUniform3uiv 
+glUniform4d 
+glUniform4dv 
+glUniform4f 
+glUniform4fv 
+glUniform4i 
+glUniform4iv 
+glUniform4ui 
+glUniform4uiv 
+glUniformBlockBinding 
+glUniformMatrix2dv 
+glUniformMatrix2fv 
+glUniformMatrix2x3dv 
+glUniformMatrix2x3fv 
+glUniformMatrix2x4dv 
+glUniformMatrix2x4fv 
+glUniformMatrix3dv 
+glUniformMatrix3fv 
+glUniformMatrix3x2dv 
+glUniformMatrix3x2fv 
+glUniformMatrix3x4dv 
+glUniformMatrix3x4fv 
+glUniformMatrix4dv 
+glUniformMatrix4fv 
+glUniformMatrix4x2dv 
+glUniformMatrix4x2fv 
+glUniformMatrix4x3dv 
+glUniformMatrix4x3fv 
+glUniformSubroutinesuiv 
+glUnmapBuffer 
+glUnmapNamedBuffer 
+glUseProgram 
+glUseProgramStages 
+glValidateProgram 
+glValidateProgramPipeline 
+glVertex2d 
+glVertex2dv 
+glVertex2f 
+glVertex2fv 
+glVertex2i 
+glVertex2iv 
+glVertex2s 
+glVertex2sv 
+glVertex3d 
+glVertex3dv 
+glVertex3f 
+glVertex3fv 
+glVertex3i 
+glVertex3iv 
+glVertex3s 
+glVertex3sv 
+glVertex4d 
+glVertex4dv 
+glVertex4f 
+glVertex4fv 
+glVertex4i 
+glVertex4iv 
+glVertex4s 
+glVertex4sv 
+glVertexArrayAttribBinding 
+glVertexArrayAttribFormat 
+glVertexArrayAttribIFormat 
+glVertexArrayAttribLFormat 
+glVertexArrayBindingDivisor 
+glVertexArrayElementBuffer 
+glVertexArrayVertexBuffer 
+glVertexArrayVertexBuffers 
+glVertexAttrib1d 
+glVertexAttrib1dv 
+glVertexAttrib1f 
+glVertexAttrib1fv 
+glVertexAttrib1s 
+glVertexAttrib1sv 
+glVertexAttrib2d 
+glVertexAttrib2dv 
+glVertexAttrib2f 
+glVertexAttrib2fv 
+glVertexAttrib2s 
+glVertexAttrib2sv 
+glVertexAttrib3d 
+glVertexAttrib3dv 
+glVertexAttrib3f 
+glVertexAttrib3fv 
+glVertexAttrib3s 
+glVertexAttrib3sv 
+glVertexAttrib4Nbv 
+glVertexAttrib4Niv 
+glVertexAttrib4Nsv 
+glVertexAttrib4Nub 
+glVertexAttrib4Nubv 
+glVertexAttrib4Nuiv 
+glVertexAttrib4Nusv 
+glVertexAttrib4bv 
+glVertexAttrib4d 
+glVertexAttrib4dv 
+glVertexAttrib4f 
+glVertexAttrib4fv 
+glVertexAttrib4iv 
+glVertexAttrib4s 
+glVertexAttrib4sv 
+glVertexAttrib4ubv 
+glVertexAttrib4uiv 
+glVertexAttrib4usv 
+glVertexAttribBinding 
+glVertexAttribDivisor 
+glVertexAttribFormat 
+glVertexAttribI1i 
+glVertexAttribI1iv 
+glVertexAttribI1ui 
+glVertexAttribI1uiv 
+glVertexAttribI2i 
+glVertexAttribI2iv 
+glVertexAttribI2ui 
+glVertexAttribI2uiv 
+glVertexAttribI3i 
+glVertexAttribI3iv 
+glVertexAttribI3ui 
+glVertexAttribI3uiv 
+glVertexAttribI4bv 
+glVertexAttribI4i 
+glVertexAttribI4iv 
+glVertexAttribI4sv 
+glVertexAttribI4ubv 
+glVertexAttribI4ui 
+glVertexAttribI4uiv 
+glVertexAttribI4usv 
+glVertexAttribIFormat 
+glVertexAttribIPointer 
+glVertexAttribL1d 
+glVertexAttribL1dv 
+glVertexAttribL2d 
+glVertexAttribL2dv 
+glVertexAttribL3d 
+glVertexAttribL3dv 
+glVertexAttribL4d 
+glVertexAttribL4dv 
+glVertexAttribLFormat 
+glVertexAttribLPointer 
+glVertexAttribP1ui 
+glVertexAttribP1uiv 
+glVertexAttribP2ui 
+glVertexAttribP2uiv 
+glVertexAttribP3ui 
+glVertexAttribP3uiv 
+glVertexAttribP4ui 
+glVertexAttribP4uiv 
+glVertexAttribPointer 
+glVertexBindingDivisor 
+glVertexP2ui 
+glVertexP2uiv 
+glVertexP3ui 
+glVertexP3uiv 
+glVertexP4ui 
+glVertexP4uiv 
+glVertexPointer 
+glViewport 
+glViewportArrayv 
+glViewportIndexedf 
+glViewportIndexedfv 
+glWaitSync 
+glWindowPos2d 
+glWindowPos2dv 
+glWindowPos2f 
+glWindowPos2fv 
+glWindowPos2i 
+glWindowPos2iv 
+glWindowPos2s 
+glWindowPos2sv 
+glWindowPos3d 
+glWindowPos3dv 
+glWindowPos3f 
+glWindowPos3fv 
+glWindowPos3i 
+glWindowPos3iv 
+glWindowPos3s 
+glWindowPos3sv 
diff --git a/subprojects/libcapsule/examples/shim/libOpenGL.so.0.symbols.updated-for b/subprojects/libcapsule/examples/shim/libOpenGL.so.0.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libX11.so.6.excluded b/subprojects/libcapsule/examples/shim/libX11.so.6.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libX11.so.6.shared b/subprojects/libcapsule/examples/shim/libX11.so.6.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libX11.so.6.symbols b/subprojects/libcapsule/examples/shim/libX11.so.6.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..22a1469f11f15d709ea706e2bf2f2c253bc73ac2
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libX11.so.6.symbols
@@ -0,0 +1,1233 @@
+KeySymToUcs4 
+XActivateScreenSaver 
+XAddConnectionWatch 
+XAddExtension 
+XAddHost 
+XAddHosts 
+XAddPixel 
+XAddToExtensionList 
+XAddToSaveSet 
+XAllPlanes 
+XAllocClassHint 
+XAllocColor 
+XAllocColorCells 
+XAllocColorPlanes 
+XAllocIconSize 
+XAllocNamedColor 
+XAllocSizeHints 
+XAllocStandardColormap 
+XAllocWMHints 
+XAllowEvents 
+XAutoRepeatOff 
+XAutoRepeatOn 
+XBaseFontNameListOfFontSet 
+XBell 
+XBitmapBitOrder 
+XBitmapPad 
+XBitmapUnit 
+XBlackPixel 
+XBlackPixelOfScreen 
+XCellsOfScreen 
+XChangeActivePointerGrab 
+XChangeGC 
+XChangeKeyboardControl 
+XChangeKeyboardMapping 
+XChangePointerControl 
+XChangeProperty 
+XChangeSaveSet 
+XChangeWindowAttributes 
+XCheckIfEvent 
+XCheckMaskEvent 
+XCheckTypedEvent 
+XCheckTypedWindowEvent 
+XCheckWindowEvent 
+XCirculateSubwindows 
+XCirculateSubwindowsDown 
+XCirculateSubwindowsUp 
+XClearArea 
+XClearWindow 
+XClipBox 
+XCloseDisplay 
+XCloseIM 
+XCloseOM 
+XConfigureWindow 
+XConnectionNumber 
+XContextDependentDrawing 
+XContextualDrawing 
+XConvertCase 
+XConvertSelection 
+XCopyArea 
+XCopyColormapAndFree 
+XCopyGC 
+XCopyPlane 
+XCreateBitmapFromData 
+XCreateColormap 
+XCreateFontCursor 
+XCreateFontSet 
+XCreateGC 
+XCreateGlyphCursor 
+XCreateIC 
+XCreateImage 
+XCreateOC 
+XCreatePixmap 
+XCreatePixmapCursor 
+XCreatePixmapFromBitmapData 
+XCreateRegion 
+XCreateSimpleWindow 
+XCreateWindow 
+XDefaultColormap 
+XDefaultColormapOfScreen 
+XDefaultDepth 
+XDefaultDepthOfScreen 
+XDefaultGC 
+XDefaultGCOfScreen 
+XDefaultRootWindow 
+XDefaultScreen 
+XDefaultScreenOfDisplay 
+XDefaultString 
+XDefaultVisual 
+XDefaultVisualOfScreen 
+XDefineCursor 
+XDeleteContext 
+XDeleteModifiermapEntry 
+XDeleteProperty 
+XDestroyIC 
+XDestroyImage 
+XDestroyOC 
+XDestroyRegion 
+XDestroySubwindows 
+XDestroyWindow 
+XDirectionalDependentDrawing 
+XDisableAccessControl 
+XDisplayCells 
+XDisplayHeight 
+XDisplayHeightMM 
+XDisplayKeycodes 
+XDisplayMotionBufferSize 
+XDisplayName 
+XDisplayOfIM 
+XDisplayOfOM 
+XDisplayOfScreen 
+XDisplayPlanes 
+XDisplayString 
+XDisplayWidth 
+XDisplayWidthMM 
+XDoesBackingStore 
+XDoesSaveUnders 
+XDrawArc 
+XDrawArcs 
+XDrawImageString 
+XDrawImageString16 
+XDrawLine 
+XDrawLines 
+XDrawPoint 
+XDrawPoints 
+XDrawRectangle 
+XDrawRectangles 
+XDrawSegments 
+XDrawString 
+XDrawString16 
+XDrawText 
+XDrawText16 
+XEHeadOfExtensionList 
+XESetBeforeFlush 
+XESetCloseDisplay 
+XESetCopyEventCookie 
+XESetCopyGC 
+XESetCreateFont 
+XESetCreateGC 
+XESetError 
+XESetErrorString 
+XESetEventToWire 
+XESetFlushGC 
+XESetFreeFont 
+XESetFreeGC 
+XESetPrintErrorValues 
+XESetWireToError 
+XESetWireToEvent 
+XESetWireToEventCookie 
+XEmptyRegion 
+XEnableAccessControl 
+XEqualRegion 
+XEventMaskOfScreen 
+XEventsQueued 
+XExtendedMaxRequestSize 
+XExtentsOfFontSet 
+XFetchBuffer 
+XFetchBytes 
+XFetchName 
+XFillArc 
+XFillArcs 
+XFillPolygon 
+XFillRectangle 
+XFillRectangles 
+XFilterEvent 
+XFindContext 
+XFindOnExtensionList 
+XFlush 
+XFlushGC 
+XFontsOfFontSet 
+XForceScreenSaver 
+XFree 
+XFreeColormap 
+XFreeColors 
+XFreeCursor 
+XFreeEventData 
+XFreeExtensionList 
+XFreeFont 
+XFreeFontInfo 
+XFreeFontNames 
+XFreeFontPath 
+XFreeFontSet 
+XFreeGC 
+XFreeModifiermap 
+XFreePixmap 
+XFreeStringList 
+XGContextFromGC 
+XGeometry 
+XGetAtomName 
+XGetAtomNames 
+XGetClassHint 
+XGetCommand 
+XGetDefault 
+XGetErrorDatabaseText 
+XGetErrorText 
+XGetEventData 
+XGetFontPath 
+XGetFontProperty 
+XGetGCValues 
+XGetGeometry 
+XGetICValues 
+XGetIMValues 
+XGetIconName 
+XGetIconSizes 
+XGetImage 
+XGetInputFocus 
+XGetKeyboardControl 
+XGetKeyboardMapping 
+XGetModifierMapping 
+XGetMotionEvents 
+XGetNormalHints 
+XGetOCValues 
+XGetOMValues 
+XGetPixel 
+XGetPointerControl 
+XGetPointerMapping 
+XGetRGBColormaps 
+XGetScreenSaver 
+XGetSelectionOwner 
+XGetSizeHints 
+XGetStandardColormap 
+XGetSubImage 
+XGetTextProperty 
+XGetTransientForHint 
+XGetVisualInfo 
+XGetWMClientMachine 
+XGetWMColormapWindows 
+XGetWMHints 
+XGetWMIconName 
+XGetWMName 
+XGetWMNormalHints 
+XGetWMProtocols 
+XGetWMSizeHints 
+XGetWindowAttributes 
+XGetWindowProperty 
+XGetZoomHints 
+XGrabButton 
+XGrabKey 
+XGrabKeyboard 
+XGrabPointer 
+XGrabServer 
+XHeightMMOfScreen 
+XHeightOfScreen 
+XIMOfIC 
+XIconifyWindow 
+XIfEvent 
+XImageByteOrder 
+XInitExtension 
+XInitImage 
+XInitThreads 
+XInsertModifiermapEntry 
+XInstallColormap 
+XInternAtom 
+XInternAtoms 
+XInternalConnectionNumbers 
+XIntersectRegion 
+XKeycodeToKeysym 
+XKeysymToKeycode 
+XKeysymToString 
+XKillClient 
+XLastKnownRequestProcessed 
+XListDepths 
+XListExtensions 
+XListFonts 
+XListFontsWithInfo 
+XListHosts 
+XListInstalledColormaps 
+XListPixmapFormats 
+XListProperties 
+XLoadFont 
+XLoadQueryFont 
+XLocaleOfFontSet 
+XLocaleOfIM 
+XLocaleOfOM 
+XLockDisplay 
+XLookupColor 
+XLookupKeysym 
+XLookupString 
+XLowerWindow 
+XMapRaised 
+XMapSubwindows 
+XMapWindow 
+XMaskEvent 
+XMatchVisualInfo 
+XMaxCmapsOfScreen 
+XMaxRequestSize 
+XMinCmapsOfScreen 
+XMoveResizeWindow 
+XMoveWindow 
+XNewModifiermap 
+XNextEvent 
+XNextRequest 
+XNoOp 
+XOMOfOC 
+XOffsetRegion 
+XOpenDisplay 
+XOpenIM 
+XOpenOM 
+XParseColor 
+XParseGeometry 
+XPeekEvent 
+XPeekIfEvent 
+XPending 
+XPlanesOfScreen 
+XPointInRegion 
+XPolygonRegion 
+XProcessInternalConnection 
+XProtocolRevision 
+XProtocolVersion 
+XPutBackEvent 
+XPutImage 
+XPutPixel 
+XQLength 
+XQueryBestCursor 
+XQueryBestSize 
+XQueryBestStipple 
+XQueryBestTile 
+XQueryColor 
+XQueryColors 
+XQueryExtension 
+XQueryFont 
+XQueryKeymap 
+XQueryPointer 
+XQueryTextExtents 
+XQueryTextExtents16 
+XQueryTree 
+XRaiseWindow 
+XReadBitmapFile 
+XReadBitmapFileData 
+XRebindKeysym 
+XRecolorCursor 
+XReconfigureWMWindow 
+XRectInRegion 
+XRefreshKeyboardMapping 
+XRegisterIMInstantiateCallback 
+XRemoveConnectionWatch 
+XRemoveFromSaveSet 
+XRemoveHost 
+XRemoveHosts 
+XReparentWindow 
+XResetScreenSaver 
+XResizeWindow 
+XResourceManagerString 
+XRestackWindows 
+XRootWindow 
+XRootWindowOfScreen 
+XRotateBuffers 
+XRotateWindowProperties 
+XSaveContext 
+XScreenCount 
+XScreenNumberOfScreen 
+XScreenOfDisplay 
+XScreenResourceString 
+XSelectInput 
+XSendEvent 
+XServerVendor 
+XSetAccessControl 
+XSetAfterFunction 
+XSetArcMode 
+XSetAuthorization 
+XSetBackground 
+XSetClassHint 
+XSetClipMask 
+XSetClipOrigin 
+XSetClipRectangles 
+XSetCloseDownMode 
+XSetCommand 
+XSetDashes 
+XSetErrorHandler 
+XSetFillRule 
+XSetFillStyle 
+XSetFont 
+XSetFontPath 
+XSetForeground 
+XSetFunction 
+XSetGraphicsExposures 
+XSetICFocus 
+XSetICValues 
+XSetIMValues 
+XSetIOErrorHandler 
+XSetIconName 
+XSetIconSizes 
+XSetInputFocus 
+XSetLineAttributes 
+XSetLocaleModifiers 
+XSetModifierMapping 
+XSetNormalHints 
+XSetOCValues 
+XSetOMValues 
+XSetPlaneMask 
+XSetPointerMapping 
+XSetRGBColormaps 
+XSetRegion 
+XSetScreenSaver 
+XSetSelectionOwner 
+XSetSizeHints 
+XSetStandardColormap 
+XSetStandardProperties 
+XSetState 
+XSetStipple 
+XSetSubwindowMode 
+XSetTSOrigin 
+XSetTextProperty 
+XSetTile 
+XSetTransientForHint 
+XSetWMClientMachine 
+XSetWMColormapWindows 
+XSetWMHints 
+XSetWMIconName 
+XSetWMName 
+XSetWMNormalHints 
+XSetWMProperties 
+XSetWMProtocols 
+XSetWMSizeHints 
+XSetWindowBackground 
+XSetWindowBackgroundPixmap 
+XSetWindowBorder 
+XSetWindowBorderPixmap 
+XSetWindowBorderWidth 
+XSetWindowColormap 
+XSetZoomHints 
+XShrinkRegion 
+XStoreBuffer 
+XStoreBytes 
+XStoreColor 
+XStoreColors 
+XStoreName 
+XStoreNamedColor 
+XStringListToTextProperty 
+XStringToKeysym 
+XSubImage 
+XSubtractRegion 
+XSupportsLocale 
+XSync 
+XSynchronize 
+XTextExtents 
+XTextExtents16 
+XTextPropertyToStringList 
+XTextWidth 
+XTextWidth16 
+XTranslateCoordinates 
+XUndefineCursor 
+XUngrabButton 
+XUngrabKey 
+XUngrabKeyboard 
+XUngrabPointer 
+XUngrabServer 
+XUninstallColormap 
+XUnionRectWithRegion 
+XUnionRegion 
+XUnloadFont 
+XUnlockDisplay 
+XUnmapSubwindows 
+XUnmapWindow 
+XUnregisterIMInstantiateCallback 
+XUnsetICFocus 
+XVaCreateNestedList 
+XVendorRelease 
+XVisualIDFromVisual 
+XWMGeometry 
+XWarpPointer 
+XWhitePixel 
+XWhitePixelOfScreen 
+XWidthMMOfScreen 
+XWidthOfScreen 
+XWindowEvent 
+XWithdrawWindow 
+XWriteBitmapFile 
+XXorRegion 
+XcmsAddColorSpace 
+XcmsAddFunctionSet 
+XcmsAllocColor 
+XcmsAllocNamedColor 
+XcmsCCCOfColormap 
+XcmsCIELabClipL 
+XcmsCIELabClipLab 
+XcmsCIELabClipab 
+XcmsCIELabColorSpace 
+XcmsCIELabQueryMaxC 
+XcmsCIELabQueryMaxL 
+XcmsCIELabQueryMaxLC 
+XcmsCIELabQueryMinL 
+XcmsCIELabToCIEXYZ 
+XcmsCIELabWhiteShiftColors 
+XcmsCIELuvClipL 
+XcmsCIELuvClipLuv 
+XcmsCIELuvClipuv 
+XcmsCIELuvColorSpace 
+XcmsCIELuvQueryMaxC 
+XcmsCIELuvQueryMaxL 
+XcmsCIELuvQueryMaxLC 
+XcmsCIELuvQueryMinL 
+XcmsCIELuvToCIEuvY 
+XcmsCIELuvWhiteShiftColors 
+XcmsCIEXYZColorSpace 
+XcmsCIEXYZToCIELab 
+XcmsCIEXYZToCIEuvY 
+XcmsCIEXYZToCIExyY 
+XcmsCIEXYZToRGBi 
+XcmsCIEuvYColorSpace 
+XcmsCIEuvYToCIELuv 
+XcmsCIEuvYToCIEXYZ 
+XcmsCIEuvYToTekHVC 
+XcmsCIExyYColorSpace 
+XcmsCIExyYToCIEXYZ 
+XcmsClientWhitePointOfCCC 
+XcmsConvertColors 
+XcmsCreateCCC 
+XcmsDefaultCCC 
+XcmsDisplayOfCCC 
+XcmsFormatOfPrefix 
+XcmsFreeCCC 
+XcmsLinearRGBFunctionSet 
+XcmsLookupColor 
+XcmsPrefixOfFormat 
+XcmsQueryBlack 
+XcmsQueryBlue 
+XcmsQueryColor 
+XcmsQueryColors 
+XcmsQueryGreen 
+XcmsQueryRed 
+XcmsQueryWhite 
+XcmsRGBColorSpace 
+XcmsRGBToRGBi 
+XcmsRGBiColorSpace 
+XcmsRGBiToCIEXYZ 
+XcmsRGBiToRGB 
+XcmsScreenNumberOfCCC 
+XcmsScreenWhitePointOfCCC 
+XcmsSetCCCOfColormap 
+XcmsSetCompressionProc 
+XcmsSetWhiteAdjustProc 
+XcmsSetWhitePoint 
+XcmsStoreColor 
+XcmsStoreColors 
+XcmsTekHVCClipC 
+XcmsTekHVCClipV 
+XcmsTekHVCClipVC 
+XcmsTekHVCColorSpace 
+XcmsTekHVCQueryMaxC 
+XcmsTekHVCQueryMaxV 
+XcmsTekHVCQueryMaxVC 
+XcmsTekHVCQueryMaxVSamples 
+XcmsTekHVCQueryMinV 
+XcmsTekHVCToCIEuvY 
+XcmsTekHVCWhiteShiftColors 
+XcmsUNDEFINEDColorSpace 
+XcmsVisualOfCCC 
+XkbAddDeviceLedInfo 
+XkbAddGeomColor 
+XkbAddGeomDoodad 
+XkbAddGeomKey 
+XkbAddGeomKeyAlias 
+XkbAddGeomOutline 
+XkbAddGeomOverlay 
+XkbAddGeomOverlayKey 
+XkbAddGeomOverlayRow 
+XkbAddGeomProperty 
+XkbAddGeomRow 
+XkbAddGeomSection 
+XkbAddGeomShape 
+XkbAddKeyType 
+XkbAllocClientMap 
+XkbAllocCompatMap 
+XkbAllocControls 
+XkbAllocDeviceInfo 
+XkbAllocGeomColors 
+XkbAllocGeomDoodads 
+XkbAllocGeomKeyAliases 
+XkbAllocGeomKeys 
+XkbAllocGeomOutlines 
+XkbAllocGeomOverlayKeys 
+XkbAllocGeomOverlayRows 
+XkbAllocGeomOverlays 
+XkbAllocGeomPoints 
+XkbAllocGeomProps 
+XkbAllocGeomRows 
+XkbAllocGeomSectionDoodads 
+XkbAllocGeomSections 
+XkbAllocGeomShapes 
+XkbAllocGeometry 
+XkbAllocIndicatorMaps 
+XkbAllocKeyboard 
+XkbAllocNames 
+XkbAllocServerMap 
+XkbApplyCompatMapToKey 
+XkbApplyVirtualModChanges 
+XkbBell 
+XkbBellEvent 
+XkbChangeDeviceInfo 
+XkbChangeEnabledControls 
+XkbChangeKeycodeRange 
+XkbChangeMap 
+XkbChangeNames 
+XkbChangeTypesOfKey 
+XkbComputeEffectiveMap 
+XkbComputeRowBounds 
+XkbComputeSectionBounds 
+XkbComputeShapeBounds 
+XkbComputeShapeTop 
+XkbCopyKeyType 
+XkbCopyKeyTypes 
+XkbDeviceBell 
+XkbDeviceBellEvent 
+XkbFindOverlayForKey 
+XkbForceBell 
+XkbForceDeviceBell 
+XkbFreeClientMap 
+XkbFreeCompatMap 
+XkbFreeComponentList 
+XkbFreeControls 
+XkbFreeDeviceInfo 
+XkbFreeGeomColors 
+XkbFreeGeomDoodads 
+XkbFreeGeomKeyAliases 
+XkbFreeGeomKeys 
+XkbFreeGeomOutlines 
+XkbFreeGeomOverlayKeys 
+XkbFreeGeomOverlayRows 
+XkbFreeGeomOverlays 
+XkbFreeGeomPoints 
+XkbFreeGeomProperties 
+XkbFreeGeomRows 
+XkbFreeGeomSections 
+XkbFreeGeomShapes 
+XkbFreeGeometry 
+XkbFreeIndicatorMaps 
+XkbFreeKeyboard 
+XkbFreeNames 
+XkbFreeServerMap 
+XkbGetAutoRepeatRate 
+XkbGetAutoResetControls 
+XkbGetCompatMap 
+XkbGetControls 
+XkbGetDetectableAutoRepeat 
+XkbGetDeviceButtonActions 
+XkbGetDeviceInfo 
+XkbGetDeviceInfoChanges 
+XkbGetDeviceLedInfo 
+XkbGetGeometry 
+XkbGetIndicatorMap 
+XkbGetIndicatorState 
+XkbGetKeyActions 
+XkbGetKeyBehaviors 
+XkbGetKeyExplicitComponents 
+XkbGetKeyModifierMap 
+XkbGetKeySyms 
+XkbGetKeyTypes 
+XkbGetKeyVirtualModMap 
+XkbGetKeyboard 
+XkbGetKeyboardByName 
+XkbGetMap 
+XkbGetMapChanges 
+XkbGetNamedDeviceIndicator 
+XkbGetNamedGeometry 
+XkbGetNamedIndicator 
+XkbGetNames 
+XkbGetPerClientControls 
+XkbGetState 
+XkbGetUpdatedMap 
+XkbGetVirtualMods 
+XkbGetXlibControls 
+XkbIgnoreExtension 
+XkbInitCanonicalKeyTypes 
+XkbKeyTypesForCoreSymbols 
+XkbKeycodeToKeysym 
+XkbKeysymToModifiers 
+XkbLatchGroup 
+XkbLatchModifiers 
+XkbLibraryVersion 
+XkbListComponents 
+XkbLockGroup 
+XkbLockModifiers 
+XkbLookupKeyBinding 
+XkbLookupKeySym 
+XkbNoteControlsChanges 
+XkbNoteDeviceChanges 
+XkbNoteMapChanges 
+XkbNoteNameChanges 
+XkbOpenDisplay 
+XkbQueryExtension 
+XkbRefreshKeyboardMapping 
+XkbResizeDeviceButtonActions 
+XkbResizeKeyActions 
+XkbResizeKeySyms 
+XkbResizeKeyType 
+XkbSelectEventDetails 
+XkbSelectEvents 
+XkbSetAtomFuncs 
+XkbSetAutoRepeatRate 
+XkbSetAutoResetControls 
+XkbSetCompatMap 
+XkbSetControls 
+XkbSetDebuggingFlags 
+XkbSetDetectableAutoRepeat 
+XkbSetDeviceButtonActions 
+XkbSetDeviceInfo 
+XkbSetDeviceLedInfo 
+XkbSetGeometry 
+XkbSetIgnoreLockMods 
+XkbSetIndicatorMap 
+XkbSetMap 
+XkbSetNamedDeviceIndicator 
+XkbSetNamedIndicator 
+XkbSetNames 
+XkbSetPerClientControls 
+XkbSetServerInternalMods 
+XkbSetXlibControls 
+XkbToControl 
+XkbTranslateKey 
+XkbTranslateKeyCode 
+XkbTranslateKeySym 
+XkbUpdateActionVirtualMods 
+XkbUpdateKeyTypeVirtualMods 
+XkbUpdateMapFromCore 
+XkbUseExtension 
+XkbVirtualModsToReal 
+XkbXlibControlsImplemented 
+XmbDrawImageString 
+XmbDrawString 
+XmbDrawText 
+XmbLookupString 
+XmbResetIC 
+XmbSetWMProperties 
+XmbTextEscapement 
+XmbTextExtents 
+XmbTextListToTextProperty 
+XmbTextPerCharExtents 
+XmbTextPropertyToTextList 
+Xpermalloc 
+XrmCombineDatabase 
+XrmCombineFileDatabase 
+XrmDestroyDatabase 
+XrmEnumerateDatabase 
+XrmGetDatabase 
+XrmGetFileDatabase 
+XrmGetResource 
+XrmGetStringDatabase 
+XrmInitialize 
+XrmLocaleOfDatabase 
+XrmMergeDatabases 
+XrmParseCommand 
+XrmPermStringToQuark 
+XrmPutFileDatabase 
+XrmPutLineResource 
+XrmPutResource 
+XrmPutStringResource 
+XrmQGetResource 
+XrmQGetSearchList 
+XrmQGetSearchResource 
+XrmQPutResource 
+XrmQPutStringResource 
+XrmQuarkToString 
+XrmSetDatabase 
+XrmStringToBindingQuarkList 
+XrmStringToQuark 
+XrmStringToQuarkList 
+XrmUniqueQuark 
+Xutf8DrawImageString 
+Xutf8DrawString 
+Xutf8DrawText 
+Xutf8LookupString 
+Xutf8ResetIC 
+Xutf8SetWMProperties 
+Xutf8TextEscapement 
+Xutf8TextExtents 
+Xutf8TextListToTextProperty 
+Xutf8TextPerCharExtents 
+Xutf8TextPropertyToTextList 
+XwcDrawImageString 
+XwcDrawString 
+XwcDrawText 
+XwcFreeStringList 
+XwcLookupString 
+XwcResetIC 
+XwcTextEscapement 
+XwcTextExtents 
+XwcTextListToTextProperty 
+XwcTextPerCharExtents 
+XwcTextPropertyToTextList 
+_Utf8GetConvByName 
+_XAllocID 
+_XAllocIDs 
+_XAllocScratch 
+_XAllocTemp 
+_XAsyncErrorHandler 
+_XCloseLC 
+_XColor_to_XcmsRGB 
+_XConnectXCB 
+_XCopyEventCookie 
+_XCopyToArg 
+_XCreateMutex_fn 
+_XData32 
+_XDefaultError 
+_XDefaultIOError 
+_XDefaultOpenIM 
+_XDefaultOpenOM 
+_XDefaultWireError 
+_XDeq 
+_XDeqAsyncHandler 
+_XEatData 
+_XEatDataWords 
+_XEnq 
+_XError 
+_XErrorFunction 
+_XEventToWire 
+_XEventsQueued 
+_XF86BigfontFreeFontMetrics 
+_XF86LoadQueryLocaleFont 
+_XFetchEventCookie 
+_XFlush 
+_XFlushGCCache 
+_XFreeAtomTable 
+_XFreeDisplayLock_fn 
+_XFreeDisplayStructure 
+_XFreeEventCookies 
+_XFreeExtData 
+_XFreeMutex_fn 
+_XFreeTemp 
+_XFreeX11XCBStructure 
+_XGetAsyncData 
+_XGetAsyncReply 
+_XGetBitsPerPixel 
+_XGetHostname 
+_XGetLCValues 
+_XGetRequest 
+_XGetScanlinePad 
+_XGetWindowAttributes 
+_XHeadOfDisplayList 
+_XIMCompileResourceList 
+_XIOError 
+_XIOErrorFunction 
+_XInitDisplayLock_fn 
+_XInitIM 
+_XInitImageFuncPtrs 
+_XInitKeysymDB 
+_XInitOM 
+_XIsEventCookie 
+_XKeyInitialize 
+_XKeycodeToKeysym 
+_XKeysymToKeycode 
+_XKeysymToModifiers 
+_XLockMutex_fn 
+_XLookupKeysym 
+_XLookupString 
+_XNoticeCreateBitmap 
+_XNoticePutBitmap 
+_XOpenLC 
+_XParseBaseFontNameList 
+_XPollfdCacheAdd 
+_XPollfdCacheDel 
+_XPollfdCacheInit 
+_XProcessInternalConnection 
+_XProcessWindowAttributes 
+_XPutBackEvent 
+_XRead 
+_XRead32 
+_XReadEvents 
+_XReadPad 
+_XRefreshKeyboardMapping 
+_XRegisterFilterByMask 
+_XRegisterFilterByType 
+_XRegisterInternalConnection 
+_XReply 
+_XReverse_Bytes 
+_XScreenOfWindow 
+_XSend 
+_XSetClipRectangles 
+_XSetImage 
+_XSetLastRequestRead 
+_XStoreEventCookie 
+_XTextHeight 
+_XTextHeight16 
+_XTranslateKey 
+_XTranslateKeySym 
+_XTryShapeBitmapCursor 
+_XUnknownCopyEventCookie 
+_XUnknownNativeEvent 
+_XUnknownWireEvent 
+_XUnknownWireEventCookie 
+_XUnlockMutex_fn 
+_XUnregisterFilter 
+_XUnregisterInternalConnection 
+_XUnresolveColor 
+_XUpdateAtomCache 
+_XUpdateGCCache 
+_XVIDtoVisual 
+_XWireToEvent 
+_XcmsAddCmapRec 
+_XcmsArcTangent 
+_XcmsCIELabQueryMaxLCRGB 
+_XcmsCIELab_prefix 
+_XcmsCIELuvQueryMaxLCRGB 
+_XcmsCIELuv_prefix 
+_XcmsCIEXYZ_ValidSpec 
+_XcmsCIEXYZ_prefix 
+_XcmsCIEuvY_ValidSpec 
+_XcmsCIEuvY_prefix 
+_XcmsCIExyY_prefix 
+_XcmsConvertColorsWithWhitePt 
+_XcmsCopyCmapRecAndFree 
+_XcmsCopyISOLatin1Lowered 
+_XcmsCopyPointerArray 
+_XcmsCosine 
+_XcmsCubeRoot 
+_XcmsDDColorSpaces 
+_XcmsDDColorSpacesInit 
+_XcmsDDConvertColors 
+_XcmsDIColorSpaces 
+_XcmsDIColorSpacesInit 
+_XcmsDIConvertColors 
+_XcmsDeleteCmapRec 
+_XcmsEqualWhitePts 
+_XcmsFreeIntensityMaps 
+_XcmsFreePointerArray 
+_XcmsGetElement 
+_XcmsGetIntensityMap 
+_XcmsGetProperty 
+_XcmsInitDefaultCCCs 
+_XcmsInitScrnInfo 
+_XcmsLRGB_InitScrnDefault 
+_XcmsPushPointerArray 
+_XcmsRGB_prefix 
+_XcmsRGB_to_XColor 
+_XcmsRGBi_prefix 
+_XcmsRegColorSpaces 
+_XcmsRegFormatOfPrefix 
+_XcmsResolveColor 
+_XcmsResolveColorString 
+_XcmsSCCFuncSets 
+_XcmsSCCFuncSetsInit 
+_XcmsSetGetColor 
+_XcmsSetGetColors 
+_XcmsSine 
+_XcmsSquareRoot 
+_XcmsTekHVCQueryMaxVCRGB 
+_XcmsTekHVC_CheckModify 
+_XcmsTekHVC_prefix 
+_XcmsUnresolveColor 
+_Xdebug 
+_Xevent_to_mask 
+_Xglobal_lock 
+_Xi18n_lock 
+_XimCbDispatch 
+_XimCheckCreateICValues 
+_XimCheckDataSize 
+_XimCheckICMode 
+_XimCheckIMMode 
+_XimCheckIfLocalProcessing 
+_XimCheckIfThaiProcessing 
+_XimCheckLocalInputStyle 
+_XimCommitCallback 
+_XimConnect 
+_XimDecodeICATTRIBUTE 
+_XimDecodeIMATTRIBUTE 
+_XimDecodeLocalICAttr 
+_XimDecodeLocalIMAttr 
+_XimDestroyIMStructureList 
+_XimDispatchInit 
+_XimEncodeICATTRIBUTE 
+_XimEncodeIMATTRIBUTE 
+_XimEncodeLocalICAttr 
+_XimEncodeLocalIMAttr 
+_XimError 
+_XimErrorCallback 
+_XimExtension 
+_XimFilterWaitEvent 
+_XimFlush 
+_XimForwardEvent 
+_XimForwardEventCallback 
+_XimFreeCommitInfo 
+_XimFreeProtoIntrCallback 
+_XimFreeTransIntrCallback 
+_XimGetAttributeID 
+_XimGetCharCode 
+_XimGetCurrentICValues 
+_XimGetCurrentIMValues 
+_XimGetICValueData 
+_XimGetIMValueData 
+_XimGetLocaleCode 
+_XimGetMyEndian 
+_XimGetResourceListRec 
+_XimGetResourceListRecByQuark 
+_XimGetWindowEventmask 
+_XimICOfXICID 
+_XimImSportRec 
+_XimInitialResourceInfo 
+_XimLcctstombs 
+_XimLcctstoutf8 
+_XimLcctstowcs 
+_XimLocalCreateIC 
+_XimLocalFilter 
+_XimLocalGetICValues 
+_XimLocalGetIMValues 
+_XimLocalIMFree 
+_XimLocalMbLookupString 
+_XimLocalOpenIM 
+_XimLocalSetICValues 
+_XimLocalSetIMValues 
+_XimLocalUtf8LookupString 
+_XimLocalWcLookupString 
+_XimLookupMBText 
+_XimLookupUTF8Text 
+_XimLookupWCText 
+_XimMakeICAttrIDList 
+_XimMakeIMAttrIDList 
+_XimOpenIM 
+_XimParseStringFile 
+_XimProcError 
+_XimProcSyncReply 
+_XimProtoCreateIC 
+_XimProtoEventToWire 
+_XimProtoIMFree 
+_XimProtoMbLookupString 
+_XimProtoOpenIM 
+_XimProtoUtf8LookupString 
+_XimProtoWcLookupString 
+_XimProtoWireToEvent 
+_XimRead 
+_XimRegProtoIntrCallback 
+_XimRegisterDispatcher 
+_XimRegisterFilter 
+_XimRegisterIMInstantiateCallback 
+_XimRegisterServerFilter 
+_XimRegisterTriggerKeysCallback 
+_XimReregisterFilter 
+_XimResetIMInstantiateCallback 
+_XimRespSyncReply 
+_XimServerDestroy 
+_XimSetCurrentICValues 
+_XimSetCurrentIMValues 
+_XimSetEventMaskCallback 
+_XimSetHeader 
+_XimSetICDefaults 
+_XimSetICMode 
+_XimSetICResourceList 
+_XimSetICValueData 
+_XimSetIMMode 
+_XimSetIMResourceList 
+_XimSetIMValueData 
+_XimSetInnerICResourceList 
+_XimSetInnerIMResourceList 
+_XimSetLocalIMDefaults 
+_XimShutdown 
+_XimSync 
+_XimSyncCallback 
+_XimThaiCloseIM 
+_XimThaiCreateIC 
+_XimThaiFilter 
+_XimThaiIMFree 
+_XimThaiOpenIM 
+_XimTransCallDispatcher 
+_XimTransConf 
+_XimTransFilterWaitEvent 
+_XimTransFlush 
+_XimTransInternalConnection 
+_XimTransRead 
+_XimTransRegisterDispatcher 
+_XimTransWrite 
+_XimTransportRec 
+_XimTriggerNotify 
+_XimUnRegisterIMInstantiateCallback 
+_XimUnregisterFilter 
+_XimUnregisterServerFilter 
+_XimWrite 
+_XimXConf 
+_XimXTransBytesReadable 
+_XimXTransClose 
+_XimXTransCloseForCloning 
+_XimXTransConnect 
+_XimXTransDisconnect 
+_XimXTransFreeConnInfo 
+_XimXTransGetConnectionNumber 
+_XimXTransGetHostname 
+_XimXTransGetMyAddr 
+_XimXTransGetPeerAddr 
+_XimXTransIsLocal 
+_XimXTransOpenCLTSClient 
+_XimXTransOpenCOTSClient 
+_XimXTransRead 
+_XimXTransReadv 
+_XimXTransSetOption 
+_XimXTransSocketINET6Funcs 
+_XimXTransSocketINETFuncs 
+_XimXTransSocketLocalFuncs 
+_XimXTransSocketTCPFuncs 
+_XimXTransSocketUNIXFuncs 
+_XimXTransWrite 
+_XimXTransWritev 
+_Ximctstombs 
+_Ximctstoutf8 
+_Ximctstowcs 
+_XkbCopyFromReadBuffer 
+_XkbFreeReadBuffer 
+_XkbGetAtomNameFunc 
+_XkbGetCharset 
+_XkbGetConverters 
+_XkbGetReadBufferCountedString 
+_XkbGetReadBufferPtr 
+_XkbInitReadBuffer 
+_XkbInternAtomFunc 
+_XkbNoteCoreMapChanges 
+_XkbPeekAtReadBuffer 
+_XkbReadBufferCopy32 
+_XkbReadBufferCopyKeySyms 
+_XkbReadCopyData32 
+_XkbReadCopyKeySyms 
+_XkbReadGetCompatMapReply 
+_XkbReadGetGeometryReply 
+_XkbReadGetIndicatorMapReply 
+_XkbReadGetMapReply 
+_XkbReadGetNamesReply 
+_XkbReloadDpy 
+_XkbSkipReadBufferData 
+_XkbWriteCopyData32 
+_XkbWriteCopyKeySyms 
+_XkeyTable 
+_XlcAddCT 
+_XlcAddCharSet 
+_XlcAddGB18030LocaleConverters 
+_XlcAddLoader 
+_XlcAddUtf8Converters 
+_XlcAddUtf8LocaleConverters 
+_XlcCloseConverter 
+_XlcCompareISOLatin1 
+_XlcCompileResourceList 
+_XlcConvert 
+_XlcCopyFromArg 
+_XlcCopyToArg 
+_XlcCountVaList 
+_XlcCreateDefaultCharSet 
+_XlcCreateLC 
+_XlcCreateLocaleDataBase 
+_XlcCurrentLC 
+_XlcDbg_printValue 
+_XlcDeInitLoader 
+_XlcDefaultLoader 
+_XlcDefaultMapModifiers 
+_XlcDestroyLC 
+_XlcDestroyLocaleDataBase 
+_XlcFileName 
+_XlcGenericLoader 
+_XlcGenericMethods 
+_XlcGetCSValues 
+_XlcGetCharSet 
+_XlcGetCharSetWithSide 
+_XlcGetLocaleDataBase 
+_XlcGetResource 
+_XlcGetValues 
+_XlcInitCTInfo 
+_XlcInitLoader 
+_XlcLocaleDirName 
+_XlcLocaleLibDirName 
+_XlcMapOSLocaleName 
+_XlcNCompareISOLatin1 
+_XlcOpenConverter 
+_XlcParseCharSet 
+_XlcParse_scopemaps 
+_XlcPublicMethods 
+_XlcRemoveLoader 
+_XlcResetConverter 
+_XlcResolveI18NPath 
+_XlcResolveLocaleName 
+_XlcSetConverter 
+_XlcSetValues 
+_XlcUtf8Loader 
+_XlcVaToArgList 
+_XlcValidModSyntax 
+_Xlcmbstoutf8 
+_Xlcmbstowcs 
+_Xlcmbtowc 
+_Xlcwcstombs 
+_Xlcwctomb 
+_XmbDefaultDrawImageString 
+_XmbDefaultDrawString 
+_XmbDefaultTextEscapement 
+_XmbDefaultTextExtents 
+_XmbDefaultTextPerCharExtents 
+_XmbGenericDrawImageString 
+_XmbGenericDrawString 
+_XmbGenericTextEscapement 
+_XmbGenericTextExtents 
+_XmbGenericTextPerCharExtents 
+_XmbTextListToTextProperty 
+_XmbTextPropertyToTextList 
+_Xmblen 
+_Xmbstoutf8 
+_Xmbstowcs 
+_Xmbtowc 
+_XomConvert 
+_XomGenericDrawString 
+_XomGenericOpenOM 
+_XomGenericTextExtents 
+_XomGetFontDataFromFontSet 
+_XomInitConverter 
+_XrmDefaultInitParseInfo 
+_XrmInitParseInfo 
+_XrmInternalStringToQuark 
+_Xthread_self_fn 
+_Xutf8DefaultDrawImageString 
+_Xutf8DefaultDrawString 
+_Xutf8DefaultTextEscapement 
+_Xutf8DefaultTextExtents 
+_Xutf8DefaultTextPerCharExtents 
+_Xutf8GenericDrawImageString 
+_Xutf8GenericDrawString 
+_Xutf8GenericTextEscapement 
+_Xutf8GenericTextExtents 
+_Xutf8GenericTextPerCharExtents 
+_Xutf8TextListToTextProperty 
+_Xutf8TextPropertyToTextList 
+_XwcDefaultDrawImageString 
+_XwcDefaultDrawString 
+_XwcDefaultTextEscapement 
+_XwcDefaultTextExtents 
+_XwcDefaultTextPerCharExtents 
+_XwcFreeStringList 
+_XwcGenericDrawImageString 
+_XwcGenericDrawString 
+_XwcGenericTextEscapement 
+_XwcGenericTextExtents 
+_XwcGenericTextPerCharExtents 
+_XwcTextListToTextProperty 
+_XwcTextPropertyToTextList 
+_Xwcscmp 
+_Xwcscpy 
+_Xwcslen 
+_Xwcsncmp 
+_Xwcsncpy 
+_Xwcstombs 
+_Xwctomb 
+_qfree 
+read_EncodingInfo 
+xlocaledir 
diff --git a/subprojects/libcapsule/examples/shim/libX11.so.6.symbols.updated-for b/subprojects/libcapsule/examples/shim/libX11.so.6.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libXext.so.6.excluded b/subprojects/libcapsule/examples/shim/libXext.so.6.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libXext.so.6.shared b/subprojects/libcapsule/examples/shim/libXext.so.6.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libXext.so.6.symbols b/subprojects/libcapsule/examples/shim/libXext.so.6.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..985808d7fdfb44adf13fbfee1de5d23e6f8e13cd
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libXext.so.6.symbols
@@ -0,0 +1,135 @@
+DPMSCapable 
+DPMSDisable 
+DPMSEnable 
+DPMSForceLevel 
+DPMSGetTimeouts 
+DPMSGetVersion 
+DPMSInfo 
+DPMSQueryExtension 
+DPMSSetTimeouts 
+XGEQueryExtension 
+XGEQueryVersion 
+XLbxGetEventBase 
+XLbxQueryExtension 
+XLbxQueryVersion 
+XMITMiscGetBugMode 
+XMITMiscQueryExtension 
+XMITMiscSetBugMode 
+XMissingExtension 
+XSecurityAllocXauth 
+XSecurityFreeXauth 
+XSecurityGenerateAuthorization 
+XSecurityQueryExtension 
+XSecurityRevokeAuthorization 
+XSetExtensionErrorHandler 
+XShapeCombineMask 
+XShapeCombineRectangles 
+XShapeCombineRegion 
+XShapeCombineShape 
+XShapeGetRectangles 
+XShapeInputSelected 
+XShapeOffsetShape 
+XShapeQueryExtension 
+XShapeQueryExtents 
+XShapeQueryVersion 
+XShapeSelectInput 
+XShmAttach 
+XShmCreateImage 
+XShmCreatePixmap 
+XShmDetach 
+XShmGetEventBase 
+XShmGetImage 
+XShmPixmapFormat 
+XShmPutImage 
+XShmQueryExtension 
+XShmQueryVersion 
+XSyncAwait 
+XSyncAwaitFence 
+XSyncChangeAlarm 
+XSyncChangeCounter 
+XSyncCreateAlarm 
+XSyncCreateCounter 
+XSyncCreateFence 
+XSyncDestroyAlarm 
+XSyncDestroyCounter 
+XSyncDestroyFence 
+XSyncFreeSystemCounterList 
+XSyncGetPriority 
+XSyncInitialize 
+XSyncIntToValue 
+XSyncIntsToValue 
+XSyncListSystemCounters 
+XSyncMaxValue 
+XSyncMinValue 
+XSyncQueryAlarm 
+XSyncQueryCounter 
+XSyncQueryExtension 
+XSyncQueryFence 
+XSyncResetFence 
+XSyncSetCounter 
+XSyncSetPriority 
+XSyncTriggerFence 
+XSyncValueAdd 
+XSyncValueEqual 
+XSyncValueGreaterOrEqual 
+XSyncValueGreaterThan 
+XSyncValueHigh32 
+XSyncValueIsNegative 
+XSyncValueIsPositive 
+XSyncValueIsZero 
+XSyncValueLessOrEqual 
+XSyncValueLessThan 
+XSyncValueLow32 
+XSyncValueSubtract 
+XTestFakeAckType 
+XTestFakeInput 
+XTestFlush 
+XTestGetInput 
+XTestInputActionType 
+XTestMovePointer 
+XTestPressButton 
+XTestPressKey 
+XTestQueryInputSize 
+XTestReset 
+XTestStopInput 
+XagCreateAssociation 
+XagCreateEmbeddedApplicationGroup 
+XagCreateNonembeddedApplicationGroup 
+XagDestroyApplicationGroup 
+XagDestroyAssociation 
+XagGetApplicationGroupAttributes 
+XagQueryApplicationGroup 
+XagQueryVersion 
+XcupGetReservedColormapEntries 
+XcupQueryVersion 
+XcupStoreColors 
+XdbeAllocateBackBufferName 
+XdbeBeginIdiom 
+XdbeDeallocateBackBufferName 
+XdbeEndIdiom 
+XdbeFreeVisualInfo 
+XdbeGetBackBufferAttributes 
+XdbeGetVisualInfo 
+XdbeQueryExtension 
+XdbeSwapBuffers 
+XeviGetVisualInfo 
+XeviQueryExtension 
+XeviQueryVersion 
+XextAddDisplay 
+XextCreateExtension 
+XextDestroyExtension 
+XextFindDisplay 
+XextRemoveDisplay 
+XmbufChangeBufferAttributes 
+XmbufChangeWindowAttributes 
+XmbufClearBufferArea 
+XmbufCreateBuffers 
+XmbufCreateStereoWindow 
+XmbufDestroyBuffers 
+XmbufDisplayBuffers 
+XmbufGetBufferAttributes 
+XmbufGetScreenInfo 
+XmbufGetVersion 
+XmbufGetWindowAttributes 
+XmbufQueryExtension 
+_XExtensionErrorFunction 
diff --git a/subprojects/libcapsule/examples/shim/libXext.so.6.symbols.updated-for b/subprojects/libcapsule/examples/shim/libXext.so.6.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libXi.so.6.excluded b/subprojects/libcapsule/examples/shim/libXi.so.6.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libXi.so.6.shared b/subprojects/libcapsule/examples/shim/libXi.so.6.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libXi.so.6.symbols b/subprojects/libcapsule/examples/shim/libXi.so.6.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..0edf1efe273d08e7bed12456656b6038655a0755
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libXi.so.6.symbols
@@ -0,0 +1,84 @@
+XAllowDeviceEvents 
+XChangeDeviceControl 
+XChangeDeviceDontPropagateList 
+XChangeDeviceKeyMapping 
+XChangeDeviceProperty 
+XChangeFeedbackControl 
+XChangeKeyboardDevice 
+XChangePointerDevice 
+XCloseDevice 
+XDeleteDeviceProperty 
+XDeviceBell 
+XFreeDeviceControl 
+XFreeDeviceList 
+XFreeDeviceMotionEvents 
+XFreeDeviceState 
+XFreeFeedbackList 
+XGetDeviceButtonMapping 
+XGetDeviceControl 
+XGetDeviceDontPropagateList 
+XGetDeviceFocus 
+XGetDeviceKeyMapping 
+XGetDeviceModifierMapping 
+XGetDeviceMotionEvents 
+XGetDeviceProperty 
+XGetExtensionVersion 
+XGetFeedbackControl 
+XGetSelectedExtensionEvents 
+XGrabDevice 
+XGrabDeviceButton 
+XGrabDeviceKey 
+XIAllowEvents 
+XIAllowTouchEvents 
+XIBarrierReleasePointer 
+XIBarrierReleasePointers 
+XIChangeHierarchy 
+XIChangeProperty 
+XIDefineCursor 
+XIDeleteProperty 
+XIFreeDeviceInfo 
+XIGetClientPointer 
+XIGetFocus 
+XIGetProperty 
+XIGetSelectedEvents 
+XIGrabButton 
+XIGrabDevice 
+XIGrabEnter 
+XIGrabFocusIn 
+XIGrabKeycode 
+XIGrabTouchBegin 
+XIListProperties 
+XIQueryDevice 
+XIQueryPointer 
+XIQueryVersion 
+XISelectEvents 
+XISetClientPointer 
+XISetFocus 
+XIUndefineCursor 
+XIUngrabButton 
+XIUngrabDevice 
+XIUngrabEnter 
+XIUngrabFocusIn 
+XIUngrabKeycode 
+XIUngrabTouchBegin 
+XIWarpPointer 
+XListDeviceProperties 
+XListInputDevices 
+XOpenDevice 
+XQueryDeviceState 
+XSelectExtensionEvent 
+XSendExtensionEvent 
+XSetDeviceButtonMapping 
+XSetDeviceFocus 
+XSetDeviceMode 
+XSetDeviceModifierMapping 
+XSetDeviceValuators 
+XUngrabDevice 
+XUngrabDeviceButton 
+XUngrabDeviceKey 
+_XiGetDevicePresenceNotifyEvent 
+_xibadclass 
+_xibaddevice 
+_xibadevent 
+_xibadmode 
+_xidevicebusy 
diff --git a/subprojects/libcapsule/examples/shim/libXi.so.6.symbols.updated-for b/subprojects/libcapsule/examples/shim/libXi.so.6.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libgbm.so.1.excluded b/subprojects/libcapsule/examples/shim/libgbm.so.1.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libgbm.so.1.shared b/subprojects/libcapsule/examples/shim/libgbm.so.1.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libgbm.so.1.symbols b/subprojects/libcapsule/examples/shim/libgbm.so.1.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..2925c84e89bd38546b1271daad672bf1e40c6828
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libgbm.so.1.symbols
@@ -0,0 +1,33 @@
+gbm_bo_create 
+gbm_bo_create_with_modifiers 
+gbm_bo_destroy 
+gbm_bo_get_device 
+gbm_bo_get_fd 
+gbm_bo_get_format 
+gbm_bo_get_handle 
+gbm_bo_get_handle_for_plane 
+gbm_bo_get_height 
+gbm_bo_get_modifier 
+gbm_bo_get_offset 
+gbm_bo_get_plane_count 
+gbm_bo_get_stride 
+gbm_bo_get_stride_for_plane 
+gbm_bo_get_user_data 
+gbm_bo_get_width 
+gbm_bo_import 
+gbm_bo_map 
+gbm_bo_set_user_data 
+gbm_bo_unmap 
+gbm_bo_write 
+gbm_create_device 
+gbm_device_destroy 
+gbm_device_get_backend_name 
+gbm_device_get_fd 
+gbm_device_is_format_supported 
+gbm_surface_create 
+gbm_surface_create_with_modifiers 
+gbm_surface_destroy 
+gbm_surface_has_free_buffers 
+gbm_surface_lock_front_buffer 
+gbm_surface_release_buffer 
+wl_drm_interface 
diff --git a/subprojects/libcapsule/examples/shim/libgbm.so.1.symbols.updated-for b/subprojects/libcapsule/examples/shim/libgbm.so.1.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.excluded b/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.shared b/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.symbols b/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..7cdbd25a0ba61126ca170318136decc795eb80d6
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.symbols
@@ -0,0 +1,62 @@
+xcb_dri2_attach_format_end 
+xcb_dri2_attach_format_next 
+xcb_dri2_authenticate 
+xcb_dri2_authenticate_reply 
+xcb_dri2_authenticate_unchecked 
+xcb_dri2_connect 
+xcb_dri2_connect_alignment_pad 
+xcb_dri2_connect_alignment_pad_end 
+xcb_dri2_connect_alignment_pad_length 
+xcb_dri2_connect_device_name 
+xcb_dri2_connect_device_name_end 
+xcb_dri2_connect_device_name_length 
+xcb_dri2_connect_driver_name 
+xcb_dri2_connect_driver_name_end 
+xcb_dri2_connect_driver_name_length 
+xcb_dri2_connect_reply 
+xcb_dri2_connect_sizeof 
+xcb_dri2_connect_unchecked 
+xcb_dri2_copy_region 
+xcb_dri2_copy_region_reply 
+xcb_dri2_copy_region_unchecked 
+xcb_dri2_create_drawable 
+xcb_dri2_create_drawable_checked 
+xcb_dri2_destroy_drawable 
+xcb_dri2_destroy_drawable_checked 
+xcb_dri2_dri2_buffer_end 
+xcb_dri2_dri2_buffer_next 
+xcb_dri2_get_buffers 
+xcb_dri2_get_buffers_buffers 
+xcb_dri2_get_buffers_buffers_iterator 
+xcb_dri2_get_buffers_buffers_length 
+xcb_dri2_get_buffers_reply 
+xcb_dri2_get_buffers_sizeof 
+xcb_dri2_get_buffers_unchecked 
+xcb_dri2_get_buffers_with_format 
+xcb_dri2_get_buffers_with_format_buffers 
+xcb_dri2_get_buffers_with_format_buffers_iterator 
+xcb_dri2_get_buffers_with_format_buffers_length 
+xcb_dri2_get_buffers_with_format_reply 
+xcb_dri2_get_buffers_with_format_sizeof 
+xcb_dri2_get_buffers_with_format_unchecked 
+xcb_dri2_get_msc 
+xcb_dri2_get_msc_reply 
+xcb_dri2_get_msc_unchecked 
+xcb_dri2_get_param 
+xcb_dri2_get_param_reply 
+xcb_dri2_get_param_unchecked 
+xcb_dri2_id 
+xcb_dri2_query_version 
+xcb_dri2_query_version_reply 
+xcb_dri2_query_version_unchecked 
+xcb_dri2_swap_buffers 
+xcb_dri2_swap_buffers_reply 
+xcb_dri2_swap_buffers_unchecked 
+xcb_dri2_swap_interval 
+xcb_dri2_swap_interval_checked 
+xcb_dri2_wait_msc 
+xcb_dri2_wait_msc_reply 
+xcb_dri2_wait_msc_unchecked 
+xcb_dri2_wait_sbc 
+xcb_dri2_wait_sbc_reply 
+xcb_dri2_wait_sbc_unchecked 
diff --git a/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.symbols.updated-for b/subprojects/libcapsule/examples/shim/libxcb-dri2.so.0.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.excluded b/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.shared b/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.symbols b/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..25d257feda23a51a31275561e2a5f728d8fea6f5
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.symbols
@@ -0,0 +1,520 @@
+xcb_glx_are_textures_resident 
+xcb_glx_are_textures_resident_data 
+xcb_glx_are_textures_resident_data_end 
+xcb_glx_are_textures_resident_data_length 
+xcb_glx_are_textures_resident_reply 
+xcb_glx_are_textures_resident_sizeof 
+xcb_glx_are_textures_resident_unchecked 
+xcb_glx_bool32_end 
+xcb_glx_bool32_next 
+xcb_glx_change_drawable_attributes 
+xcb_glx_change_drawable_attributes_checked 
+xcb_glx_change_drawable_attributes_sizeof 
+xcb_glx_client_info 
+xcb_glx_client_info_checked 
+xcb_glx_client_info_sizeof 
+xcb_glx_context_end 
+xcb_glx_context_next 
+xcb_glx_context_tag_end 
+xcb_glx_context_tag_next 
+xcb_glx_copy_context 
+xcb_glx_copy_context_checked 
+xcb_glx_create_context 
+xcb_glx_create_context_attribs_arb 
+xcb_glx_create_context_attribs_arb_checked 
+xcb_glx_create_context_attribs_arb_sizeof 
+xcb_glx_create_context_checked 
+xcb_glx_create_glx_pixmap 
+xcb_glx_create_glx_pixmap_checked 
+xcb_glx_create_new_context 
+xcb_glx_create_new_context_checked 
+xcb_glx_create_pbuffer 
+xcb_glx_create_pbuffer_checked 
+xcb_glx_create_pbuffer_sizeof 
+xcb_glx_create_pixmap 
+xcb_glx_create_pixmap_checked 
+xcb_glx_create_pixmap_sizeof 
+xcb_glx_create_window 
+xcb_glx_create_window_checked 
+xcb_glx_create_window_sizeof 
+xcb_glx_delete_lists 
+xcb_glx_delete_lists_checked 
+xcb_glx_delete_queries_arb 
+xcb_glx_delete_queries_arb_checked 
+xcb_glx_delete_queries_arb_sizeof 
+xcb_glx_delete_textures 
+xcb_glx_delete_textures_checked 
+xcb_glx_delete_textures_sizeof 
+xcb_glx_delete_window 
+xcb_glx_delete_window_checked 
+xcb_glx_destroy_context 
+xcb_glx_destroy_context_checked 
+xcb_glx_destroy_glx_pixmap 
+xcb_glx_destroy_glx_pixmap_checked 
+xcb_glx_destroy_pbuffer 
+xcb_glx_destroy_pbuffer_checked 
+xcb_glx_destroy_pixmap 
+xcb_glx_destroy_pixmap_checked 
+xcb_glx_drawable_end 
+xcb_glx_drawable_next 
+xcb_glx_end_list 
+xcb_glx_end_list_checked 
+xcb_glx_fbconfig_end 
+xcb_glx_fbconfig_next 
+xcb_glx_feedback_buffer 
+xcb_glx_feedback_buffer_checked 
+xcb_glx_finish 
+xcb_glx_finish_reply 
+xcb_glx_finish_unchecked 
+xcb_glx_float32_end 
+xcb_glx_float32_next 
+xcb_glx_float64_end 
+xcb_glx_float64_next 
+xcb_glx_flush 
+xcb_glx_flush_checked 
+xcb_glx_gen_lists 
+xcb_glx_gen_lists_reply 
+xcb_glx_gen_lists_unchecked 
+xcb_glx_gen_queries_arb 
+xcb_glx_gen_queries_arb_data 
+xcb_glx_gen_queries_arb_data_end 
+xcb_glx_gen_queries_arb_data_length 
+xcb_glx_gen_queries_arb_reply 
+xcb_glx_gen_queries_arb_sizeof 
+xcb_glx_gen_queries_arb_unchecked 
+xcb_glx_gen_textures 
+xcb_glx_gen_textures_data 
+xcb_glx_gen_textures_data_end 
+xcb_glx_gen_textures_data_length 
+xcb_glx_gen_textures_reply 
+xcb_glx_gen_textures_sizeof 
+xcb_glx_gen_textures_unchecked 
+xcb_glx_get_booleanv 
+xcb_glx_get_booleanv_data 
+xcb_glx_get_booleanv_data_end 
+xcb_glx_get_booleanv_data_length 
+xcb_glx_get_booleanv_reply 
+xcb_glx_get_booleanv_sizeof 
+xcb_glx_get_booleanv_unchecked 
+xcb_glx_get_clip_plane 
+xcb_glx_get_clip_plane_data 
+xcb_glx_get_clip_plane_data_end 
+xcb_glx_get_clip_plane_data_length 
+xcb_glx_get_clip_plane_reply 
+xcb_glx_get_clip_plane_sizeof 
+xcb_glx_get_clip_plane_unchecked 
+xcb_glx_get_color_table 
+xcb_glx_get_color_table_data 
+xcb_glx_get_color_table_data_end 
+xcb_glx_get_color_table_data_length 
+xcb_glx_get_color_table_parameterfv 
+xcb_glx_get_color_table_parameterfv_data 
+xcb_glx_get_color_table_parameterfv_data_end 
+xcb_glx_get_color_table_parameterfv_data_length 
+xcb_glx_get_color_table_parameterfv_reply 
+xcb_glx_get_color_table_parameterfv_sizeof 
+xcb_glx_get_color_table_parameterfv_unchecked 
+xcb_glx_get_color_table_parameteriv 
+xcb_glx_get_color_table_parameteriv_data 
+xcb_glx_get_color_table_parameteriv_data_end 
+xcb_glx_get_color_table_parameteriv_data_length 
+xcb_glx_get_color_table_parameteriv_reply 
+xcb_glx_get_color_table_parameteriv_sizeof 
+xcb_glx_get_color_table_parameteriv_unchecked 
+xcb_glx_get_color_table_reply 
+xcb_glx_get_color_table_sizeof 
+xcb_glx_get_color_table_unchecked 
+xcb_glx_get_compressed_tex_image_arb 
+xcb_glx_get_compressed_tex_image_arb_data 
+xcb_glx_get_compressed_tex_image_arb_data_end 
+xcb_glx_get_compressed_tex_image_arb_data_length 
+xcb_glx_get_compressed_tex_image_arb_reply 
+xcb_glx_get_compressed_tex_image_arb_sizeof 
+xcb_glx_get_compressed_tex_image_arb_unchecked 
+xcb_glx_get_convolution_filter 
+xcb_glx_get_convolution_filter_data 
+xcb_glx_get_convolution_filter_data_end 
+xcb_glx_get_convolution_filter_data_length 
+xcb_glx_get_convolution_filter_reply 
+xcb_glx_get_convolution_filter_sizeof 
+xcb_glx_get_convolution_filter_unchecked 
+xcb_glx_get_convolution_parameterfv 
+xcb_glx_get_convolution_parameterfv_data 
+xcb_glx_get_convolution_parameterfv_data_end 
+xcb_glx_get_convolution_parameterfv_data_length 
+xcb_glx_get_convolution_parameterfv_reply 
+xcb_glx_get_convolution_parameterfv_sizeof 
+xcb_glx_get_convolution_parameterfv_unchecked 
+xcb_glx_get_convolution_parameteriv 
+xcb_glx_get_convolution_parameteriv_data 
+xcb_glx_get_convolution_parameteriv_data_end 
+xcb_glx_get_convolution_parameteriv_data_length 
+xcb_glx_get_convolution_parameteriv_reply 
+xcb_glx_get_convolution_parameteriv_sizeof 
+xcb_glx_get_convolution_parameteriv_unchecked 
+xcb_glx_get_doublev 
+xcb_glx_get_doublev_data 
+xcb_glx_get_doublev_data_end 
+xcb_glx_get_doublev_data_length 
+xcb_glx_get_doublev_reply 
+xcb_glx_get_doublev_sizeof 
+xcb_glx_get_doublev_unchecked 
+xcb_glx_get_drawable_attributes 
+xcb_glx_get_drawable_attributes_attribs 
+xcb_glx_get_drawable_attributes_attribs_end 
+xcb_glx_get_drawable_attributes_attribs_length 
+xcb_glx_get_drawable_attributes_reply 
+xcb_glx_get_drawable_attributes_sizeof 
+xcb_glx_get_drawable_attributes_unchecked 
+xcb_glx_get_error 
+xcb_glx_get_error_reply 
+xcb_glx_get_error_unchecked 
+xcb_glx_get_fb_configs 
+xcb_glx_get_fb_configs_property_list 
+xcb_glx_get_fb_configs_property_list_end 
+xcb_glx_get_fb_configs_property_list_length 
+xcb_glx_get_fb_configs_reply 
+xcb_glx_get_fb_configs_sizeof 
+xcb_glx_get_fb_configs_unchecked 
+xcb_glx_get_floatv 
+xcb_glx_get_floatv_data 
+xcb_glx_get_floatv_data_end 
+xcb_glx_get_floatv_data_length 
+xcb_glx_get_floatv_reply 
+xcb_glx_get_floatv_sizeof 
+xcb_glx_get_floatv_unchecked 
+xcb_glx_get_histogram 
+xcb_glx_get_histogram_data 
+xcb_glx_get_histogram_data_end 
+xcb_glx_get_histogram_data_length 
+xcb_glx_get_histogram_parameterfv 
+xcb_glx_get_histogram_parameterfv_data 
+xcb_glx_get_histogram_parameterfv_data_end 
+xcb_glx_get_histogram_parameterfv_data_length 
+xcb_glx_get_histogram_parameterfv_reply 
+xcb_glx_get_histogram_parameterfv_sizeof 
+xcb_glx_get_histogram_parameterfv_unchecked 
+xcb_glx_get_histogram_parameteriv 
+xcb_glx_get_histogram_parameteriv_data 
+xcb_glx_get_histogram_parameteriv_data_end 
+xcb_glx_get_histogram_parameteriv_data_length 
+xcb_glx_get_histogram_parameteriv_reply 
+xcb_glx_get_histogram_parameteriv_sizeof 
+xcb_glx_get_histogram_parameteriv_unchecked 
+xcb_glx_get_histogram_reply 
+xcb_glx_get_histogram_sizeof 
+xcb_glx_get_histogram_unchecked 
+xcb_glx_get_integerv 
+xcb_glx_get_integerv_data 
+xcb_glx_get_integerv_data_end 
+xcb_glx_get_integerv_data_length 
+xcb_glx_get_integerv_reply 
+xcb_glx_get_integerv_sizeof 
+xcb_glx_get_integerv_unchecked 
+xcb_glx_get_lightfv 
+xcb_glx_get_lightfv_data 
+xcb_glx_get_lightfv_data_end 
+xcb_glx_get_lightfv_data_length 
+xcb_glx_get_lightfv_reply 
+xcb_glx_get_lightfv_sizeof 
+xcb_glx_get_lightfv_unchecked 
+xcb_glx_get_lightiv 
+xcb_glx_get_lightiv_data 
+xcb_glx_get_lightiv_data_end 
+xcb_glx_get_lightiv_data_length 
+xcb_glx_get_lightiv_reply 
+xcb_glx_get_lightiv_sizeof 
+xcb_glx_get_lightiv_unchecked 
+xcb_glx_get_mapdv 
+xcb_glx_get_mapdv_data 
+xcb_glx_get_mapdv_data_end 
+xcb_glx_get_mapdv_data_length 
+xcb_glx_get_mapdv_reply 
+xcb_glx_get_mapdv_sizeof 
+xcb_glx_get_mapdv_unchecked 
+xcb_glx_get_mapfv 
+xcb_glx_get_mapfv_data 
+xcb_glx_get_mapfv_data_end 
+xcb_glx_get_mapfv_data_length 
+xcb_glx_get_mapfv_reply 
+xcb_glx_get_mapfv_sizeof 
+xcb_glx_get_mapfv_unchecked 
+xcb_glx_get_mapiv 
+xcb_glx_get_mapiv_data 
+xcb_glx_get_mapiv_data_end 
+xcb_glx_get_mapiv_data_length 
+xcb_glx_get_mapiv_reply 
+xcb_glx_get_mapiv_sizeof 
+xcb_glx_get_mapiv_unchecked 
+xcb_glx_get_materialfv 
+xcb_glx_get_materialfv_data 
+xcb_glx_get_materialfv_data_end 
+xcb_glx_get_materialfv_data_length 
+xcb_glx_get_materialfv_reply 
+xcb_glx_get_materialfv_sizeof 
+xcb_glx_get_materialfv_unchecked 
+xcb_glx_get_materialiv 
+xcb_glx_get_materialiv_data 
+xcb_glx_get_materialiv_data_end 
+xcb_glx_get_materialiv_data_length 
+xcb_glx_get_materialiv_reply 
+xcb_glx_get_materialiv_sizeof 
+xcb_glx_get_materialiv_unchecked 
+xcb_glx_get_minmax 
+xcb_glx_get_minmax_data 
+xcb_glx_get_minmax_data_end 
+xcb_glx_get_minmax_data_length 
+xcb_glx_get_minmax_parameterfv 
+xcb_glx_get_minmax_parameterfv_data 
+xcb_glx_get_minmax_parameterfv_data_end 
+xcb_glx_get_minmax_parameterfv_data_length 
+xcb_glx_get_minmax_parameterfv_reply 
+xcb_glx_get_minmax_parameterfv_sizeof 
+xcb_glx_get_minmax_parameterfv_unchecked 
+xcb_glx_get_minmax_parameteriv 
+xcb_glx_get_minmax_parameteriv_data 
+xcb_glx_get_minmax_parameteriv_data_end 
+xcb_glx_get_minmax_parameteriv_data_length 
+xcb_glx_get_minmax_parameteriv_reply 
+xcb_glx_get_minmax_parameteriv_sizeof 
+xcb_glx_get_minmax_parameteriv_unchecked 
+xcb_glx_get_minmax_reply 
+xcb_glx_get_minmax_sizeof 
+xcb_glx_get_minmax_unchecked 
+xcb_glx_get_pixel_mapfv 
+xcb_glx_get_pixel_mapfv_data 
+xcb_glx_get_pixel_mapfv_data_end 
+xcb_glx_get_pixel_mapfv_data_length 
+xcb_glx_get_pixel_mapfv_reply 
+xcb_glx_get_pixel_mapfv_sizeof 
+xcb_glx_get_pixel_mapfv_unchecked 
+xcb_glx_get_pixel_mapuiv 
+xcb_glx_get_pixel_mapuiv_data 
+xcb_glx_get_pixel_mapuiv_data_end 
+xcb_glx_get_pixel_mapuiv_data_length 
+xcb_glx_get_pixel_mapuiv_reply 
+xcb_glx_get_pixel_mapuiv_sizeof 
+xcb_glx_get_pixel_mapuiv_unchecked 
+xcb_glx_get_pixel_mapusv 
+xcb_glx_get_pixel_mapusv_data 
+xcb_glx_get_pixel_mapusv_data_end 
+xcb_glx_get_pixel_mapusv_data_length 
+xcb_glx_get_pixel_mapusv_reply 
+xcb_glx_get_pixel_mapusv_sizeof 
+xcb_glx_get_pixel_mapusv_unchecked 
+xcb_glx_get_polygon_stipple 
+xcb_glx_get_polygon_stipple_data 
+xcb_glx_get_polygon_stipple_data_end 
+xcb_glx_get_polygon_stipple_data_length 
+xcb_glx_get_polygon_stipple_reply 
+xcb_glx_get_polygon_stipple_sizeof 
+xcb_glx_get_polygon_stipple_unchecked 
+xcb_glx_get_query_objectiv_arb 
+xcb_glx_get_query_objectiv_arb_data 
+xcb_glx_get_query_objectiv_arb_data_end 
+xcb_glx_get_query_objectiv_arb_data_length 
+xcb_glx_get_query_objectiv_arb_reply 
+xcb_glx_get_query_objectiv_arb_sizeof 
+xcb_glx_get_query_objectiv_arb_unchecked 
+xcb_glx_get_query_objectuiv_arb 
+xcb_glx_get_query_objectuiv_arb_data 
+xcb_glx_get_query_objectuiv_arb_data_end 
+xcb_glx_get_query_objectuiv_arb_data_length 
+xcb_glx_get_query_objectuiv_arb_reply 
+xcb_glx_get_query_objectuiv_arb_sizeof 
+xcb_glx_get_query_objectuiv_arb_unchecked 
+xcb_glx_get_queryiv_arb 
+xcb_glx_get_queryiv_arb_data 
+xcb_glx_get_queryiv_arb_data_end 
+xcb_glx_get_queryiv_arb_data_length 
+xcb_glx_get_queryiv_arb_reply 
+xcb_glx_get_queryiv_arb_sizeof 
+xcb_glx_get_queryiv_arb_unchecked 
+xcb_glx_get_separable_filter 
+xcb_glx_get_separable_filter_reply 
+xcb_glx_get_separable_filter_rows_and_cols 
+xcb_glx_get_separable_filter_rows_and_cols_end 
+xcb_glx_get_separable_filter_rows_and_cols_length 
+xcb_glx_get_separable_filter_sizeof 
+xcb_glx_get_separable_filter_unchecked 
+xcb_glx_get_string 
+xcb_glx_get_string_reply 
+xcb_glx_get_string_sizeof 
+xcb_glx_get_string_string 
+xcb_glx_get_string_string_end 
+xcb_glx_get_string_string_length 
+xcb_glx_get_string_unchecked 
+xcb_glx_get_tex_envfv 
+xcb_glx_get_tex_envfv_data 
+xcb_glx_get_tex_envfv_data_end 
+xcb_glx_get_tex_envfv_data_length 
+xcb_glx_get_tex_envfv_reply 
+xcb_glx_get_tex_envfv_sizeof 
+xcb_glx_get_tex_envfv_unchecked 
+xcb_glx_get_tex_enviv 
+xcb_glx_get_tex_enviv_data 
+xcb_glx_get_tex_enviv_data_end 
+xcb_glx_get_tex_enviv_data_length 
+xcb_glx_get_tex_enviv_reply 
+xcb_glx_get_tex_enviv_sizeof 
+xcb_glx_get_tex_enviv_unchecked 
+xcb_glx_get_tex_gendv 
+xcb_glx_get_tex_gendv_data 
+xcb_glx_get_tex_gendv_data_end 
+xcb_glx_get_tex_gendv_data_length 
+xcb_glx_get_tex_gendv_reply 
+xcb_glx_get_tex_gendv_sizeof 
+xcb_glx_get_tex_gendv_unchecked 
+xcb_glx_get_tex_genfv 
+xcb_glx_get_tex_genfv_data 
+xcb_glx_get_tex_genfv_data_end 
+xcb_glx_get_tex_genfv_data_length 
+xcb_glx_get_tex_genfv_reply 
+xcb_glx_get_tex_genfv_sizeof 
+xcb_glx_get_tex_genfv_unchecked 
+xcb_glx_get_tex_geniv 
+xcb_glx_get_tex_geniv_data 
+xcb_glx_get_tex_geniv_data_end 
+xcb_glx_get_tex_geniv_data_length 
+xcb_glx_get_tex_geniv_reply 
+xcb_glx_get_tex_geniv_sizeof 
+xcb_glx_get_tex_geniv_unchecked 
+xcb_glx_get_tex_image 
+xcb_glx_get_tex_image_data 
+xcb_glx_get_tex_image_data_end 
+xcb_glx_get_tex_image_data_length 
+xcb_glx_get_tex_image_reply 
+xcb_glx_get_tex_image_sizeof 
+xcb_glx_get_tex_image_unchecked 
+xcb_glx_get_tex_level_parameterfv 
+xcb_glx_get_tex_level_parameterfv_data 
+xcb_glx_get_tex_level_parameterfv_data_end 
+xcb_glx_get_tex_level_parameterfv_data_length 
+xcb_glx_get_tex_level_parameterfv_reply 
+xcb_glx_get_tex_level_parameterfv_sizeof 
+xcb_glx_get_tex_level_parameterfv_unchecked 
+xcb_glx_get_tex_level_parameteriv 
+xcb_glx_get_tex_level_parameteriv_data 
+xcb_glx_get_tex_level_parameteriv_data_end 
+xcb_glx_get_tex_level_parameteriv_data_length 
+xcb_glx_get_tex_level_parameteriv_reply 
+xcb_glx_get_tex_level_parameteriv_sizeof 
+xcb_glx_get_tex_level_parameteriv_unchecked 
+xcb_glx_get_tex_parameterfv 
+xcb_glx_get_tex_parameterfv_data 
+xcb_glx_get_tex_parameterfv_data_end 
+xcb_glx_get_tex_parameterfv_data_length 
+xcb_glx_get_tex_parameterfv_reply 
+xcb_glx_get_tex_parameterfv_sizeof 
+xcb_glx_get_tex_parameterfv_unchecked 
+xcb_glx_get_tex_parameteriv 
+xcb_glx_get_tex_parameteriv_data 
+xcb_glx_get_tex_parameteriv_data_end 
+xcb_glx_get_tex_parameteriv_data_length 
+xcb_glx_get_tex_parameteriv_reply 
+xcb_glx_get_tex_parameteriv_sizeof 
+xcb_glx_get_tex_parameteriv_unchecked 
+xcb_glx_get_visual_configs 
+xcb_glx_get_visual_configs_property_list 
+xcb_glx_get_visual_configs_property_list_end 
+xcb_glx_get_visual_configs_property_list_length 
+xcb_glx_get_visual_configs_reply 
+xcb_glx_get_visual_configs_sizeof 
+xcb_glx_get_visual_configs_unchecked 
+xcb_glx_id 
+xcb_glx_is_direct 
+xcb_glx_is_direct_reply 
+xcb_glx_is_direct_unchecked 
+xcb_glx_is_list 
+xcb_glx_is_list_reply 
+xcb_glx_is_list_unchecked 
+xcb_glx_is_query_arb 
+xcb_glx_is_query_arb_reply 
+xcb_glx_is_query_arb_unchecked 
+xcb_glx_is_texture 
+xcb_glx_is_texture_reply 
+xcb_glx_is_texture_unchecked 
+xcb_glx_make_context_current 
+xcb_glx_make_context_current_reply 
+xcb_glx_make_context_current_unchecked 
+xcb_glx_make_current 
+xcb_glx_make_current_reply 
+xcb_glx_make_current_unchecked 
+xcb_glx_new_list 
+xcb_glx_new_list_checked 
+xcb_glx_pbuffer_end 
+xcb_glx_pbuffer_next 
+xcb_glx_pixel_storef 
+xcb_glx_pixel_storef_checked 
+xcb_glx_pixel_storei 
+xcb_glx_pixel_storei_checked 
+xcb_glx_pixmap_end 
+xcb_glx_pixmap_next 
+xcb_glx_query_context 
+xcb_glx_query_context_attribs 
+xcb_glx_query_context_attribs_end 
+xcb_glx_query_context_attribs_length 
+xcb_glx_query_context_reply 
+xcb_glx_query_context_sizeof 
+xcb_glx_query_context_unchecked 
+xcb_glx_query_extensions_string 
+xcb_glx_query_extensions_string_reply 
+xcb_glx_query_extensions_string_unchecked 
+xcb_glx_query_server_string 
+xcb_glx_query_server_string_reply 
+xcb_glx_query_server_string_sizeof 
+xcb_glx_query_server_string_string 
+xcb_glx_query_server_string_string_end 
+xcb_glx_query_server_string_string_length 
+xcb_glx_query_server_string_unchecked 
+xcb_glx_query_version 
+xcb_glx_query_version_reply 
+xcb_glx_query_version_unchecked 
+xcb_glx_read_pixels 
+xcb_glx_read_pixels_data 
+xcb_glx_read_pixels_data_end 
+xcb_glx_read_pixels_data_length 
+xcb_glx_read_pixels_reply 
+xcb_glx_read_pixels_sizeof 
+xcb_glx_read_pixels_unchecked 
+xcb_glx_render 
+xcb_glx_render_checked 
+xcb_glx_render_large 
+xcb_glx_render_large_checked 
+xcb_glx_render_large_sizeof 
+xcb_glx_render_mode 
+xcb_glx_render_mode_data 
+xcb_glx_render_mode_data_end 
+xcb_glx_render_mode_data_length 
+xcb_glx_render_mode_reply 
+xcb_glx_render_mode_sizeof 
+xcb_glx_render_mode_unchecked 
+xcb_glx_render_sizeof 
+xcb_glx_select_buffer 
+xcb_glx_select_buffer_checked 
+xcb_glx_set_client_info_2arb 
+xcb_glx_set_client_info_2arb_checked 
+xcb_glx_set_client_info_2arb_sizeof 
+xcb_glx_set_client_info_arb 
+xcb_glx_set_client_info_arb_checked 
+xcb_glx_set_client_info_arb_sizeof 
+xcb_glx_swap_buffers 
+xcb_glx_swap_buffers_checked 
+xcb_glx_use_x_font 
+xcb_glx_use_x_font_checked 
+xcb_glx_vendor_private 
+xcb_glx_vendor_private_checked 
+xcb_glx_vendor_private_sizeof 
+xcb_glx_vendor_private_with_reply 
+xcb_glx_vendor_private_with_reply_data_2 
+xcb_glx_vendor_private_with_reply_data_2_end 
+xcb_glx_vendor_private_with_reply_data_2_length 
+xcb_glx_vendor_private_with_reply_reply 
+xcb_glx_vendor_private_with_reply_sizeof 
+xcb_glx_vendor_private_with_reply_unchecked 
+xcb_glx_wait_gl 
+xcb_glx_wait_gl_checked 
+xcb_glx_wait_x 
+xcb_glx_wait_x_checked 
+xcb_glx_window_end 
+xcb_glx_window_next 
diff --git a/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.symbols.updated-for b/subprojects/libcapsule/examples/shim/libxcb-glx.so.0.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-present.so.0.excluded b/subprojects/libcapsule/examples/shim/libxcb-present.so.0.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-present.so.0.shared b/subprojects/libcapsule/examples/shim/libxcb-present.so.0.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-present.so.0.symbols b/subprojects/libcapsule/examples/shim/libxcb-present.so.0.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..aafd15f17cecacd1bd9f5f0d0ec59f2e23683def
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libxcb-present.so.0.symbols
@@ -0,0 +1,19 @@
+xcb_present_event_end 
+xcb_present_event_next 
+xcb_present_id 
+xcb_present_notify_end 
+xcb_present_notify_msc 
+xcb_present_notify_msc_checked 
+xcb_present_notify_next 
+xcb_present_pixmap 
+xcb_present_pixmap_checked 
+xcb_present_pixmap_sizeof 
+xcb_present_query_capabilities 
+xcb_present_query_capabilities_reply 
+xcb_present_query_capabilities_unchecked 
+xcb_present_query_version 
+xcb_present_query_version_reply 
+xcb_present_query_version_unchecked 
+xcb_present_redirect_notify_sizeof 
+xcb_present_select_input 
+xcb_present_select_input_checked 
diff --git a/subprojects/libcapsule/examples/shim/libxcb-present.so.0.symbols.updated-for b/subprojects/libcapsule/examples/shim/libxcb-present.so.0.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.excluded b/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.shared b/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.symbols b/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..649602392a28eea809894dcf4f2d42705c720eb9
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.symbols
@@ -0,0 +1,80 @@
+xcb_sync_alarm_end 
+xcb_sync_alarm_next 
+xcb_sync_await 
+xcb_sync_await_checked 
+xcb_sync_await_fence 
+xcb_sync_await_fence_checked 
+xcb_sync_await_fence_sizeof 
+xcb_sync_await_sizeof 
+xcb_sync_change_alarm 
+xcb_sync_change_alarm_aux 
+xcb_sync_change_alarm_aux_checked 
+xcb_sync_change_alarm_checked 
+xcb_sync_change_alarm_value_list_serialize 
+xcb_sync_change_alarm_value_list_sizeof 
+xcb_sync_change_alarm_value_list_unpack 
+xcb_sync_change_counter 
+xcb_sync_change_counter_checked 
+xcb_sync_counter_end 
+xcb_sync_counter_next 
+xcb_sync_create_alarm 
+xcb_sync_create_alarm_aux 
+xcb_sync_create_alarm_aux_checked 
+xcb_sync_create_alarm_checked 
+xcb_sync_create_alarm_value_list_serialize 
+xcb_sync_create_alarm_value_list_sizeof 
+xcb_sync_create_alarm_value_list_unpack 
+xcb_sync_create_counter 
+xcb_sync_create_counter_checked 
+xcb_sync_create_fence 
+xcb_sync_create_fence_checked 
+xcb_sync_destroy_alarm 
+xcb_sync_destroy_alarm_checked 
+xcb_sync_destroy_counter 
+xcb_sync_destroy_counter_checked 
+xcb_sync_destroy_fence 
+xcb_sync_destroy_fence_checked 
+xcb_sync_fence_end 
+xcb_sync_fence_next 
+xcb_sync_get_priority 
+xcb_sync_get_priority_reply 
+xcb_sync_get_priority_unchecked 
+xcb_sync_id 
+xcb_sync_initialize 
+xcb_sync_initialize_reply 
+xcb_sync_initialize_unchecked 
+xcb_sync_int64_end 
+xcb_sync_int64_next 
+xcb_sync_list_system_counters 
+xcb_sync_list_system_counters_counters_iterator 
+xcb_sync_list_system_counters_counters_length 
+xcb_sync_list_system_counters_reply 
+xcb_sync_list_system_counters_sizeof 
+xcb_sync_list_system_counters_unchecked 
+xcb_sync_query_alarm 
+xcb_sync_query_alarm_reply 
+xcb_sync_query_alarm_unchecked 
+xcb_sync_query_counter 
+xcb_sync_query_counter_reply 
+xcb_sync_query_counter_unchecked 
+xcb_sync_query_fence 
+xcb_sync_query_fence_reply 
+xcb_sync_query_fence_unchecked 
+xcb_sync_reset_fence 
+xcb_sync_reset_fence_checked 
+xcb_sync_set_counter 
+xcb_sync_set_counter_checked 
+xcb_sync_set_priority 
+xcb_sync_set_priority_checked 
+xcb_sync_systemcounter_end 
+xcb_sync_systemcounter_name 
+xcb_sync_systemcounter_name_end 
+xcb_sync_systemcounter_name_length 
+xcb_sync_systemcounter_next 
+xcb_sync_systemcounter_sizeof 
+xcb_sync_trigger_end 
+xcb_sync_trigger_fence 
+xcb_sync_trigger_fence_checked 
+xcb_sync_trigger_next 
+xcb_sync_waitcondition_end 
+xcb_sync_waitcondition_next 
diff --git a/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.symbols.updated-for b/subprojects/libcapsule/examples/shim/libxcb-sync.so.1.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb.so.1.excluded b/subprojects/libcapsule/examples/shim/libxcb.so.1.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb.so.1.shared b/subprojects/libcapsule/examples/shim/libxcb.so.1.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/examples/shim/libxcb.so.1.symbols b/subprojects/libcapsule/examples/shim/libxcb.so.1.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..2edb3b679ddefbaa379f4690dca9ab6b880a2626
--- /dev/null
+++ b/subprojects/libcapsule/examples/shim/libxcb.so.1.symbols
@@ -0,0 +1,557 @@
+xcb_alloc_color 
+xcb_alloc_color_cells 
+xcb_alloc_color_cells_masks 
+xcb_alloc_color_cells_masks_end 
+xcb_alloc_color_cells_masks_length 
+xcb_alloc_color_cells_pixels 
+xcb_alloc_color_cells_pixels_end 
+xcb_alloc_color_cells_pixels_length 
+xcb_alloc_color_cells_reply 
+xcb_alloc_color_cells_sizeof 
+xcb_alloc_color_cells_unchecked 
+xcb_alloc_color_planes 
+xcb_alloc_color_planes_pixels 
+xcb_alloc_color_planes_pixels_end 
+xcb_alloc_color_planes_pixels_length 
+xcb_alloc_color_planes_reply 
+xcb_alloc_color_planes_sizeof 
+xcb_alloc_color_planes_unchecked 
+xcb_alloc_color_reply 
+xcb_alloc_color_unchecked 
+xcb_alloc_named_color 
+xcb_alloc_named_color_reply 
+xcb_alloc_named_color_sizeof 
+xcb_alloc_named_color_unchecked 
+xcb_allow_events 
+xcb_allow_events_checked 
+xcb_arc_end 
+xcb_arc_next 
+xcb_atom_end 
+xcb_atom_next 
+xcb_bell 
+xcb_bell_checked 
+xcb_big_requests_enable 
+xcb_big_requests_enable_reply 
+xcb_big_requests_enable_unchecked 
+xcb_big_requests_id 
+xcb_button_end 
+xcb_button_next 
+xcb_change_active_pointer_grab 
+xcb_change_active_pointer_grab_checked 
+xcb_change_gc 
+xcb_change_gc_checked 
+xcb_change_gc_sizeof 
+xcb_change_hosts 
+xcb_change_hosts_checked 
+xcb_change_hosts_sizeof 
+xcb_change_keyboard_control 
+xcb_change_keyboard_control_checked 
+xcb_change_keyboard_control_sizeof 
+xcb_change_keyboard_mapping 
+xcb_change_keyboard_mapping_checked 
+xcb_change_keyboard_mapping_sizeof 
+xcb_change_pointer_control 
+xcb_change_pointer_control_checked 
+xcb_change_property 
+xcb_change_property_checked 
+xcb_change_property_sizeof 
+xcb_change_save_set 
+xcb_change_save_set_checked 
+xcb_change_window_attributes 
+xcb_change_window_attributes_checked 
+xcb_change_window_attributes_sizeof 
+xcb_char2b_end 
+xcb_char2b_next 
+xcb_charinfo_end 
+xcb_charinfo_next 
+xcb_circulate_window 
+xcb_circulate_window_checked 
+xcb_clear_area 
+xcb_clear_area_checked 
+xcb_client_message_data_end 
+xcb_client_message_data_next 
+xcb_close_font 
+xcb_close_font_checked 
+xcb_coloritem_end 
+xcb_coloritem_next 
+xcb_colormap_end 
+xcb_colormap_next 
+xcb_configure_window 
+xcb_configure_window_checked 
+xcb_configure_window_sizeof 
+xcb_connect 
+xcb_connect_to_display_with_auth_info 
+xcb_connect_to_fd 
+xcb_connection_has_error 
+xcb_convert_selection 
+xcb_convert_selection_checked 
+xcb_copy_area 
+xcb_copy_area_checked 
+xcb_copy_colormap_and_free 
+xcb_copy_colormap_and_free_checked 
+xcb_copy_gc 
+xcb_copy_gc_checked 
+xcb_copy_plane 
+xcb_copy_plane_checked 
+xcb_create_colormap 
+xcb_create_colormap_checked 
+xcb_create_cursor 
+xcb_create_cursor_checked 
+xcb_create_gc 
+xcb_create_gc_checked 
+xcb_create_gc_sizeof 
+xcb_create_glyph_cursor 
+xcb_create_glyph_cursor_checked 
+xcb_create_pixmap 
+xcb_create_pixmap_checked 
+xcb_create_window 
+xcb_create_window_checked 
+xcb_create_window_sizeof 
+xcb_cursor_end 
+xcb_cursor_next 
+xcb_delete_property 
+xcb_delete_property_checked 
+xcb_depth_end 
+xcb_depth_next 
+xcb_depth_sizeof 
+xcb_depth_visuals 
+xcb_depth_visuals_iterator 
+xcb_depth_visuals_length 
+xcb_destroy_subwindows 
+xcb_destroy_subwindows_checked 
+xcb_destroy_window 
+xcb_destroy_window_checked 
+xcb_discard_reply 
+xcb_disconnect 
+xcb_drawable_end 
+xcb_drawable_next 
+xcb_fill_poly 
+xcb_fill_poly_checked 
+xcb_fill_poly_sizeof 
+xcb_flush 
+xcb_font_end 
+xcb_font_next 
+xcb_fontable_end 
+xcb_fontable_next 
+xcb_fontprop_end 
+xcb_fontprop_next 
+xcb_force_screen_saver 
+xcb_force_screen_saver_checked 
+xcb_format_end 
+xcb_format_next 
+xcb_free_colormap 
+xcb_free_colormap_checked 
+xcb_free_colors 
+xcb_free_colors_checked 
+xcb_free_colors_sizeof 
+xcb_free_cursor 
+xcb_free_cursor_checked 
+xcb_free_gc 
+xcb_free_gc_checked 
+xcb_free_pixmap 
+xcb_free_pixmap_checked 
+xcb_gcontext_end 
+xcb_gcontext_next 
+xcb_generate_id 
+xcb_get_atom_name 
+xcb_get_atom_name_name 
+xcb_get_atom_name_name_end 
+xcb_get_atom_name_name_length 
+xcb_get_atom_name_reply 
+xcb_get_atom_name_sizeof 
+xcb_get_atom_name_unchecked 
+xcb_get_extension_data 
+xcb_get_file_descriptor 
+xcb_get_font_path 
+xcb_get_font_path_path_iterator 
+xcb_get_font_path_path_length 
+xcb_get_font_path_reply 
+xcb_get_font_path_sizeof 
+xcb_get_font_path_unchecked 
+xcb_get_geometry 
+xcb_get_geometry_reply 
+xcb_get_geometry_unchecked 
+xcb_get_image 
+xcb_get_image_data 
+xcb_get_image_data_end 
+xcb_get_image_data_length 
+xcb_get_image_reply 
+xcb_get_image_sizeof 
+xcb_get_image_unchecked 
+xcb_get_input_focus 
+xcb_get_input_focus_reply 
+xcb_get_input_focus_unchecked 
+xcb_get_keyboard_control 
+xcb_get_keyboard_control_reply 
+xcb_get_keyboard_control_unchecked 
+xcb_get_keyboard_mapping 
+xcb_get_keyboard_mapping_keysyms 
+xcb_get_keyboard_mapping_keysyms_end 
+xcb_get_keyboard_mapping_keysyms_length 
+xcb_get_keyboard_mapping_reply 
+xcb_get_keyboard_mapping_sizeof 
+xcb_get_keyboard_mapping_unchecked 
+xcb_get_maximum_request_length 
+xcb_get_modifier_mapping 
+xcb_get_modifier_mapping_keycodes 
+xcb_get_modifier_mapping_keycodes_end 
+xcb_get_modifier_mapping_keycodes_length 
+xcb_get_modifier_mapping_reply 
+xcb_get_modifier_mapping_sizeof 
+xcb_get_modifier_mapping_unchecked 
+xcb_get_motion_events 
+xcb_get_motion_events_events 
+xcb_get_motion_events_events_iterator 
+xcb_get_motion_events_events_length 
+xcb_get_motion_events_reply 
+xcb_get_motion_events_sizeof 
+xcb_get_motion_events_unchecked 
+xcb_get_pointer_control 
+xcb_get_pointer_control_reply 
+xcb_get_pointer_control_unchecked 
+xcb_get_pointer_mapping 
+xcb_get_pointer_mapping_map 
+xcb_get_pointer_mapping_map_end 
+xcb_get_pointer_mapping_map_length 
+xcb_get_pointer_mapping_reply 
+xcb_get_pointer_mapping_sizeof 
+xcb_get_pointer_mapping_unchecked 
+xcb_get_property 
+xcb_get_property_reply 
+xcb_get_property_sizeof 
+xcb_get_property_unchecked 
+xcb_get_property_value 
+xcb_get_property_value_end 
+xcb_get_property_value_length 
+xcb_get_reply_fds 
+xcb_get_screen_saver 
+xcb_get_screen_saver_reply 
+xcb_get_screen_saver_unchecked 
+xcb_get_selection_owner 
+xcb_get_selection_owner_reply 
+xcb_get_selection_owner_unchecked 
+xcb_get_setup 
+xcb_get_window_attributes 
+xcb_get_window_attributes_reply 
+xcb_get_window_attributes_unchecked 
+xcb_grab_button 
+xcb_grab_button_checked 
+xcb_grab_key 
+xcb_grab_key_checked 
+xcb_grab_keyboard 
+xcb_grab_keyboard_reply 
+xcb_grab_keyboard_unchecked 
+xcb_grab_pointer 
+xcb_grab_pointer_reply 
+xcb_grab_pointer_unchecked 
+xcb_grab_server 
+xcb_grab_server_checked 
+xcb_host_address 
+xcb_host_address_end 
+xcb_host_address_length 
+xcb_host_end 
+xcb_host_next 
+xcb_host_sizeof 
+xcb_image_text_16 
+xcb_image_text_16_checked 
+xcb_image_text_16_sizeof 
+xcb_image_text_8 
+xcb_image_text_8_checked 
+xcb_image_text_8_sizeof 
+xcb_install_colormap 
+xcb_install_colormap_checked 
+xcb_intern_atom 
+xcb_intern_atom_reply 
+xcb_intern_atom_sizeof 
+xcb_intern_atom_unchecked 
+xcb_keycode_end 
+xcb_keycode_next 
+xcb_keysym_end 
+xcb_keysym_next 
+xcb_kill_client 
+xcb_kill_client_checked 
+xcb_list_extensions 
+xcb_list_extensions_names_iterator 
+xcb_list_extensions_names_length 
+xcb_list_extensions_reply 
+xcb_list_extensions_sizeof 
+xcb_list_extensions_unchecked 
+xcb_list_fonts 
+xcb_list_fonts_names_iterator 
+xcb_list_fonts_names_length 
+xcb_list_fonts_reply 
+xcb_list_fonts_sizeof 
+xcb_list_fonts_unchecked 
+xcb_list_fonts_with_info 
+xcb_list_fonts_with_info_name 
+xcb_list_fonts_with_info_name_end 
+xcb_list_fonts_with_info_name_length 
+xcb_list_fonts_with_info_properties 
+xcb_list_fonts_with_info_properties_iterator 
+xcb_list_fonts_with_info_properties_length 
+xcb_list_fonts_with_info_reply 
+xcb_list_fonts_with_info_sizeof 
+xcb_list_fonts_with_info_unchecked 
+xcb_list_hosts 
+xcb_list_hosts_hosts_iterator 
+xcb_list_hosts_hosts_length 
+xcb_list_hosts_reply 
+xcb_list_hosts_sizeof 
+xcb_list_hosts_unchecked 
+xcb_list_installed_colormaps 
+xcb_list_installed_colormaps_cmaps 
+xcb_list_installed_colormaps_cmaps_end 
+xcb_list_installed_colormaps_cmaps_length 
+xcb_list_installed_colormaps_reply 
+xcb_list_installed_colormaps_sizeof 
+xcb_list_installed_colormaps_unchecked 
+xcb_list_properties 
+xcb_list_properties_atoms 
+xcb_list_properties_atoms_end 
+xcb_list_properties_atoms_length 
+xcb_list_properties_reply 
+xcb_list_properties_sizeof 
+xcb_list_properties_unchecked 
+xcb_lookup_color 
+xcb_lookup_color_reply 
+xcb_lookup_color_sizeof 
+xcb_lookup_color_unchecked 
+xcb_map_subwindows 
+xcb_map_subwindows_checked 
+xcb_map_window 
+xcb_map_window_checked 
+xcb_no_operation 
+xcb_no_operation_checked 
+xcb_open_font 
+xcb_open_font_checked 
+xcb_open_font_sizeof 
+xcb_parse_display 
+xcb_pixmap_end 
+xcb_pixmap_next 
+xcb_point_end 
+xcb_point_next 
+xcb_poll_for_event 
+xcb_poll_for_queued_event 
+xcb_poll_for_reply 
+xcb_poll_for_special_event 
+xcb_poly_arc 
+xcb_poly_arc_checked 
+xcb_poly_arc_sizeof 
+xcb_poly_fill_arc 
+xcb_poly_fill_arc_checked 
+xcb_poly_fill_arc_sizeof 
+xcb_poly_fill_rectangle 
+xcb_poly_fill_rectangle_checked 
+xcb_poly_fill_rectangle_sizeof 
+xcb_poly_line 
+xcb_poly_line_checked 
+xcb_poly_line_sizeof 
+xcb_poly_point 
+xcb_poly_point_checked 
+xcb_poly_point_sizeof 
+xcb_poly_rectangle 
+xcb_poly_rectangle_checked 
+xcb_poly_rectangle_sizeof 
+xcb_poly_segment 
+xcb_poly_segment_checked 
+xcb_poly_segment_sizeof 
+xcb_poly_text_16 
+xcb_poly_text_16_checked 
+xcb_poly_text_16_sizeof 
+xcb_poly_text_8 
+xcb_poly_text_8_checked 
+xcb_poly_text_8_sizeof 
+xcb_popcount 
+xcb_prefetch_extension_data 
+xcb_prefetch_maximum_request_length 
+xcb_put_image 
+xcb_put_image_checked 
+xcb_put_image_sizeof 
+xcb_query_best_size 
+xcb_query_best_size_reply 
+xcb_query_best_size_unchecked 
+xcb_query_colors 
+xcb_query_colors_colors 
+xcb_query_colors_colors_iterator 
+xcb_query_colors_colors_length 
+xcb_query_colors_reply 
+xcb_query_colors_sizeof 
+xcb_query_colors_unchecked 
+xcb_query_extension 
+xcb_query_extension_reply 
+xcb_query_extension_sizeof 
+xcb_query_extension_unchecked 
+xcb_query_font 
+xcb_query_font_char_infos 
+xcb_query_font_char_infos_iterator 
+xcb_query_font_char_infos_length 
+xcb_query_font_properties 
+xcb_query_font_properties_iterator 
+xcb_query_font_properties_length 
+xcb_query_font_reply 
+xcb_query_font_sizeof 
+xcb_query_font_unchecked 
+xcb_query_keymap 
+xcb_query_keymap_reply 
+xcb_query_keymap_unchecked 
+xcb_query_pointer 
+xcb_query_pointer_reply 
+xcb_query_pointer_unchecked 
+xcb_query_text_extents 
+xcb_query_text_extents_reply 
+xcb_query_text_extents_sizeof 
+xcb_query_text_extents_unchecked 
+xcb_query_tree 
+xcb_query_tree_children 
+xcb_query_tree_children_end 
+xcb_query_tree_children_length 
+xcb_query_tree_reply 
+xcb_query_tree_sizeof 
+xcb_query_tree_unchecked 
+xcb_recolor_cursor 
+xcb_recolor_cursor_checked 
+xcb_rectangle_end 
+xcb_rectangle_next 
+xcb_register_for_special_xge 
+xcb_reparent_window 
+xcb_reparent_window_checked 
+xcb_request_check 
+xcb_rgb_end 
+xcb_rgb_next 
+xcb_rotate_properties 
+xcb_rotate_properties_checked 
+xcb_rotate_properties_sizeof 
+xcb_screen_allowed_depths_iterator 
+xcb_screen_allowed_depths_length 
+xcb_screen_end 
+xcb_screen_next 
+xcb_screen_sizeof 
+xcb_segment_end 
+xcb_segment_next 
+xcb_send_event 
+xcb_send_event_checked 
+xcb_send_fd 
+xcb_send_request 
+xcb_set_access_control 
+xcb_set_access_control_checked 
+xcb_set_clip_rectangles 
+xcb_set_clip_rectangles_checked 
+xcb_set_clip_rectangles_sizeof 
+xcb_set_close_down_mode 
+xcb_set_close_down_mode_checked 
+xcb_set_dashes 
+xcb_set_dashes_checked 
+xcb_set_dashes_sizeof 
+xcb_set_font_path 
+xcb_set_font_path_checked 
+xcb_set_font_path_sizeof 
+xcb_set_input_focus 
+xcb_set_input_focus_checked 
+xcb_set_modifier_mapping 
+xcb_set_modifier_mapping_reply 
+xcb_set_modifier_mapping_sizeof 
+xcb_set_modifier_mapping_unchecked 
+xcb_set_pointer_mapping 
+xcb_set_pointer_mapping_reply 
+xcb_set_pointer_mapping_sizeof 
+xcb_set_pointer_mapping_unchecked 
+xcb_set_screen_saver 
+xcb_set_screen_saver_checked 
+xcb_set_selection_owner 
+xcb_set_selection_owner_checked 
+xcb_setup_authenticate_end 
+xcb_setup_authenticate_next 
+xcb_setup_authenticate_reason 
+xcb_setup_authenticate_reason_end 
+xcb_setup_authenticate_reason_length 
+xcb_setup_authenticate_sizeof 
+xcb_setup_end 
+xcb_setup_failed_end 
+xcb_setup_failed_next 
+xcb_setup_failed_reason 
+xcb_setup_failed_reason_end 
+xcb_setup_failed_reason_length 
+xcb_setup_failed_sizeof 
+xcb_setup_next 
+xcb_setup_pixmap_formats 
+xcb_setup_pixmap_formats_iterator 
+xcb_setup_pixmap_formats_length 
+xcb_setup_request_authorization_protocol_data 
+xcb_setup_request_authorization_protocol_data_end 
+xcb_setup_request_authorization_protocol_data_length 
+xcb_setup_request_authorization_protocol_name 
+xcb_setup_request_authorization_protocol_name_end 
+xcb_setup_request_authorization_protocol_name_length 
+xcb_setup_request_end 
+xcb_setup_request_next 
+xcb_setup_request_sizeof 
+xcb_setup_roots_iterator 
+xcb_setup_roots_length 
+xcb_setup_sizeof 
+xcb_setup_vendor 
+xcb_setup_vendor_end 
+xcb_setup_vendor_length 
+xcb_store_colors 
+xcb_store_colors_checked 
+xcb_store_colors_sizeof 
+xcb_store_named_color 
+xcb_store_named_color_checked 
+xcb_store_named_color_sizeof 
+xcb_str_end 
+xcb_str_name 
+xcb_str_name_end 
+xcb_str_name_length 
+xcb_str_next 
+xcb_str_sizeof 
+xcb_sumof 
+xcb_take_socket 
+xcb_timecoord_end 
+xcb_timecoord_next 
+xcb_timestamp_end 
+xcb_timestamp_next 
+xcb_translate_coordinates 
+xcb_translate_coordinates_reply 
+xcb_translate_coordinates_unchecked 
+xcb_ungrab_button 
+xcb_ungrab_button_checked 
+xcb_ungrab_key 
+xcb_ungrab_key_checked 
+xcb_ungrab_keyboard 
+xcb_ungrab_keyboard_checked 
+xcb_ungrab_pointer 
+xcb_ungrab_pointer_checked 
+xcb_ungrab_server 
+xcb_ungrab_server_checked 
+xcb_uninstall_colormap 
+xcb_uninstall_colormap_checked 
+xcb_unmap_subwindows 
+xcb_unmap_subwindows_checked 
+xcb_unmap_window 
+xcb_unmap_window_checked 
+xcb_unregister_for_special_event 
+xcb_visualid_end 
+xcb_visualid_next 
+xcb_visualtype_end 
+xcb_visualtype_next 
+xcb_wait_for_event 
+xcb_wait_for_reply 
+xcb_wait_for_special_event 
+xcb_warp_pointer 
+xcb_warp_pointer_checked 
+xcb_window_end 
+xcb_window_next 
+xcb_writev 
+xcb_xc_misc_get_version 
+xcb_xc_misc_get_version_reply 
+xcb_xc_misc_get_version_unchecked 
+xcb_xc_misc_get_xid_list 
+xcb_xc_misc_get_xid_list_ids 
+xcb_xc_misc_get_xid_list_ids_end 
+xcb_xc_misc_get_xid_list_ids_length 
+xcb_xc_misc_get_xid_list_reply 
+xcb_xc_misc_get_xid_list_sizeof 
+xcb_xc_misc_get_xid_list_unchecked 
+xcb_xc_misc_get_xid_range 
+xcb_xc_misc_get_xid_range_reply 
+xcb_xc_misc_get_xid_range_unchecked 
+xcb_xc_misc_id 
diff --git a/subprojects/libcapsule/examples/shim/libxcb.so.1.symbols.updated-for b/subprojects/libcapsule/examples/shim/libxcb.so.1.symbols.updated-for
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/m4/ax_is_release.m4 b/subprojects/libcapsule/m4/ax_is_release.m4
new file mode 100644
index 0000000000000000000000000000000000000000..9097ddb6a92a8143a940f7327211d5a9be2694c6
--- /dev/null
+++ b/subprojects/libcapsule/m4/ax_is_release.m4
@@ -0,0 +1,80 @@
+# ===========================================================================
+#      https://www.gnu.org/software/autoconf-archive/ax_is_release.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+#   AX_IS_RELEASE(POLICY)
+#
+# DESCRIPTION
+#
+#   Determine whether the code is being configured as a release, or from
+#   git. Set the ax_is_release variable to 'yes' or 'no'.
+#
+#   If building a release version, it is recommended that the configure
+#   script disable compiler errors and debug features, by conditionalising
+#   them on the ax_is_release variable.  If building from git, these
+#   features should be enabled.
+#
+#   The POLICY parameter specifies how ax_is_release is determined. It can
+#   take the following values:
+#
+#    * git-directory:  ax_is_release will be 'no' if a '.git' directory exists
+#    * minor-version:  ax_is_release will be 'no' if the minor version number
+#                      in $PACKAGE_VERSION is odd; this assumes
+#                      $PACKAGE_VERSION follows the 'major.minor.micro' scheme
+#    * micro-version:  ax_is_release will be 'no' if the micro version number
+#                      in $PACKAGE_VERSION is odd; this assumes
+#                      $PACKAGE_VERSION follows the 'major.minor.micro' scheme
+#    * dash-version:   ax_is_release will be 'no' if there is a dash '-'
+#                      in $PACKAGE_VERSION, for example 1.2-pre3, 1.2.42-a8b9
+#                      or 2.0-dirty (in particular this is suitable for use
+#                      with git-version-gen)
+#    * always:         ax_is_release will always be 'yes'
+#    * never:          ax_is_release will always be 'no'
+#
+#   Other policies may be added in future.
+#
+# LICENSE
+#
+#   Copyright (c) 2015 Philip Withnall <philip@tecnocode.co.uk>
+#   Copyright (c) 2016 Collabora Ltd.
+#
+#   Copying and distribution of this file, with or without modification, are
+#   permitted in any medium without royalty provided the copyright notice
+#   and this notice are preserved.
+
+#serial 7
+
+AC_DEFUN([AX_IS_RELEASE],[
+    AC_BEFORE([AC_INIT],[$0])
+
+    m4_case([$1],
+      [git-directory],[
+        # $is_release = (.git directory does not exist)
+        AS_IF([test -d ${srcdir}/.git],[ax_is_release=no],[ax_is_release=yes])
+      ],
+      [minor-version],[
+        # $is_release = ($minor_version is even)
+        minor_version=`echo "$PACKAGE_VERSION" | sed 's/[[^.]][[^.]]*.\([[^.]][[^.]]*\).*/\1/'`
+        AS_IF([test "$(( $minor_version % 2 ))" -ne 0],
+              [ax_is_release=no],[ax_is_release=yes])
+      ],
+      [micro-version],[
+        # $is_release = ($micro_version is even)
+        micro_version=`echo "$PACKAGE_VERSION" | sed 's/[[^.]]*\.[[^.]]*\.\([[^.]]*\).*/\1/'`
+        AS_IF([test "$(( $micro_version % 2 ))" -ne 0],
+              [ax_is_release=no],[ax_is_release=yes])
+      ],
+      [dash-version],[
+        # $is_release = ($PACKAGE_VERSION has a dash)
+        AS_CASE([$PACKAGE_VERSION],
+                [*-*], [ax_is_release=no],
+                [*], [ax_is_release=yes])
+      ],
+      [always],[ax_is_release=yes],
+      [never],[ax_is_release=no],
+      [
+        AC_MSG_ERROR([Invalid policy. Valid policies: git-directory, minor-version, micro-version, dash-version, always, never.])
+      ])
+])
diff --git a/subprojects/libcapsule/m4/capsule_matching_abi.m4 b/subprojects/libcapsule/m4/capsule_matching_abi.m4
new file mode 100644
index 0000000000000000000000000000000000000000..603a56fbf86fa76a94c51415d4e9112c5752ee21
--- /dev/null
+++ b/subprojects/libcapsule/m4/capsule_matching_abi.m4
@@ -0,0 +1,76 @@
+# Copyright 2019 Collabora Ltd.
+#
+# Copying and distribution of this file, with or without modification, are
+# permitted in any medium without royalty provided the copyright notice
+# and this notice are preserved.  This file is offered as-is, without any
+# warranty.
+
+#serial 1
+
+dnl CAPSULE_MATCHING_ABI()
+dnl
+dnl Assert that the --host architecture is consistent with the selected
+dnl compiler.
+AC_DEFUN([CAPSULE_MATCHING_ABI], [
+    AC_CANONICAL_HOST
+    AC_LANG_PUSH([C])
+
+    AS_CASE([${host_cpu}-${host_os}],
+        [i?86-*], [
+            AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[
+#if defined(__i386__)
+/* OK */
+#else
+#error not i386
+#endif
+                    ]]
+                )],
+                [],
+                [
+                    AC_MSG_ERROR([Host system $host must use an i386 compiler such as CC='gcc -m32' or CC='${host_cpu}-${host_os}-gcc'])
+                ]
+            )
+        ],
+
+        [x86_64-*-gnu], [
+            AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[
+#if defined(__x86_64__) && defined(__LP64__)
+/* OK */
+#else
+#error not x86_64
+#endif
+                    ]]
+                )],
+                [],
+                [
+                    AC_MSG_ERROR([Host system $host must use an x86_64 compiler such as CC='gcc -m64' or CC='${host_cpu}-${host_os}-gcc'])
+                ]
+            )
+        ],
+
+        # libcapsule doesn't actually support x32, but for completeness...
+        [x86_64-*-gnux32], [
+            AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[
+#if defined(__x86_64__) && defined(__ILP32__)
+/* OK */
+#else
+#error not x32
+#endif
+                    ]]
+                )],
+                [],
+                [
+                    AC_MSG_ERROR([Host system $host must use an x32 compiler such as CC='gcc -mx32' or CC='${host_cpu}-${host_os}-gcc'])
+                ]
+            )
+        ],
+
+        # For non-x86 we assume users are more aware of how to select the
+        # right cross-compiler. (libcapsule doesn't support non-x86 yet
+        # anyway.)
+    )
+
+    AC_LANG_POP([C])
+])dnl CAPSULE_MATCHING_ABI
+
+# vim:set sw=4 sts=4 et:
diff --git a/subprojects/libcapsule/tests/CapsuleTest.pm b/subprojects/libcapsule/tests/CapsuleTest.pm
new file mode 100644
index 0000000000000000000000000000000000000000..38e3f4d6ef82c7d3f8711edf6c8319290b679b1b
--- /dev/null
+++ b/subprojects/libcapsule/tests/CapsuleTest.pm
@@ -0,0 +1,486 @@
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Note that get_symbols_with_nm() uses some GPL-2+ code taken from dpkg.
+
+package CapsuleTest;
+
+use strict;
+use warnings;
+
+use Cwd qw(abs_path);
+use File::Glob qw(bsd_glob);
+use FindBin;
+use Exporter qw(import);
+use IPC::Run qw(run);
+use Test::More;
+
+our @EXPORT = qw(
+    assert_run_verbose
+    bind_usr
+    diag_multiline
+    explain_wait_status
+    get_symbols_with_nm
+    libcapsule_uninstalled
+    run_assert_exit_status
+    run_ok
+    run_verbose
+    skip_all_unless_bwrap
+    skip_all_unless_nm
+    $CAPSULE_CAPTURE_LIBS_TOOL
+    $CAPSULE_INIT_PROJECT_TOOL
+    $CAPSULE_SYMBOLS_TOOL
+    $CAPSULE_VERSION_TOOL
+    $NM
+    $PKG_CONFIG
+    $builddir
+    $srcdir
+);
+
+=encoding utf8
+
+=head1 NAME
+
+CapsuleTest - utilities for libcapsule automated and manual tests
+
+=head1 EXPORTED VARIABLES
+
+=over
+
+=cut
+
+=item $PKG_CONFIG
+
+The B<pkg-config>(1) utility.
+
+=cut
+
+our $PKG_CONFIG = $ENV{PKG_CONFIG};
+$PKG_CONFIG = 'pkg-config' unless length $PKG_CONFIG;
+
+=item $CAPSULE_CAPTURE_LIBS_TOOL
+
+The B<capsule-capture-libs>(1) development tool.
+
+=cut
+
+our $CAPSULE_CAPTURE_LIBS_TOOL = $ENV{CAPSULE_CAPTURE_LIBS_TOOL};
+
+if (! length $CAPSULE_CAPTURE_LIBS_TOOL) {
+    $CAPSULE_CAPTURE_LIBS_TOOL = `$PKG_CONFIG --variable=CAPSULE_CAPTURE_LIBS_TOOL libcapsule-tools`;
+    chomp $CAPSULE_CAPTURE_LIBS_TOOL;
+}
+
+=item $CAPSULE_INIT_PROJECT_TOOL
+
+The B<capsule-init-project>(1) development tool.
+
+=cut
+
+our $CAPSULE_INIT_PROJECT_TOOL = $ENV{CAPSULE_INIT_PROJECT_TOOL};
+
+if (! length $CAPSULE_INIT_PROJECT_TOOL) {
+    $CAPSULE_INIT_PROJECT_TOOL = `$PKG_CONFIG --variable=CAPSULE_INIT_PROJECT_TOOL libcapsule-tools`;
+    chomp $CAPSULE_INIT_PROJECT_TOOL;
+}
+
+=item $CAPSULE_SYMBOLS_TOOL
+
+The B<capsule-symbols>(1) development tool.
+
+=cut
+
+our $CAPSULE_SYMBOLS_TOOL = $ENV{CAPSULE_SYMBOLS_TOOL};
+
+if (! length $CAPSULE_SYMBOLS_TOOL) {
+    $CAPSULE_SYMBOLS_TOOL = `$PKG_CONFIG --variable=CAPSULE_SYMBOLS_TOOL libcapsule-tools`;
+    chomp $CAPSULE_SYMBOLS_TOOL;
+}
+
+=item $CAPSULE_VERSION_TOOL
+
+The B<capsule-version>(1) development tool.
+
+=cut
+
+our $CAPSULE_VERSION_TOOL = $ENV{CAPSULE_VERSION_TOOL};
+
+unless (defined $CAPSULE_VERSION_TOOL) {
+    $CAPSULE_VERSION_TOOL = `$PKG_CONFIG --variable=CAPSULE_VERSION_TOOL libcapsule-tools`;
+    chomp $CAPSULE_VERSION_TOOL;
+}
+
+=item $NM
+
+The B<nm>(1) symbol-name-listing utility, configured for BSD output format.
+
+=cut
+
+our $NM = $ENV{NM};
+
+if (! length $NM) {
+    $NM = 'nm -B';
+}
+
+=item $srcdir
+
+An appropriate directory to find non-generated files: the top directory
+of the source tree, or the root directory of this package's GLib-style
+installed-tests.
+
+=cut
+
+# G_TEST_* convention stolen from GLib, even though we aren't using GTest
+my (undef, $here, undef) = File::Spec->splitpath($INC{'CapsuleTest.pm'});
+our $srcdir = $ENV{G_TEST_SRCDIR};
+$srcdir = abs_path("$here/..") unless defined $srcdir;
+
+=item $builddir
+
+An appropriate directory to find non-generated files: the top directory
+of the build tree, or the root directory of this package's GLib-style
+installed-tests.
+
+=cut
+
+our $builddir = $ENV{G_TEST_BUILDDIR};
+$builddir = abs_path("$here/..") unless defined $builddir;
+
+diag "Source or installation directory: $srcdir";
+diag "Build or installation directory: $builddir";
+
+=back
+
+=head1 EXPORTED FUNCTIONS
+
+=over
+
+=item assert_run_verbose(I<ARGV>, ...)
+
+Log the given command, run it, and die if it didn't return success.
+I<ARGV> is an array-reference containing arguments.
+Subsequent parameters are passed to C<IPC::Run::run> and can be used
+to redirect output.
+
+=cut
+
+sub assert_run_verbose {
+    my $argv = shift;
+    my $debug = join(' ', @$argv);
+    diag($debug);
+    if (! run($argv, @_)) {
+        my $explained = explain_wait_status($?);
+        die "Command exited with status $? ($explained): '$debug'";
+    }
+}
+
+=item diag_multiline(I<TEXT>)
+
+Split I<TEXT> into lines and emit them as TAP diagnostics.
+
+=cut
+
+sub diag_multiline {
+    foreach my $line (split /^/m, shift) {
+        chomp $line;
+        diag "    $line";
+    }
+}
+
+=item explain_wait_status(I<CODE>)
+
+Convert Unix-style wait status I<CODE> into something human-readable.
+
+=cut
+
+sub explain_wait_status {
+    my $status = shift;
+    my @ret;
+    my $signal = $status & 127;
+    my $code = ($status >> 8);
+
+    if ($signal) {
+        push @ret, "killed by signal $signal";
+    }
+
+    if ($status & 128) {
+        push @ret, 'core dumped';
+    }
+
+    if ($code & 128) {
+        my $maybe = $code & 127;
+        unshift @ret,
+            "exited with code $code (child process killed by signal $maybe?)";
+    }
+    elsif ($code || ! @ret) {
+        unshift @ret, "exited with code $code";
+    }
+
+    return join(', ', @ret);
+}
+
+=item run_assert_exit_status(I<STATUS>, I<ARGV>, ...)
+
+A TAP assertion that the given command exits with I<STATUS>. I<ARGV> is an
+array-reference containing arguments. Subsequent parameters are
+passed to C<IPC::Run::run> and can be used to redirect output.
+
+=cut
+
+sub run_assert_exit_status {
+    my $exp_status = shift;
+    my $argv = shift;
+    my $debug = join(' ', @$argv);
+    diag($debug);
+    run($argv, @_);
+    my $explained = explain_wait_status($?);
+    my $actual_status = ($? >> 8);
+    if ($actual_status == $exp_status) {
+        ok(1, "Command exited with status $? ($explained): '$debug'");
+    } else {
+        ok(0, "Command unexpectedly exited with status $? ($explained): '$debug'");
+    }
+}
+
+=item run_ok(I<ARGV>, ...)
+
+A TAP assertion that the given command exits 0. I<ARGV> is an
+array-reference containing arguments. Subsequent parameters are
+passed to C<IPC::Run::run> and can be used to redirect output.
+
+=cut
+
+sub run_ok {
+    my $argv = shift;
+    my $debug = join(' ', @$argv);
+    diag($debug);
+    if (run($argv, @_)) {
+        ok(1, qq{Command successful as expected: '$debug'});
+    }
+    else {
+        my $explained = explain_wait_status($?);
+        ok(0, "Command exited with status $? ($explained): '$debug'");
+    }
+}
+
+=item run_verbose(I<ARGV>, ...)
+
+Log the given command, run it, and return the same thing as
+C<IPC::Run::run>. I<ARGV> is an array-reference containing arguments.
+Subsequent parameters are passed to C<IPC::Run::run> and can be used
+to redirect output.
+
+=cut
+
+sub run_verbose {
+    my $argv = shift;
+    my $debug = join(' ', @$argv);
+    diag($debug);
+    return run($argv, @_);
+}
+
+=item skip_all_unless_bwrap()
+
+If we cannot run B<bwrap>(1), log a TAP report that all tests have been
+skipped (as if via C<plan skip_all =E<gt> ...>), and exit.
+
+=cut
+
+sub skip_all_unless_bwrap {
+    if (! run([qw(
+                env bwrap --ro-bind / / --unshare-ipc --unshare-net
+                --unshare-pid --unshare-user --unshare-uts true
+            )], '>&2')) {
+        plan(skip_all => 'Cannot run bwrap');
+    }
+}
+
+=item skip_all_unless_nm()
+
+If we cannot run B<nm>(1) to implement B<get_symbols_with_nm>, log a
+TAP report that all tests have been skipped (as if via
+C<plan skip_all =E<gt> ...>), and exit.
+
+=cut
+
+sub skip_all_unless_nm {
+    if (! run([split(' ', $NM),
+                qw(--dynamic --extern-only --defined-only
+                --with-symbol-versions /bin/true)], '>/dev/null')) {
+        plan(skip_all =>
+            'Cannot run nm (no support for --with-symbol-versions?)');
+    }
+}
+
+=item get_symbols_with_nm(I<LIBRARY>)
+
+Return a list of symbols found in I<LIBRARY>, in the same format
+that capsule-symbols would use.
+
+=cut
+
+sub get_symbols_with_nm {
+    my $library = shift;
+    my $output;
+
+    run_ok([split(' ', $NM), '--dynamic', '--extern-only', '--defined-only',
+            '--with-symbol-versions', $library], '>', \$output);
+    my @symbols_produced;
+    foreach my $line (split /\n/, $output) {
+        if ($line =~ m/^[[:xdigit:]]+\s+[ABCDGIRSTW]+\s+([^@]+)(\@\@?.*)?/) {
+            my $symbol = $1;
+            my $version = $2;
+            if (defined $version && $version =~ m/^(\@\@?[^@]+)\1$/) {
+                # Ignore doubled symbol-version suffix.
+                # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=964457
+                $version = $1;
+            }
+            require CapsuleTestDpkg;
+            next if CapsuleTestDpkg::symbol_is_blacklisted($symbol);
+            next if defined $version && "\@\@$symbol" eq $version;
+
+            # Put them in the same format that capsule-symbols uses
+            if (length $version && $version ne '@@Base') {
+                push @symbols_produced, "$symbol $version";
+            }
+            else {
+                push @symbols_produced, "$symbol ";
+            }
+        }
+    }
+    foreach my $sym (@symbols_produced) {
+        diag "- $sym";
+    }
+    return sort @symbols_produced;
+}
+
+=item bind_usr(I<TREE>[, I<DEST>])
+
+Return B<bwrap>(1) arguments that would bind-mount I<TREE> on I<DEST>.
+If I<TREE> has a F<usr> directory, it is assumed to be a complete
+sysroot with F<bin>, F<sbin>, F<lib*> and F<usr> directories, which
+will be mounted on I<DEST>. If not, it is assumed to be a merged
+F</usr>, and will be mounted on I<DEST>F</usr>, with compatibility
+symbolic links in I<DEST> for the F<bin>, F<sbin> and F<lib*>
+subdirectories.
+
+=cut
+
+sub bind_usr {
+    my $tree = shift;
+    my $dest = shift;
+    $dest = '' unless defined $dest;
+    my @bwrap;
+
+    my $has_usr = (-d "$tree/usr");
+
+    if ($has_usr) {
+        push @bwrap, '--ro-bind', "$tree/usr", "$dest/usr";
+    }
+    else {
+        push @bwrap, '--ro-bind', "$tree", "$dest/usr";
+    }
+
+    opendir(my $dir, $tree);
+    while (defined(my $subdir = readdir $dir)) {
+        if ($subdir =~ /^lib/ || $subdir =~ /^s?bin$/) {
+            if ($has_usr) {
+                push @bwrap, '--ro-bind', "$tree/$subdir", "$dest/$subdir";
+            }
+            else {
+                push @bwrap, '--symlink', "usr/$subdir", "$dest/$subdir";
+            }
+        }
+    }
+    closedir $dir;
+
+    foreach my $ldso (bsd_glob("$tree/etc/ld.so.cache"),
+                      bsd_glob("$tree/etc/ld-*.cache"),
+                      bsd_glob("$tree/etc/ld-*.path")) {
+        next unless -e $ldso;
+
+        my $base = $ldso;
+        $base =~ s,.*/,,;
+        push @bwrap, "--ro-bind", $ldso, "$dest/etc/$base";
+    }
+
+    # TODO: This wouldn't be necessary in a purely glvnd system
+    if (-d "$tree/etc/alternatives") {
+        push @bwrap, "--ro-bind", "$tree/etc/alternatives",
+            "$dest/etc/alternatives";
+    }
+
+    return @bwrap;
+}
+
+=item libcapsule_uninstalled()
+
+Return true if libcapsule is uninstalled (build-time tests) and false
+if we are carrying out installed-tests.
+
+=cut
+
+sub libcapsule_uninstalled {
+    return exists $ENV{CAPSULE_TESTS_UNINSTALLED};
+}
+
+=back
+
+=head1 ENVIRONMENT
+
+=over
+
+=item CAPSULE_INIT_PROJECT_TOOL
+
+B<capsule-init-project>(1)
+
+=item CAPSULE_SYMBOLS_TOOL
+
+B<capsule-symbols>(1)
+
+=item CAPSULE_TESTS_KEEP_TEMP
+
+If set to a non-empty value, temporary directories created by this test
+will not be cleaned up.
+
+=cut
+
+if (length $ENV{CAPSULE_TESTS_KEEP_TEMP}) {
+    $File::Temp::KEEP_ALL = 1;
+}
+
+=item CAPSULE_VERSION_TOOL
+
+B<capsule-version>(1)
+
+=item NM
+
+The B<nm>(1) symbol-name-listing utility, if not C<nm -B>.
+
+=item PKG_CONFIG
+
+B<pkg-config>(1)
+
+=back
+
+=head1 SEE ALSO
+
+B<Test::More>(3pm), B<bwrap>(1)
+
+=cut
+
+1;
diff --git a/subprojects/libcapsule/tests/CapsuleTestDpkg.pm b/subprojects/libcapsule/tests/CapsuleTestDpkg.pm
new file mode 100644
index 0000000000000000000000000000000000000000..dd23d788d0d44248466bb31bf85342fd6a8270f9
--- /dev/null
+++ b/subprojects/libcapsule/tests/CapsuleTestDpkg.pm
@@ -0,0 +1,81 @@
+# Exerpts from Dpkg::Shlibs::SymbolFile
+#
+# Copyright © 2007 Raphaël Hertzog <hertzog@debian.org>
+# Copyright © 2009-2010 Modestas Vainius <modax@debian.org>
+#
+# 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, see <https://www.gnu.org/licenses/>.
+
+package CapsuleTestDpkg;
+
+use strict;
+use warnings;
+
+my %blacklist = (
+    __bss_end__ => 1,                   # arm
+    __bss_end => 1,                     # arm
+    _bss_end__ => 1,                    # arm
+    __bss_start => 1,                   # ALL
+    __bss_start__ => 1,                 # arm
+    __data_start => 1,                  # arm
+    __do_global_ctors_aux => 1,         # ia64
+    __do_global_dtors_aux => 1,         # ia64
+    __do_jv_register_classes => 1,      # ia64
+    _DYNAMIC => 1,                      # ALL
+    _edata => 1,                        # ALL
+    _end => 1,                          # ALL
+    __end__ => 1,                       # arm
+    __exidx_end => 1,                   # armel
+    __exidx_start => 1,                 # armel
+    _fbss => 1,                         # mips, mipsel
+    _fdata => 1,                        # mips, mipsel
+    _fini => 1,                         # ALL
+    _ftext => 1,                        # mips, mipsel
+    _GLOBAL_OFFSET_TABLE_ => 1,         # hppa, mips, mipsel
+    __gmon_start__ => 1,                # hppa
+    __gnu_local_gp => 1,                # mips, mipsel
+    _gp => 1,                           # mips, mipsel
+    _init => 1,                         # ALL
+    _PROCEDURE_LINKAGE_TABLE_ => 1,     # sparc, alpha
+    _SDA2_BASE_ => 1,                   # powerpc
+    _SDA_BASE_ => 1,                    # powerpc
+);
+
+for my $i (14 .. 31) {
+    # Many powerpc specific symbols
+    $blacklist{"_restfpr_$i"} = 1;
+    $blacklist{"_restfpr_$i\_x"} = 1;
+    $blacklist{"_restgpr_$i"} = 1;
+    $blacklist{"_restgpr_$i\_x"} = 1;
+    $blacklist{"_savefpr_$i"} = 1;
+    $blacklist{"_savegpr_$i"} = 1;
+}
+
+sub symbol_is_blacklisted {
+    my ($symbol, $include_groups) = @_;
+
+    return 1 if exists $blacklist{$symbol};
+
+    # The ARM Embedded ABI spec states symbols under this namespace as
+    # possibly appearing in output objects.
+    return 1 if not ${$include_groups}{aeabi} and $symbol =~ /^__aeabi_/;
+
+    # The GNU implementation of the OpenMP spec, specifies symbols under
+    # this namespace as possibly appearing in output objects.
+    return 1 if not ${$include_groups}{gomp}
+                and $symbol =~ /^\.gomp_critical_user_/;
+
+    return 0;
+}
+
+1;
diff --git a/subprojects/libcapsule/tests/capture-libs.pl b/subprojects/libcapsule/tests/capture-libs.pl
new file mode 100755
index 0000000000000000000000000000000000000000..93a9fa06cd7e51b51b509c07f2d98a903162baad
--- /dev/null
+++ b/subprojects/libcapsule/tests/capture-libs.pl
@@ -0,0 +1,716 @@
+#!/usr/bin/perl
+
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+use autodie;
+use warnings;
+use strict;
+
+use Cwd qw(abs_path);
+use File::Temp qw();
+use IPC::Run qw(run);
+use Test::More;
+
+use FindBin;
+use lib $FindBin::Bin;
+
+use CapsuleTest;
+
+sub tolerant_readlink ($)
+{
+    no autodie;
+    return readlink shift;
+}
+
+skip_all_unless_bwrap;
+
+my $LIBDIR = qr{/(?:usr/(?:\w+-(?:\w+-)?linux-gnu\w*/)?)?lib(?:32|64|x32)?(?:/\w+-linux-gnu\w*)?(?:/i686)?(?:/cmov)?};
+
+my $test_tempdir = File::Temp->newdir();
+diag "Working directory: $test_tempdir";
+my $host = "${test_tempdir}/host";
+mkdir($host);
+my $container = "${test_tempdir}/container";
+mkdir($container);
+my $libdir = "${test_tempdir}/libdir";
+
+{
+    open(my $fh, '>', "${test_tempdir}/empty");
+    close $fh;
+}
+{
+    open(my $fh, '>', "${test_tempdir}/libc");
+    print $fh "# comment\n";
+    print $fh "\n";
+    print $fh "soname:libc.so.6\n";
+    print $fh "soname:libc.so.6";  # deliberately no trailing newline
+    close $fh;
+}
+{
+    open(my $fh, '>', "${test_tempdir}/capture-libs.ini");
+    print $fh "[Library /opt/libversionedsymbols.so.1]\n";
+    print $fh "CompareBy=versions;name;symbols;\n";
+    print $fh "[Library /opt/libversionedlikedbus.so.1]\n";
+    print $fh "CompareBy=container;\n";
+    print $fh "[Library /opt/libversionedprivatenothidden.so.1]\n";
+    print $fh "CompareBy=versions;\n";
+    print $fh "PublicSymbolVersions=!LIBVERSIONEDPRIVATE*;*;\n";
+    close $fh;
+}
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, 'soname:libc.so.6'], '>&2',
+    init => sub { chdir $libdir or die $!; });
+ok(-e "$libdir/libc.so.6", 'libc.so.6 was captured');
+like(readlink "$libdir/libc.so.6", qr{^$LIBDIR/libc\.so\.6$},
+    '$libdir/libc.so.6 is a symlink to the real libc.so.6');
+my $libc = abs_path("$libdir/libc.so.6");
+ok(-e "$libdir/libBrokenLocale.so.1", 'related libraries were captured');
+ok(-e "$libdir/libm.so.6", 'related libraries were captured');
+ok(-e "$libdir/libpthread.so.0", 'related libraries were captured');
+ok(-e "$libdir/libmemusage.so", 'related libraries were captured');
+like(readlink "$libdir/libm.so.6", qr{^$LIBDIR/libm\.so\.6$},
+    'libm is a symlink to the right place');
+my @libc_family;
+{
+    opendir(my $dir_iter, $libdir);
+    @libc_family = sort grep { $_ ne '.' and $_ ne '..' } readdir $dir_iter;
+    closedir $dir_iter;
+    foreach my $symlink (@libc_family) {
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+}
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+        "--dest=$libdir", 'soname-match:libc.so.6'], '>&2');
+{
+    opendir(my $dir_iter, $libdir);
+    my @links = sort grep { $_ ne '.' and $_ ne '..' } readdir $dir_iter;
+    foreach my $symlink (@links) {
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+    is_deeply(\@links, \@libc_family,
+        'the same libraries are captured when using --link-target');
+}
+like(readlink "$libdir/libc.so.6", qr{^/run/host\Q$libc\E$},
+     '$libdir/libc.so.6 is a symlink to /run/host + realpath of libc.so.6');
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+        '--remap-link-prefix=/run/host/=/run/media/', "--dest=$libdir",
+        'soname-match:libc.so.6'], '>&2');
+{
+    opendir(my $dir_iter, $libdir);
+    my @links = sort grep { $_ ne '.' and $_ ne '..' } readdir $dir_iter;
+    foreach my $symlink (@links) {
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+    is_deeply(\@links, \@libc_family,
+        'the same libraries are captured when using --link-target');
+}
+like(readlink "$libdir/libc.so.6", qr{^/run/media\Q$libc\E$},
+     '$libdir/libc.so.6 is a symlink to /run/media + realpath of libc.so.6');
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+        '--remap-link-prefix=/one=/two', '--remap-link-prefix=/run/ho=/run/co',
+        "--dest=$libdir", 'soname-match:libc.so.6'], '>&2');
+{
+    opendir(my $dir_iter, $libdir);
+    my @links = sort grep { $_ ne '.' and $_ ne '..' } readdir $dir_iter;
+    foreach my $symlink (@links) {
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+    is_deeply(\@links, \@libc_family,
+        'the same libraries are captured when using --link-target');
+}
+like(readlink "$libdir/libc.so.6", qr{^/run/cost\Q$libc\E$},
+     '$libdir/libc.so.6 is a symlink to /run/cost + realpath of libc.so.6');
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+        '--remap-link-prefix=/run/host2=/run/media', "--dest=$libdir",
+        'soname-match:libc.so.6'], '>&2');
+{
+    opendir(my $dir_iter, $libdir);
+    my @links = sort grep { $_ ne '.' and $_ ne '..' } readdir $dir_iter;
+    foreach my $symlink (@links) {
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+    is_deeply(\@links, \@libc_family,
+        'the same libraries are captured when using --link-target');
+}
+like(readlink "$libdir/libc.so.6", qr{^/run/host\Q$libc\E$},
+     '$libdir/libc.so.6 is a symlink to /run/host + realpath of libc.so.6');
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+# We already asserted that libc will be either in /usr/ or /lib*
+run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--remap-link-prefix=/usr/=/run/host/usr/',
+        '--remap-link-prefix=/lib=/run/host/lib', "--dest=$libdir",
+        'soname-match:libc.so.6'], '>&2');
+like(readlink "$libdir/libc.so.6", qr{^/run/host\Q$libc\E$},
+     "$libdir/libc.so.6 is a symlink to /run/host + realpath of libc.so.6");
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+# We already asserted that libc will be either in /usr/ or /lib*
+run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--remap-link-prefix=/usr/=/tmp+foo=what/usr/',
+        '--remap-link-prefix=/lib=/tmp+foo=what/lib', "--dest=$libdir",
+        'soname-match:libc.so.6'], '>&2');
+like(readlink "$libdir/libc.so.6", qr{^/tmp\+foo=what\Q$libc\E$},
+     "$libdir/libc.so.6 is a symlink to /run/host + realpath of libc.so.6");
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--remap-link-prefix=/opt/=/OPT/',
+        "--dest=$libdir", 'soname-match:libc.so.6'], '>&2');
+like(readlink "$libdir/libc.so.6", qr{^$LIBDIR/libc\.so\.6$},
+     "$libdir/libc.so.6 is a symlink to the real libc.so.6");
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+my $stderr;
+run_assert_exit_status(1, [$CAPSULE_CAPTURE_LIBS_TOOL,
+                        '--remap-link-prefix==/run/host',
+                        "--dest=$libdir", 'soname-match:libc.so.6'],
+                       '2>', \$stderr, '>&2');
+like($stderr, qr{--remap-link-prefix value must follow the FROM=TO pattern$},
+     '--remap-link-prefix should follow the pattern FROM=TO');
+
+run_assert_exit_status(1, [$CAPSULE_CAPTURE_LIBS_TOOL, '--remap-link-prefix=/usr=',
+            "--dest=$libdir", 'soname-match:libc.so.6'], '2>', \$stderr, '>&2');
+like($stderr, qr{--remap-link-prefix value must follow the FROM=TO pattern$},
+     '--remap-link-prefix should follow the pattern FROM=TO');
+
+run_assert_exit_status(1, [$CAPSULE_CAPTURE_LIBS_TOOL, '--remap-link-prefix=/usr',
+            "--dest=$libdir", 'soname-match:libc.so.6'], '2>', \$stderr, '>&2');
+like($stderr, qr{--remap-link-prefix value must follow the FROM=TO pattern$},
+     '--remap-link-prefix should follow the pattern FROM=TO');
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        '--bind', $libdir, $libdir,
+        qw(--dev-bind /dev /dev),
+        $CAPSULE_CAPTURE_LIBS_TOOL,
+        "--dest=$libdir",
+        "--provider=$host",
+        "from:$test_tempdir/empty",
+        "from:$test_tempdir/libc"], '>&2');
+{
+    opendir(my $dir_iter, $libdir);
+    my @links = sort grep { $_ ne '.' and $_ ne '..' } readdir $dir_iter;
+    foreach my $symlink (@links) {
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+    is_deeply(\@links, \@libc_family,
+        'the same libraries are captured when using $host');
+}
+like(readlink "$libdir/libc.so.6", qr{^\Q$host\E\Q$libc\E$},
+     '$libdir/libc.so.6 is a symlink to $host + realpath of libc.so.6');
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        '--bind', $libdir, $libdir,
+        qw(--dev-bind /dev /dev),
+        $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+        "--dest=$libdir",
+        "--provider=$host", 'soname:libc.so.6'], '>&2');
+{
+    opendir(my $dir_iter, $libdir);
+    my @links = sort grep { $_ ne '.' and $_ ne '..' } readdir $dir_iter;
+    foreach my $symlink (@links) {
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+    is_deeply(\@links, \@libc_family,
+        'the same libraries are captured when using $host and --link-target');
+}
+like(readlink "$libdir/libc.so.6", qr{^/run/host\Q$libc\E$},
+     '$libdir/libc.so.6 is a symlink to /run/host + realpath of libc.so.6');
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        '--bind', $libdir, $libdir,
+        qw(--dev-bind /dev /dev),
+        $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+        "--dest=$libdir", "--provider=$host",
+        'soname-match:lib*ml2.so.2*'], '>&2');
+{
+    opendir(my $dir_iter, $libdir);
+    foreach my $symlink (readdir $dir_iter) {
+        next if $symlink eq '.' || $symlink eq '..';
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+}
+like(readlink "$libdir/libxml2.so.2", qr{^$LIBDIR/libxml2\.so\.2(?:[0-9.]+)$},
+     '$libdir/libxml2.so.2 is a symlink to /run/host + realpath of libxml2');
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+my $result = run_verbose([qw(bwrap --ro-bind / / --ro-bind /), $host,
+                          '--bind', $libdir, $libdir,
+                          qw(--dev-bind /dev /dev),
+                          $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+                          "--dest=$libdir", "--provider=$host",
+                          'soname-match:this*library*does?not?exist'],
+                         '2>', \$stderr, '>&2');
+ok(! $result, 'a non-matching wildcard should fail');
+like($stderr, qr{"this\*library\*does\?not\?exist"});
+
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        '--bind', $libdir, $libdir,
+        qw(--dev-bind /dev /dev),
+        $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+        "--dest=$libdir", "--provider=$host",
+        'if-exists:soname-match:this*library*does?not?exist']);
+
+# libgobject-2.0.so.0 depends on libglib-2.0.so.0 so normally,
+# capturing GObject captures GLib
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        '--bind', $libdir, $libdir,
+        qw(--dev-bind /dev /dev),
+        $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+        "--dest=$libdir", "--provider=$host",
+        'soname:libgobject-2.0.so.0']);
+{
+    opendir(my $dir_iter, $libdir);
+    foreach my $symlink (readdir $dir_iter) {
+        next if $symlink eq '.' || $symlink eq '..';
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+}
+like(readlink "$libdir/libgobject-2.0.so.0",
+     qr{^$LIBDIR/libgobject-2\.0\.so\.0(?:[0-9.]+)$},
+     '$libdir/libgobject-2.0.so.0 is captured');
+like(readlink "$libdir/libglib-2.0.so.0",
+     qr{^$LIBDIR/libglib-2\.0\.so\.0(?:[0-9.]+)$},
+     '$libdir/libglib-2.0.so.0 is captured as a dependency');
+
+# only-dependencies: captures the dependency, GLib but not the
+# dependent library, GObject
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        '--bind', $libdir, $libdir,
+        qw(--dev-bind /dev /dev),
+        $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+        "--dest=$libdir", "--provider=$host",
+        'only-dependencies:soname:libgobject-2.0.so.0']);
+{
+    opendir(my $dir_iter, $libdir);
+    foreach my $symlink (readdir $dir_iter) {
+        next if $symlink eq '.' || $symlink eq '..';
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+}
+ok(! -e "$libdir/libgobject-2.0.so.0",
+   'only-dependencies: does not capture the library itself');
+like(readlink "$libdir/libglib-2.0.so.0",
+     qr{^$LIBDIR/libglib-2\.0\.so\.0(?:[0-9.]+)$},
+     '$libdir/libglib-2.0.so.0 is captured as a dependency');
+
+# no-dependencies: captures the dependent library, GObject but not the
+# dependency, GLib (not amazingly useful)
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        '--bind', $libdir, $libdir,
+        qw(--dev-bind /dev /dev),
+        $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+        "--dest=$libdir", "--provider=$host",
+        'no-dependencies:soname:libgobject-2.0.so.0']);
+{
+    opendir(my $dir_iter, $libdir);
+    foreach my $symlink (readdir $dir_iter) {
+        next if $symlink eq '.' || $symlink eq '..';
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+}
+like(readlink "$libdir/libgobject-2.0.so.0",
+     qr{^$LIBDIR/libgobject-2\.0\.so\.0(?:[0-9.]+)$},
+     '$libdir/libgobject-2.0.so.0 is captured');
+ok(! -e "$libdir/libglib-2.0.so.0",
+   'no-dependencies: does not capture libglib-2.0.so.0');
+
+# --no-glibc doesn't capture glibc even if other dependencies are
+# captured
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        '--bind', $libdir, $libdir,
+        qw(--dev-bind /dev /dev),
+        $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+        "--dest=$libdir", "--provider=$host", "--no-glibc",
+        'soname:libgobject-2.0.so.0']);
+{
+    opendir(my $dir_iter, $libdir);
+    foreach my $symlink (readdir $dir_iter) {
+        next if $symlink eq '.' || $symlink eq '..';
+        diag "- $symlink -> ".readlink("$libdir/$symlink");
+    }
+    closedir $dir_iter;
+}
+like(readlink "$libdir/libgobject-2.0.so.0",
+     qr{^$LIBDIR/libgobject-2\.0\.so\.0(?:[0-9.]+)$},
+     '$libdir/libgobject-2.0.so.0 is captured');
+like(readlink "$libdir/libglib-2.0.so.0",
+     qr{^$LIBDIR/libglib-2\.0\.so\.0(?:[0-9.]+)$},
+     '$libdir/libglib-2.0.so.0 is captured');
+ok(! -e "$libdir/libc.so.6",
+   '--no-glibc does not capture libc.so.6');
+
+# only-dependencies:no-dependencies: (either way round) is completely useless
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+$result = run_verbose([qw(bwrap --ro-bind / / --ro-bind /), $host,
+                       '--bind', $libdir, $libdir,
+                       qw(--dev-bind /dev /dev),
+                       $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+                       "--dest=$libdir", "--provider=$host",
+                       'only-dependencies:no-dependencies:libglib-2.0.so.0'],
+                       '>&2');
+ok(! $result, 'only-dependencies:no-dependencies: is useless');
+run_ok(['rm', '-fr', $libdir]);
+mkdir($libdir);
+$result = run_verbose([qw(bwrap --ro-bind / / --ro-bind /), $host,
+                       '--bind', $libdir, $libdir,
+                       qw(--dev-bind /dev /dev),
+                       $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+                       "--dest=$libdir", "--provider=$host",
+                       'no-dependencies:only-dependencies:libglib-2.0.so.0'],
+                       '>&2');
+ok(! $result, 'no-dependencies:only-dependencies: is useless');
+
+SKIP: {
+    skip "not on Linux? Good luck!", 1 unless $^O eq 'linux';
+    my $stdout;
+    my $ld_so;
+
+    run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--print-ld.so'], '>', \$ld_so);
+    chomp $ld_so;
+    diag "ld.so is $ld_so";
+
+    run_ok([$CAPSULE_CAPTURE_LIBS_TOOL, '--resolve-ld.so=/'], '>', \$stdout);
+    my $resolved = $stdout;
+    chomp $resolved;
+
+    is(abs_path($resolved), abs_path($ld_so),
+       '--resolve-ld.so=/ should print a path to '.abs_path($ld_so));
+    ok(! -l $resolved,
+       '--resolve-ld.so=/ should not print a symlink');
+
+    # Autotools considers an unexpected pass to be a test failure, so we
+    # have to second-guess whether this is going to work.
+    if ($stdout eq abs_path($ld_so)."\n") {
+        is($stdout, abs_path($ld_so)."\n",
+           '--resolve-ld.so=/ should print '.abs_path($ld_so));
+    }
+    else {
+        TODO: {
+            local $TODO = 'symlinks before the last component are not resolved, '
+                .'e.g. /lib64 -> usr/lib on Arch Linux';
+            is($stdout, abs_path($ld_so)."\n",
+               '--resolve-ld.so=/ should print '.abs_path($ld_so));
+        }
+    }
+
+    run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+            $CAPSULE_CAPTURE_LIBS_TOOL, "--resolve-ld.so=$host"],
+        '>', \$stdout);
+    $resolved = $stdout;
+    chomp $resolved;
+
+    is(abs_path($resolved), abs_path($ld_so),
+       "--resolve-ld.so=$host should print a path to ".abs_path($ld_so));
+    ok(! -l $resolved,
+       "--resolve-ld.so=$host should not print a symlink");
+
+    if ($stdout eq abs_path($ld_so)."\n") {
+        is($stdout, abs_path($ld_so)."\n",
+           "--resolve-ld.so=$host should print ".abs_path($ld_so));
+    }
+    else {
+        TODO: {
+            local $TODO = 'symlinks before the last component are not resolved, '
+                .'e.g. /lib64 -> usr/lib on Arch Linux';
+            is($stdout, abs_path($ld_so)."\n",
+               "--resolve-ld.so=$host should print ".abs_path($ld_so));
+        }
+    }
+};
+
+SKIP: {
+    my $multiarch;
+    my $other_multiarch;
+
+    # For simplicity, we only consider this case on x86.
+    skip "$ENV{CAPSULE_TESTS_GNU_HOST} not x86_64 or i386", 1
+        unless $ENV{CAPSULE_TESTS_GNU_HOST} =~ m/^(x86_64|i.86)-/;
+
+    if ($ENV{CAPSULE_TESTS_GNU_HOST} =~ m/^x86_64-/) {
+        $other_multiarch = 'i386-linux-gnu';
+    }
+    else {
+        $other_multiarch = 'x86_64-linux-gnu';
+    }
+
+    skip "$other_multiarch libxml2.so.2 not available", 1
+        unless -e "/usr/lib/$other_multiarch/libxml2.so.2";
+
+    # Normally, the wrong ABI is an error...
+    run_ok(['rm', '-fr', $libdir]);
+    mkdir($libdir);
+    $result = run_verbose([qw(bwrap --ro-bind / / --ro-bind /), $host,
+                           '--bind', $libdir, $libdir,
+                           qw(--dev-bind /dev /dev),
+                           $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+                           "--dest=$libdir", "--provider=$host",
+                           "path:/usr/lib/$other_multiarch/libxml2.so.2"],
+                           '>&2');
+    ok(! $result, 'library of wrong ABI yields an error');
+    ok(! -e "$libdir/libxml2.so.2");
+
+    # ... but when we're dealing with a glob match, other ABIs are silently
+    # ignored.
+    run_ok(['rm', '-fr', $libdir]);
+    mkdir($libdir);
+    $result = run_verbose([qw(bwrap --ro-bind / / --ro-bind /), $host,
+                           '--bind', $libdir, $libdir,
+                           qw(--dev-bind /dev /dev),
+                           $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+                           "--dest=$libdir", "--provider=$host",
+                           "path-match:/usr/lib/$other_multiarch/libxml2.so.2"],
+                           '>&2');
+    ok($result, 'library of wrong ABI ignored when using path-match');
+    ok(! -e "$libdir/libxml2.so.2");
+
+    # We can also ignore this case explicitly.
+    run_ok(['rm', '-fr', $libdir]);
+    mkdir($libdir);
+    $result = run_verbose([qw(bwrap --ro-bind / / --ro-bind /), $host,
+                           '--bind', $libdir, $libdir,
+                           qw(--dev-bind /dev /dev),
+                           $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/',
+                           "--dest=$libdir", "--provider=$host",
+                           "if-same-abi:path:/usr/lib/$other_multiarch/libxml2.so.2"],
+                           '>&2');
+    ok($result, 'library of wrong ABI ignored when using if-same-abi');
+    ok(! -e "$libdir/libxml2.so.2");
+};
+
+SKIP: {
+    my $version1 = "$builddir/tests/version1";
+    $version1 .= '/.libs' if libcapsule_uninstalled;
+    my $version2 = "$builddir/tests/version2";
+    $version2 .= '/.libs' if libcapsule_uninstalled;
+
+    skip "shared libraries disabled", 1 unless -e "$version1/libunversionedsymbols.so.1";
+
+    run_ok(['rm', '-fr', $libdir]);
+    mkdir($libdir);
+    $result = run_verbose([qw(bwrap --ro-bind / /),
+                           '--tmpfs', $host,
+                           bind_usr('/', $host),
+                           '--tmpfs', $container,
+                           bind_usr('/', $container),
+                           '--ro-bind', $version1, "$host/opt",
+                           '--ro-bind', $version2, "$container/opt",
+                           '--bind', $libdir, $libdir,
+                           qw(--dev-bind /dev /dev),
+                           'env', 'CAPSULE_DEBUG=all',
+                           $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+                           "--dest=$libdir", "--provider=$host",
+                           "--compare-by=versions,name,symbols,provider",
+                           "--container=$container",
+                           'path-match:/opt/lib*.so.1'],
+                           '2>', \$stderr, '>&2');
+    diag $stderr;
+    ok($result);
+    is(tolerant_readlink("$libdir/libunversionedabibreak.so.1"),
+       '/run/host/opt/libunversionedabibreak.so.1.0.0',
+       "should take provider's version when neither's symbols are a superset of the other");
+    ok(! -l "$libdir/libunversionednumber.so.1",
+       "should not take provider's version when container's numeric tail is strictly newer");
+    ok(! -l "$libdir/libunversionedsymbols.so.1",
+       "should not take provider's version when container has strictly more symbols");
+    is(tolerant_readlink("$libdir/libversionedabibreak.so.1"),
+       '/run/host/opt/libversionedabibreak.so.1.0.0',
+       "should take provider's version when neither's symbol-versions are a superset of the other");
+    ok(! -l "$libdir/libversionedlikedbus.so.1",
+       "should not take provider's older fake libdbus");
+    ok(! -l "$libdir/libversionedlikeglibc.so.1",
+       "should not take provider's older fake glibc");
+    ok(! -l "$libdir/libversionednumber.so.1",
+       "should not take provider's version when container's numeric tail is strictly newer and symbols are equal");
+    ok(! -l "$libdir/libversionedsymbols.so.1",
+       "should not take provider's version when container has strictly more symbols");
+    ok(! -l "$libdir/libversionedupgrade.so.1",
+       "should not take provider's version when container has strictly more symbols");
+
+    # The opposite.
+    run_ok(['rm', '-fr', $libdir]);
+    mkdir($libdir);
+    $result = run_verbose([qw(bwrap --ro-bind / /),
+                           '--tmpfs', $host,
+                           bind_usr('/', $host),
+                           '--tmpfs', $container,
+                           bind_usr('/', $container),
+                           '--ro-bind', $version1, "$container/opt",
+                           '--ro-bind', $version2, "$host/opt",
+                           '--bind', $libdir, $libdir,
+                           qw(--dev-bind /dev /dev),
+                           'env', 'CAPSULE_DEBUG=all',
+                           $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+                           "--dest=$libdir", "--provider=$host",
+                           # This time we leave the trailing ,provider implict
+                           "--compare-by=versions,name,symbols",
+                           "--container=$container",
+                           'path-match:/opt/lib*.so.1'],
+                           '2>', \$stderr, '>&2');
+    diag $stderr;
+    ok($result);
+    is(tolerant_readlink("$libdir/libunversionedabibreak.so.1"),
+       '/run/host/opt/libunversionedabibreak.so.1.0.0',
+       "should take provider's version when neither's symbols are a superset of the other");
+    is(tolerant_readlink("$libdir/libunversionednumber.so.1"),
+       '/run/host/opt/libunversionednumber.so.1.2.3',
+       "should take provider's version when container's numeric tail is strictly older");
+    is(tolerant_readlink("$libdir/libunversionedsymbols.so.1"),
+       '/run/host/opt/libunversionedsymbols.so.1.0.0',
+       "should take provider's version when it has strictly more symbols than container");
+    is(tolerant_readlink("$libdir/libversionedabibreak.so.1"),
+       '/run/host/opt/libversionedabibreak.so.1.0.0',
+       "should take provider's version when neither's symbol-versions are a superset of the other");
+    is(tolerant_readlink("$libdir/libversionedlikedbus.so.1"),
+       '/run/host/opt/libversionedlikedbus.so.1.2.0',
+       "should take provider's newer fake libdbus");
+    is(tolerant_readlink("$libdir/libversionedlikeglibc.so.1"),
+       '/run/host/opt/libversionedlikeglibc.so.1.0.0',
+       "should take provider's newer fake glibc");
+    is(tolerant_readlink("$libdir/libversionednumber.so.1"),
+       '/run/host/opt/libversionednumber.so.1.2.3',
+       "should take provider's version when its numeric tail is strictly newer and symbols are equal");
+    is(tolerant_readlink("$libdir/libversionedsymbols.so.1"),
+       '/run/host/opt/libversionedsymbols.so.1.0.0',
+       "should take provider's version when it has strictly more symbols");
+    is(tolerant_readlink("$libdir/libversionedupgrade.so.1"),
+       '/run/host/opt/libversionedupgrade.so.1.0.0',
+       "should take provider's version when it has strictly more symbols");
+
+    # The default is equivalent to --compare-by=name,provider.
+    run_ok(['rm', '-fr', $libdir]);
+    mkdir($libdir);
+    $result = run_verbose([qw(bwrap --ro-bind / /),
+                           '--tmpfs', $host,
+                           bind_usr('/', $host),
+                           '--tmpfs', $container,
+                           bind_usr('/', $container),
+                           '--ro-bind', $version1, "$host/opt",
+                           '--ro-bind', $version2, "$container/opt",
+                           '--bind', $libdir, $libdir,
+                           qw(--dev-bind /dev /dev),
+                           'env', 'CAPSULE_DEBUG=all',
+                           $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+                           "--dest=$libdir", "--provider=$host",
+                           "--container=$container",
+                           'path-match:/opt/lib*.so.1'],
+                           '2>', \$stderr, '>&2');
+    diag $stderr;
+    ok($result);
+    is(tolerant_readlink("$libdir/libunversionedabibreak.so.1"),
+       '/run/host/opt/libunversionedabibreak.so.1.0.0',
+       "should take provider's version by default if name is the same");
+    is(tolerant_readlink("$libdir/libunversionedsymbols.so.1"),
+       '/run/host/opt/libunversionedsymbols.so.1.0.0',
+       "should take provider's version by default if name is the same");
+    is(tolerant_readlink("$libdir/libversionedabibreak.so.1"),
+       '/run/host/opt/libversionedabibreak.so.1.0.0',
+       "should take provider's version by default if name is the same");
+    is(tolerant_readlink("$libdir/libversionedsymbols.so.1"),
+       '/run/host/opt/libversionedsymbols.so.1.0.0',
+       "should take provider's version by default if name is the same");
+    is(tolerant_readlink("$libdir/libversionedupgrade.so.1"),
+       '/run/host/opt/libversionedupgrade.so.1.0.0',
+       "should take provider's version by default if name is the same");
+    is(tolerant_readlink("$libdir/libversionedlikeglibc.so.1"),
+       '/run/host/opt/libversionedlikeglibc.so.1.0.0',
+       "should take provider's version by default if name is the same");
+    ok(! -l "$libdir/libunversionednumber.so.1",
+       "should not take provider's version when container's numeric tail is newer");
+    ok(! -l "$libdir/libversionedlikedbus.so.1",
+       "should not take provider's version when container's numeric tail is newer");
+    ok(! -l "$libdir/libversionednumber.so.1",
+       "should not take provider's version when container's numeric tail is newer");
+
+    run_ok(['rm', '-fr', $libdir]);
+    mkdir($libdir);
+    $result = run_verbose([qw(bwrap --ro-bind / /),
+                           '--tmpfs', $host,
+                           bind_usr('/', $host),
+                           '--tmpfs', $container,
+                           bind_usr('/', $container),
+                           '--ro-bind', $version1, "$host/opt",
+                           '--ro-bind', $version2, "$container/opt",
+                           '--ro-bind', "$test_tempdir/capture-libs.ini",
+                                        "$test_tempdir/capture-libs.ini",
+                           '--bind', $libdir, $libdir,
+                           qw(--dev-bind /dev /dev),
+                           'env', 'CAPSULE_DEBUG=all',
+                           $CAPSULE_CAPTURE_LIBS_TOOL, '--link-target=/run/host',
+                           "--dest=$libdir", "--provider=$host",
+                           "--container=$container",
+                           "--library-knowledge=$test_tempdir/capture-libs.ini",
+                           'path-match:/opt/lib*.so.1'],
+                           '2>', \$stderr, '>&2');
+    diag $stderr;
+    ok($result);
+    ok(! -l "$libdir/libversionedsymbols.so.1",
+       "should not take provider's version when told to look at container's symbols");
+    ok(! -l "$libdir/libversionedlikedbus.so.1",
+       "should not take provider's version when forced to use container's");
+    is(tolerant_readlink("$libdir/libversionedprivatenothidden.so.1"),
+       '/run/host/opt/libversionedprivatenothidden.so.1.1.1',
+       "should take provider's version by default when they have the same symbol versions");
+}
+
+done_testing;
+
+# vim:set sw=4 sts=4 et:
diff --git a/subprojects/libcapsule/tests/gl-shim.pl b/subprojects/libcapsule/tests/gl-shim.pl
new file mode 100755
index 0000000000000000000000000000000000000000..3ff45e451da56eb0e2b519d0e716c921e939b75c
--- /dev/null
+++ b/subprojects/libcapsule/tests/gl-shim.pl
@@ -0,0 +1,115 @@
+#!/usr/bin/perl
+
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+use autodie;
+use warnings;
+use strict;
+
+use File::Temp qw();
+use IPC::Run qw(run);
+use Test::More;
+
+use FindBin;
+use lib $FindBin::Bin;
+
+use CapsuleTest;
+
+skip_all_unless_nm;
+
+my $test_tempdir = File::Temp->newdir();
+diag "Working directory: $test_tempdir";
+chdir $test_tempdir;
+
+my ($output, $ignored, $error);
+
+my @libcapsule_environment;
+if (length $ENV{CAPSULE_TESTS_UNINSTALLED}) {
+    # Make sure the shim can load the just-built libcapsule
+    push @libcapsule_environment, "LD_LIBRARY_PATH=$ENV{G_TEST_BUILDDIR}/.libs";
+}
+delete $ENV{MAKEFLAGS};
+
+my $examples = "$srcdir/examples";
+
+my @sonames = qw(libEGL.so.1 libGL.so.1 libGLESv2.so.2 libGLX.so.0
+    libOpenGL.so.0 libX11.so.6 libXext.so.6 libXi.so.6 libgbm.so.1
+    libxcb-dri2.so.0 libxcb-glx.so.0 libxcb-present.so.0 libxcb-sync.so.1
+    libxcb.so.1);
+
+my @configure_arguments = ();
+
+if (length $ENV{CAPSULE_TESTS_GNU_BUILD}) {
+    push @configure_arguments, '--build', $ENV{CAPSULE_TESTS_GNU_BUILD};
+}
+
+if (length $ENV{CAPSULE_TESTS_GNU_HOST}) {
+    push @configure_arguments, '--host', $ENV{CAPSULE_TESTS_GNU_HOST};
+}
+
+# Don't require running capsule-version - just use the major versions
+sub use_major_version {
+    my $soname = shift;
+    if ($soname =~ m/.*\.([0-9]+)$/) {
+        return "$soname/$1";
+    }
+    else {
+        die "$soname doesn't end with a major version";
+    }
+}
+
+run_ok([$CAPSULE_INIT_PROJECT_TOOL,
+        '--runtime-tree=/run/host',
+        '--set-version=1.0.0',
+        "--symbols-from=$examples/shim",
+        map { use_major_version($_) } @sonames,
+    ]);
+# The default name for the output is taken from the first library,
+# which in this case happens to be libEGL-proxy
+run_ok([
+        'sh', '-euc', 'cd "$1"; shift; ./configure "$@"',
+        'sh', "$test_tempdir/libEGL-proxy",
+        @configure_arguments,
+    ], '>&2');
+run_ok(['make', '-C', "$test_tempdir/libEGL-proxy", 'V=1'], '>&2');
+
+foreach my $soname (@sonames) {
+    my $basename = $soname;
+    $basename =~ s/\.so\..*$//;
+
+    ok(-e "$test_tempdir/libEGL-proxy/$basename.la");
+    ok(-e "$test_tempdir/libEGL-proxy/.libs/$basename.so");
+    ok(-e "$test_tempdir/libEGL-proxy/.libs/$soname");
+    ok(-e "$test_tempdir/libEGL-proxy/.libs/$soname.0.0");
+
+    my @symbols_wanted;
+    open my $fh, "$examples/shim/$soname.symbols";
+    while (defined(my $line = <$fh>)) {
+        chomp $line;
+        push @symbols_wanted, $line;
+    }
+
+    my @symbols_produced =
+        get_symbols_with_nm("$test_tempdir/libEGL-proxy/.libs/$soname");
+    is_deeply \@symbols_wanted, [grep {! m/^capsule_meta $/} @symbols_produced];
+}
+
+chdir '/';
+done_testing;
+
+# vim:set sw=4 sts=4 et:
diff --git a/subprojects/libcapsule/tests/glib-tap.sh b/subprojects/libcapsule/tests/glib-tap.sh
new file mode 100755
index 0000000000000000000000000000000000000000..ccb44b0435a160d4051cb0bc57df708d7dcc101e
--- /dev/null
+++ b/subprojects/libcapsule/tests/glib-tap.sh
@@ -0,0 +1,14 @@
+#!/bin/sh
+# Wrapper to make GTest tests output TAP syntax, because Automake's test
+# drivers do not currently support passing the same command-line argument
+# to each test executable. All GTest tests produce TAP output if invoked
+# with the --tap option.
+#
+# Usage: "glib-tap.sh test-foo --verbose ..." is equivalent to
+# "test-foo --tap --verbose ..."
+
+set -e
+t="$1"
+shift
+
+exec "$t" --tap "$@"
diff --git a/subprojects/libcapsule/tests/init-project.pl b/subprojects/libcapsule/tests/init-project.pl
new file mode 100755
index 0000000000000000000000000000000000000000..6781c8e90fb8af202e47cdcc47fbaad7f2cc931f
--- /dev/null
+++ b/subprojects/libcapsule/tests/init-project.pl
@@ -0,0 +1,237 @@
+#!/usr/bin/perl
+
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+use autodie;
+use warnings;
+use strict;
+
+use File::Temp qw();
+use IPC::Run qw(run);
+use Test::More;
+
+use FindBin;
+use lib $FindBin::Bin;
+
+use CapsuleTest;
+
+skip_all_unless_nm;
+
+my $test_tempdir = File::Temp->newdir();
+diag "Working directory: $test_tempdir";
+chdir $test_tempdir;
+
+my ($output, $ignored, $error);
+
+my @libcapsule_environment;
+if (length $ENV{CAPSULE_TESTS_UNINSTALLED}) {
+    # Make sure the shim can load the just-built libcapsule
+    push @libcapsule_environment, "LD_LIBRARY_PATH=$ENV{G_TEST_BUILDDIR}/.libs";
+}
+delete $ENV{MAKEFLAGS};
+
+my @configure_arguments = ();
+
+if (length $ENV{CAPSULE_TESTS_GNU_BUILD}) {
+    push @configure_arguments, '--build', $ENV{CAPSULE_TESTS_GNU_BUILD};
+}
+
+if (length $ENV{CAPSULE_TESTS_GNU_HOST}) {
+    push @configure_arguments, '--host', $ENV{CAPSULE_TESTS_GNU_HOST};
+}
+
+run_ok([$CAPSULE_INIT_PROJECT_TOOL,
+        '--search-tree=/',
+        '--runtime-tree=/run/host',
+        'libz.so.1/1.2.3',
+        'libxml2.so.2']);
+run_ok([
+        'sh', '-euc', 'cd "$1"; shift; ./configure "$@"',
+        'sh', "$test_tempdir/libz-proxy",
+        @configure_arguments,
+        '--with-search-tree=/',
+        '--with-runtime-tree=/host',
+    ], '>&2');
+run_ok(['make', '-C', "$test_tempdir/libz-proxy", 'V=1'], '>&2');
+ok(-e "$test_tempdir/libz-proxy/libz.la");
+ok(-e "$test_tempdir/libz-proxy/.libs/libz.so");
+ok(-e "$test_tempdir/libz-proxy/.libs/libz.so.1");
+ok(-e "$test_tempdir/libz-proxy/.libs/libz.so.1.2.3");
+ok(-e "$test_tempdir/libz-proxy/libxml2.la");
+ok(-e "$test_tempdir/libz-proxy/.libs/libxml2.so");
+ok(-e "$test_tempdir/libz-proxy/.libs/libxml2.so.2");
+ok(-e "$test_tempdir/libz-proxy/.libs/libxml2.so.2.0.0");
+{
+    local $/ = undef;   # read entire file in one go
+    open(my $fh, '<', "$test_tempdir/libz-proxy/shim/libz.so.1.c");
+    my $source = <$fh>;
+    like($source, qr{\.default_prefix\s*=\s*"/host"},
+        'Configure-time runtime tree takes precedence');
+    unlike($source, qr{\.default_prefix\s*=\s*"/run/host"},
+        'Init-time runtime tree is not used');
+    unlike($source, qr{\.default_prefix\s*=\s*"/"},
+        'Search tree is not used at runtime');
+    close $fh;
+}
+{
+    local $/ = undef;   # read entire file in one go
+    open(my $fh, '<', "$test_tempdir/libz-proxy/shim/libxml2.so.2.c");
+    my $source = <$fh>;
+    like($source, qr{\.default_prefix\s*=\s*"/host"},
+        'Configure-time runtime tree takes precedence');
+    unlike($source, qr{\.default_prefix\s*=\s*"/run/host"},
+        'Init-time runtime tree is not used');
+    unlike($source, qr{\.default_prefix\s*=\s*"/"},
+        'Search tree is not used at runtime');
+    close $fh;
+}
+
+run_ok([$CAPSULE_SYMBOLS_TOOL, 'libz.so.1'],
+    '>', \$output);
+my @symbols_wanted = sort(split /\n/, $output);
+foreach my $sym (@symbols_wanted) {
+    diag "- $sym";
+}
+
+sub uniq {
+    my @unique;
+    my %seen;
+    foreach my $item (@_) {
+        push @unique, $item unless $seen{$item};
+        $seen{$item} = 1;
+    }
+    return @unique;
+}
+
+my @symbols_produced =
+    grep { !/^ZLIB_\d+\./ }
+    get_symbols_with_nm("$test_tempdir/libz-proxy/.libs/libz.so.1");
+is_deeply \@symbols_wanted, [grep {! m/^capsule_meta $/} @symbols_produced];
+
+# Do the same thing with capsule-symbols.
+# We can't load the library unless we let it load its real
+# implementation, and it's hardwired to get that from /host, which
+# probably doesn't exist... so cheat by overriding it.
+run_ok(['env', "CAPSULE_PREFIX=/", @libcapsule_environment,
+        $CAPSULE_SYMBOLS_TOOL, "$test_tempdir/libz-proxy/.libs/libz.so.1"],
+    '>', \$output);
+@symbols_produced = grep { !/capsule_meta\b/ } sort(split /\n/, $output);
+foreach my $sym (@symbols_produced) {
+    diag "- $sym";
+}
+
+if ($symbols_produced[0] eq $symbols_produced[1]) {
+    TODO: {
+        local $TODO = 'Each symbol shows up twice on some OSs';
+        is_deeply \@symbols_wanted, \@symbols_produced;
+    }
+}
+
+is_deeply \@symbols_wanted, [uniq @symbols_produced];
+
+# Make sure the symbols list appears older than the shared list.
+# Exclude one symbol, effectively behaving as though we'd used an
+# older version of zlib that didn't have it.
+{
+    open(my $fh, '>', "$test_tempdir/libz-proxy/shim/libz.so.1.symbols");
+    foreach my $symbol (@symbols_wanted) {
+        print $fh "$symbol\n" unless $symbol =~ m/^zlibCompileFlags/;
+    }
+    close($fh);
+}
+run_ok(['touch', '-d', '1980-01-01 00:00',
+        "$test_tempdir/libz-proxy/shim/libz.so.1.symbols"], '>&2');
+# This doesn't fail or regenerate the symbols list
+run_ok(['make', '-C', "$test_tempdir/libz-proxy", 'V=1'], '>&2');
+{
+    local $/ = undef;   # read entire file in one go
+    open(my $fh, '<', "$test_tempdir/libz-proxy/shim/libz.so.1.symbols");
+    my $symbols = <$fh>;
+    unlike($symbols, qr{^zlibCompileFlags}m);
+    close $fh;
+}
+
+# Make the shared list have actually differing contents
+run_ok(['touch', '-d', '1980-01-01 00:00',
+        "$test_tempdir/libz-proxy/shim/libz.so.1.symbols"], '>&2');
+rename("$test_tempdir/libz-proxy/shim/libz.so.1.shared",
+    "$test_tempdir/libz-proxy/shim/libz.so.1.shared.good");
+{
+    open(my $fh, '>', "$test_tempdir/libz-proxy/shim/libz.so.1.shared");
+    print $fh "libfoo.so.2\n";
+    print $fh "libbar.so.3\n";
+    close($fh);
+}
+# This does fail
+run(['make', '-C', "$test_tempdir/libz-proxy", 'V=1'],
+    '>', \$ignored, '2>', \$error);
+diag_multiline $error;
+like($error, qr{ERROR.*make maintainer-update-capsule-symbols}s);
+# ... and does not fix the problem
+run(['make', '-C', "$test_tempdir/libz-proxy", 'V=1'],
+    '>', \$ignored, '2>', \$error);
+diag_multiline $error;
+like($error, qr{ERROR.*make maintainer-update-capsule-symbols}s);
+
+# Pretend we had listed the symbols of libfoo and libbar, and go back
+# to just zlib
+rename("$test_tempdir/libz-proxy/shim/libz.so.1.shared",
+    "$test_tempdir/libz-proxy/shim/libz.so.1.symbols.updated-for");
+rename("$test_tempdir/libz-proxy/shim/libz.so.1.shared.good",
+    "$test_tempdir/libz-proxy/shim/libz.so.1.shared");
+{
+    open(my $fh, '>', "$test_tempdir/libz-proxy/shim/libz.so.1.symbols");
+    close($fh);
+}
+run_ok(['make', '-C', "$test_tempdir/libz-proxy", 'V=1',
+        'maintainer-update-capsule-symbols'], '>&2');
+# The file has been updated with the full list of symbols
+{
+    local $/ = undef;   # read entire file in one go
+    open(my $fh, '<', "$test_tempdir/libz-proxy/shim/libz.so.1.symbols");
+    my $symbols = <$fh>;
+    like($symbols, qr{^zlibCompileFlags}m);
+    close $fh;
+}
+run_ok(['make', '-C', "$test_tempdir/libz-proxy", 'V=1'], '>&2');
+
+@symbols_produced =
+    grep { !/^ZLIB_\d+\./ }
+    get_symbols_with_nm("$test_tempdir/libz-proxy/.libs/libz.so.1");
+is_deeply \@symbols_wanted, [grep {! m/^capsule_meta $/} @symbols_produced];
+
+run_ok(['env', "CAPSULE_PREFIX=/", @libcapsule_environment,
+        $CAPSULE_SYMBOLS_TOOL, "$test_tempdir/libz-proxy/.libs/libz.so.1"],
+    '>', \$output);
+@symbols_produced = grep { !/^capsule_meta\b/ } sort(split /\n/, $output);
+foreach my $sym (@symbols_produced) {
+    diag "- $sym";
+}
+
+if ($symbols_produced[0] eq $symbols_produced[1]) {
+    TODO: {
+        local $TODO = 'Each symbol shows up twice on some OSs';
+        is_deeply \@symbols_wanted, \@symbols_produced;
+    }
+}
+is_deeply \@symbols_wanted, [uniq @symbols_produced];
+
+chdir '/';
+done_testing;
+
+# vim:set sw=4 sts=4 et:
diff --git a/subprojects/libcapsule/tests/libversioned.map b/subprojects/libcapsule/tests/libversioned.map
new file mode 100644
index 0000000000000000000000000000000000000000..72eb3014514adb75ee9bab73e98f34ddbb231c5d
--- /dev/null
+++ b/subprojects/libcapsule/tests/libversioned.map
@@ -0,0 +1,4 @@
+LIBVERSIONED1 {
+  global:
+    *;
+};
diff --git a/subprojects/libcapsule/tests/manual/gl.pl b/subprojects/libcapsule/tests/manual/gl.pl
new file mode 100755
index 0000000000000000000000000000000000000000..2310d6e626b77b4bedf65f11849bf2746008bcef
--- /dev/null
+++ b/subprojects/libcapsule/tests/manual/gl.pl
@@ -0,0 +1,1052 @@
+#!/usr/bin/env perl
+
+# gl.pl — exercise libGL + bwrap, with or without libcapsule
+#
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+use warnings;
+use strict;
+
+use autodie;
+use Cwd qw(realpath);
+use File::Path qw(make_path);
+use File::Spec qw();
+use File::Temp qw();
+use Getopt::Long;
+use IPC::Run qw(run);
+use Sort::Versions;
+use Test::More;
+
+use FindBin;
+use lib ($FindBin::Bin, "$FindBin::Bin/..");
+use CapsuleTest;
+
+=encoding utf8
+
+=head1 NAME
+
+gl.pl - exercise libGL + bwrap, with or without libcapsule
+
+=head1 SYNOPSIS
+
+B<gl.pl> [I<OPTIONS>] [I<COMMAND> [I<ARGUMENTS...>]]
+
+Run C<I<COMMAND> I<ARGUMENTS...>> in the container, or if no command
+is given, run B<glxinfo>(1) and make some assertions about its output.
+
+The GL stack is made available in the container as F</overrides/lib/*>.
+This can either be the host system's GL stack (provided as symbolic
+links into F</run/host/{usr/,}lib*>), a different sysroot's GL stack
+(provided as symbolic links into F</run/host/some-sysroot/{usr/,}lib*>), a
+standalone GL stack (for example proprietary binaries), or a libcapsule
+shim that will load the real GL stack from F</run/host> or
+F</run/host/some-sysroot>.
+
+F</usr>, F</bin>, F</sbin>, F</lib*>, F</etc/alternatives> and
+F</etc/ld.so.cache> from the host system or a sysroot are always mounted
+at F</run/host> or F</run/host/some-sysroot>.
+
+By default, glibc is taken from either the GL provider or the container,
+whichever has the newer version. It can also be taken from a third tree.
+If the version in use is not the container's, F</overrides/lib/*> is
+populated with symbolic links into F</run/host/.../lib*>.
+
+Similarly, libcapsule and libelf are taken from either the GL provider
+or the container, or can be taken from a third tree.
+
+Other core graphics libraries (libX11, libxcb, etc.) are taken from either
+the GL provider, the container, or another tree.
+
+An application "payload" is mounted on F</app>, as if for Flatpak.
+
+The B<LD_LIBRARY_PATH> for the child process is made to include
+library directories from F</app> and F</overrides>.
+
+=head1 EXAMPLES
+
+=over
+
+=item Basic use
+
+    $ gl.pl
+
+Run B<glxinfo>(1), non-interactively. The container is a Debian stretch
+Flatpak runtime; get it using the instructions from
+L<https://gitlab.collabora.com/smcv/flatdeb/blob/master/README>,
+and rsync ~/.cache/flatdeb/repo onto a separate test machine if necessary.
+
+The OpenGL implementation and graphics stack are provided by the host
+system. Core libraries (glibc, libcapsule, libX11) and
+supporting libraries for the graphics stack (libstdc++, libpciaccess,
+libXext) are provided by either the container or the host system,
+whichever has the newer version, preferring the host system if the
+versions appear to be the same.
+
+=item Using the Steam Runtime
+
+    $ gl.pl \
+    --flatpak-runtime=com.valvesoftware.SteamRuntime.Platform/x86_64/scout_beta \
+    --flatpak-app=org.debian.packages.mesa_utils/x86_64/scout_beta
+
+Run B<glxinfo>(1) in the Steam Runtime. The container is a Steam Runtime
+scout_beta Flatpak runtime; get it using the instructions from
+L<https://gitlab.collabora.com/smcv/flatdeb-steam/blob/master/README>,
+and rsync ~/.cache/flatdeb/repo onto a separate test machine if necessary.
+
+=item Running glxgears
+
+    $ gl.pl glxgears
+
+Run B<glxgears>(1) instead of B<glxinfo>(1). Press B<Escape> to exit.
+
+=item Running OpenArena
+
+    $ gl.pl \
+    --flatpak-app=org.debian.packages.openarena/x86_64/master \
+    -- \
+    openarena +set com_hunkmegs 512 +set timedemo 1 +demo demo088-test1
+
+Run B<openarena>(6) instead of B<glxinfo>(1), and run a demo at the maximum
+possible frame rate. Type B<\quit> into the terminal, or
+B<Shift+Escape \quit> into the GUI window, when the demo has finished.
+
+=item Using a sysroot instead of a Flatpak runtime
+
+    $ sudo debootstrap --include=(many packages) stretch \
+    ~/stretch-sysroot http://deb.debian.org/debian
+    $ gl.pl --container ~/stretch-sysroot
+
+The container can be a complete sysroot (with a F</usr>) instead of just
+a Flatpak runtime or a merged F</usr>.
+
+=item Using NVIDIA proprietary drivers
+
+    $ gl.pl --proposed-nvidia
+
+This uses the host NVIDIA drivers, without libcapsule.
+
+=item Using Mesa not via libcapsule
+
+    $ gl.pl --full-mesa
+
+This uses the host Mesa drivers, without libcapsule. We need to pull in
+lots of libraries from the host system.
+
+=item Using Mesa via libcapsule
+
+    $ gl.pl --proposed-mesa
+
+This uses the host Mesa drivers, with libcapsule.
+
+=item Using a non-host GL stack
+
+    $ sudo debootstrap --include=(many packages) sid \
+    ~/sid-sysroot http://deb.debian.org/debian
+    $ gl.pl --gl-provider ~/sid-sysroot --container ~/stretch-sysroot
+
+The GL provider can be a complete sysroot (with a F</usr>), or a
+merged F</usr>, instead of the host system.
+
+If the GL provider uses B<GLVND> and the host system's X11 display does
+not have the B<GLX_EXT_libglvnd> extension, then either the GL provider
+must have a symbolic link B<libGLX_indirect.so.0> to the correct
+vendor GLX library, or B<__GLX_FORCE_VENDOR_LIBRARY_0> or
+B<__GLX_VENDOR_LIBRARY_NAME> must be set to the correct vendor name
+(typically B<mesa> or B<nvidia>).
+
+=item Using libcapsule
+
+    $ gl.pl --libcapsule
+      # which is just a shortcut for...
+    $ gl.pl --gl-stack=/usr/lib/libcapsule/shims
+
+Actually use libcapsule. This currently works with a SteamOS brewmaster
+(Debian jessie-based) host, a Steam Runtime or Debian stretch container,
+Mesa graphics, and glxinfo, glxgears or OpenArena as the app, but crashes
+when the graphics driver is NVIDIA.
+
+=back
+
+=head1 OPTIONS
+
+=over
+
+=item --app=I<APP>
+
+Mount I<APP> on F</app>. See B<--flatpak-app> for the default.
+
+=item --container=I<TREE>
+
+A complete sysroot or a merged F</usr> for the container. Most libraries
+will come from here. See B<--flatpak-runtime> for the default.
+
+=item --flatpak-app=I<ID>/I<ARCH>/I<BRANCH>
+
+Assume that I<ID>/I<ARCH>/I<BRANCH> has been installed per-user with
+C<flatpak --user install ...>, and use it as the B<--app>.
+The default is F<org.debian.packages.mesa_utils/x86_64/scout_beta>.
+
+=item --flatpak-runtime=I<ID>/I<ARCH>/I<BRANCH>
+
+Assume that I<ID>/I<ARCH>/I<BRANCH> has been installed per-user with
+C<flatpak --user install ...>, and use it as the B<--container>.
+The default is F<net.debian.flatpak.Games.Platform/x86_64/stretch>.
+Another interesting value is
+F<com.valvesoftware.SteamRuntime.Platform/x86_64/scout_beta>.
+
+=item --gl-provider=I<TREE>
+
+A complete sysroot or a merged F</usr> for the OpenGL provider.
+The OpenGL implementation will come from here unless overridden
+by B<--gl-stack>, and whatever supporting libraries are required will
+come from here unless overridden by B<--libc-provider>,
+B<--libcapsule-provider>, B<--x11-provider>. It will be mounted under
+F</run/host>. The default is F</>, meaning we use the host system
+as the OpenGL provider.
+
+=item --gl-stack=I<TREE>
+
+A pre-prepared OpenGL stack, with F<lib/I<TUPLE>> subdirectories
+containing F<libGLX.so.1> and related libraries. It will be mounted
+under F</run/host>, and F</overrides> will be populated with symlinks
+to it and added to the B<LD_LIBRARY_PATH>.
+
+=item --multiarch=I<TUPLE>[,I<TUPLE>...]
+
+A list of Debian multiarch tuples to enable. The default is
+C<x86_64-linux-gnu>.
+
+=item --libc-provider=B<auto>|B<container>|B<gl-provider>|I<USR>|I<SYSROOT>
+
+Get glibc (libc, libm, libpthread etc.) from here. B<container> uses the
+B<--container> (or B<--flatpak-runtime>). B<gl-provider> uses the
+B<--gl-provider>. B<auto> uses whichever of those appears to have newer
+libraries, mixing versions from both if necessary. Alternatively,
+a complete sysroot or a merged F</usr> can be given.
+The default is B<auto>.
+
+=item --x11-provider=B<auto>|B<container>|B<gl-provider>|I<USR>|I<SYSROOT>
+
+The same as for B<--libc-provider>, but for libX11 and related libraries.
+The default is B<auto>.
+
+=item --libcapsule-provider=B<auto>|B<container>|B<gl-provider>|I<USR>|I<SYSROOT>
+
+The same as for B<--libc-provider>, but for libcapsule and libelf.
+The default is B<container>, but B<--libcapsule> overrides this to
+B</>.
+
+=item --libcapsule
+
+A shortcut for B<--gl-stack=/usr/lib/libcapsule/shims> and
+B<--libcapsule-provider=/>.
+This uses the libcapsule libGL, etc. shims from the host system.
+
+=item --proposed-mesa
+
+Use behaviour proposed as potentially appropriate for the Mesa graphics
+stack: B<--gl-provider=/> B<--libcapsule> B<--libc-provider=auto>
+B<--x11-provider=auto>.
+
+=item --proposed-nvidia
+
+Use behaviour proposed as potentially appropriate for the NVIDIA graphics
+stack, or other binary blobs with conservative dependencies:
+B<--gl-provider=/> B<--libc-provider=container>
+B<--x11-provider=container>.
+
+=back
+
+=cut
+
+my $ansi_bright = "";
+my $ansi_cyan = "";
+my $ansi_green = "";
+my $ansi_magenta = "";
+my $ansi_reset = "";
+
+if (-t STDOUT) {
+    # Mnemonic: cyan is the container, green is the GL provider, magenta
+    # is miscellaneous
+    $ansi_bright = "\e[1m";
+    $ansi_cyan = "\e[36m";
+    $ansi_green = "\e[32m";
+    $ansi_magenta = "\e[35m";
+    $ansi_reset = "\e[0m";
+}
+
+my %CAPSULE_VERSION_TOOLS;
+my %CAPSULE_CAPTURE_LIBS_TOOLS;
+
+=head1 FUNCTIONS
+
+=over
+
+=item run_in_container(I<TREE>, I<ARGV>, ...)
+
+Use B<bwrap>(1) to run the command I<ARGV> (an array reference) in I<TREE>,
+which may either be a complete sysroot or just the contents of a merged
+F</usr> (see C<bind_usr>). The command is assumed not to need write
+access to anything in the tree. Remaining arguments are passed to
+C<IPC::Run::run> after the I<ARGV>.
+
+=cut
+
+sub run_in_container {
+    my $tree = shift;
+    my $argv = shift;
+    my @run_params = @_;
+    my @bwrap;
+
+    @bwrap = qw(bwrap --dev-bind /dev /dev --bind /proc /proc --bind /sys /sys);
+    push @bwrap, bind_usr($tree);
+
+    foreach my $subdir (qw(etc var)) {
+        if (-d "$tree/$subdir") {
+            push @bwrap, '--ro-bind', "$tree/$subdir", "/$subdir";
+        }
+    }
+
+    return run([@bwrap, @$argv], @run_params);
+}
+
+=item multiarch_tuple_to_ldso(I<TUPLE>)
+
+Return the B<ld.so>(8) implementation for a Debian multiarch tuple.
+For example,
+C<multiarch_tuple_to_ldso('x86_64-linux-gnu')> returns
+F</lib64/ld-linux-x86-64.so.2>.
+
+=cut
+
+my %multiarch_tuple_to_ldso;
+
+sub multiarch_tuple_to_ldso {
+    my $tuple = shift;
+    my $stdout;
+
+    if (defined $multiarch_tuple_to_ldso{$tuple}) {
+        return $multiarch_tuple_to_ldso{$tuple};
+    }
+
+    assert_run_verbose([
+        $CAPSULE_CAPTURE_LIBS_TOOLS{$tuple}, '--print-ld.so'
+    ], '>', \$stdout);
+    chomp $stdout;
+    $multiarch_tuple_to_ldso{$tuple} = $stdout;
+    return $stdout;
+}
+
+=item real_path_of_ldso(I<TREE>, I<TUPLE>, I<WORKSPACE>)
+
+Return the path to the B<ld.so>(8) implementation for a Debian multiarch
+tuple in I<TREE>, relative to I<TREE> itself. For example,
+C<real_path_of_ldso('/', 'x86_64-linux-gnu', $workspace)> might
+return F</lib/x86_64-linux-gnu/ld-2.25.so>, while
+C<real_path_of_ldso('/chroots/stretch', 'i386-linux-gnu', $workspace)>
+might return F</lib/i386-linux-gnu/ld-2.24.so>.
+
+I<WORKSPACE> is a temporary directory used internally.
+
+=cut
+
+sub real_path_of_ldso {
+    my $tree = shift;
+    my $tuple = shift;
+    my $workspace = shift;
+    my $stdout;
+    $tree = '/' unless defined $tree;
+
+    assert_run_verbose([
+        'bwrap',
+        '--ro-bind', '/', '/',
+        '--tmpfs', $workspace,
+        bind_usr($tree, $workspace),
+        $CAPSULE_CAPTURE_LIBS_TOOLS{$tuple},
+        "--resolve-ld.so=$workspace",
+    ], '>', \$stdout);
+    chomp $stdout;
+    diag "-> $stdout";
+    return $stdout;
+}
+
+=item use_ldso(I<LIBC_PROVIDER>, I<CONTAINER>, I<TUPLES>, I<WORKSPACE>)
+
+Return the necessary B<bwrap>(1) arguments to override the container's
+B<ld.so>(1) with the one from the libc provider.
+
+=cut
+
+sub use_ldso {
+    my $libc_provider_tree = shift;
+    my $container_tree = shift;
+    my @multiarch_tuples = @{ shift() };
+    my $workspace = shift;
+    my @bwrap;
+
+    foreach my $tuple (@multiarch_tuples) {
+        my $resolved = real_path_of_ldso($libc_provider_tree, $tuple,
+            $workspace);
+        my $ldso = real_path_of_ldso($container_tree, $tuple, $workspace);
+
+        if (-d "$container_tree/usr" || $ldso =~ m{^/usr/}) {
+            diag "Adding bind-mount of $libc_provider_tree$resolved over $ldso";
+            push @bwrap, '--ro-bind', "$libc_provider_tree$resolved", $ldso;
+        }
+        else {
+            diag "Adding bind-mount of $libc_provider_tree$resolved over /usr$ldso because /lib{,64} will be symlinks";
+            push @bwrap, '--ro-bind', "$libc_provider_tree$resolved", "/usr$ldso";
+        }
+    }
+
+    return @bwrap;
+}
+
+=item capture_libs(I<PROVIDER>, I<CONTAINER>, I<TUPLES>, I<DEST>, I<LIBS>, I<WORKSPACE>, I<MOUNT>)
+
+Populate I<DEST>F</lib/>I<TUPLE> with symbolic links to the libraries
+in I<PROVIDER>, assuming that I<PROVIDER> will be mounted on
+I<MOUNT>, such that I<DEST>F</lib/>I<TUPLE> can be added
+to the B<LD_LIBRARY_PATH>. Use I<WORKSPACE> as a temporary work area.
+
+Libraries that appear to be strictly newer in I<CONTAINER> are skipped.
+
+I<TUPLES> is a reference to an array of Debian multiarch tuples.
+
+I<LIBS> is a list of library patterns as for B<capsule-capture-libs>(1).
+
+=cut
+
+sub capture_libs {
+    my $provider_tree = shift;
+    my $container_tree = shift;
+    my @multiarch_tuples = @{ shift() };
+    my $dest = shift;
+    my @libs = @{ shift() };
+    my $workspace = shift;
+    my $mount = shift;
+    my @maybe_container;
+
+
+    foreach my $tuple (@multiarch_tuples) {
+        make_path("$dest/lib/$tuple", verbose => 1);
+
+        assert_run_verbose([
+            'bwrap',
+            '--ro-bind', '/', '/',
+            '--bind', $dest, $dest,
+            '--tmpfs', $workspace,
+            bind_usr($container_tree, $workspace),
+            $CAPSULE_CAPTURE_LIBS_TOOLS{$tuple},
+            "--container=$workspace",
+            "--link-target=$mount",
+            "--dest=$dest/lib/$tuple",
+            "--provider=$provider_tree",
+            @libs,
+        ], '>&2');
+    }
+}
+
+=back
+
+=cut
+
+skip_all_unless_bwrap;
+
+my $stdout;
+
+my $data_home = $ENV{XDG_DATA_HOME};
+$data_home = "$ENV{HOME}/.local/share" unless defined $data_home;
+my $gl_provider_tree = "/";
+my $container_tree;
+my $flatpak_app;
+my $flatpak_runtime;
+my @multiarch_tuples = qw(x86_64-linux-gnu);
+my $app;
+my $gl_stack = undef;
+my $arch = 'x86_64';
+my $libc_provider_tree = 'auto';
+my $libcapsule_tree = 'container';
+my $x11_provider_tree = 'auto';
+my $mesa_drivers = 1;
+my $mesa_driver_deps = 1;
+my $nvidia_only = 0;
+
+GetOptions(
+    'app=s' => \$app,
+    'container=s' => \$container_tree,
+    'flatpak-app=s' => \$flatpak_app,
+    'flatpak-runtime=s' => \$flatpak_runtime,
+    'gl-provider=s' => \$gl_provider_tree,
+    'gl-stack=s' => \$gl_stack,
+    'libcapsule-provider=s' => \$libcapsule_tree,
+    'libcapsule' => sub {
+        $gl_stack = '/usr/lib/libcapsule/shims';
+        $libcapsule_tree = '/';
+        $mesa_driver_deps = 0;
+    },
+    'libc-provider=s' => \$libc_provider_tree,
+    'mesa-drivers' => \$mesa_drivers,
+    'no-mesa-drivers' => sub { $mesa_drivers = 0; },
+    'mesa-driver-deps' => \$mesa_driver_deps,
+    'no-mesa-driver-deps' => sub { $mesa_driver_deps = 0; },
+    'nvidia-only' => \$nvidia_only,
+    'x11-provider=s' => \$x11_provider_tree,
+    'multiarch=s' => sub {
+        @multiarch_tuples = split /[\s,]+/, $_[1];
+    },
+    'full-mesa' => sub {
+        $gl_provider_tree = '/';
+        undef $gl_stack;
+        $libcapsule_tree = 'container';
+        $libc_provider_tree = 'auto';
+        $mesa_drivers = 1;
+        $mesa_driver_deps = 1;
+        $nvidia_only = 0;
+        $x11_provider_tree = 'auto';
+    },
+    'proposed-mesa' => sub {
+        $gl_provider_tree = '/';
+        $gl_stack = '/usr/lib/libcapsule/shims';
+        $libcapsule_tree = '/';
+        $libc_provider_tree = 'auto';
+        $mesa_drivers = 1;
+        $mesa_driver_deps = 0;
+        $nvidia_only = 0;
+        $x11_provider_tree = 'auto';
+    },
+    'proposed-nvidia' => sub {
+        $gl_provider_tree = '/';
+        undef $gl_stack;
+        $libcapsule_tree = 'container';
+        $libc_provider_tree = 'container';
+        $mesa_drivers = 0;
+        $mesa_driver_deps = 0;
+        $nvidia_only = 1;
+        $x11_provider_tree = 'container';
+    },
+    help => sub {
+        print <<EOF;
+Usage: $0 [OPTIONS] [-- COMMAND]
+Options:
+    --app=DIRECTORY                     Mount DIRECTORY at /app
+    --flatpak-app=NAME/ARCH/BRANCH      Use a `flatpak --user` app for --app
+                                        [….mesa_utils/$arch/master]
+    --container=USR|SYSROOT             Populate /usr from USR or SYSROOT
+    --flatpak-runtime=NAME/ARCH/BRANCH  Use a `flatpak --user` runtime
+                                        for --container
+                                        [….Games.Platform/$arch/stretch]
+    --gl-provider=USR|SYSROOT           Get libGL etc. from USR or SYSROOT
+                                        [/]
+    --gl-stack=STACK                    Get libGL etc. from STACK/lib/MULTIARCH
+                                        instead of --gl-provider, but
+                                        still use --gl-provider libc, libX11,
+                                        libcapsule if appropriate.
+                                        [Same as --gl-provider]
+    --x11-provider=auto|container|gl-provider|USR|SYSROOT
+                                        Use libX11 etc. from here
+                                        [auto]
+    --libc-provider=auto|container|gl-provider|USR|SYSROOT
+                                        Use glibc from here
+                                        [auto]
+    --libcapsule-provider=auto|container|gl-provider|USR|SYSROOT
+                                        Use libcapsule, libelf from here
+                                        [container]
+    --libcapsule                        Shortcut for:
+                                        --gl-stack=/usr/lib/libcapsule/shims
+                                        --libcapsule-provider=/
+    --multiarch=TUPLE[,TUPLE…]          Enable architecture(s) by Debian
+                                        multiarch tuple
+                                        [x86_64-linux-gnu]
+    --nvidia-only                       Only capture NVIDIA proprietary
+                                        libraries (and possibly GLVND
+                                        wrappers), not the rest of the
+                                        libGL stack
+    --full-mesa                         Use short-term handling for Mesa:
+                                        --gl-provider=/
+                                        --libcapsule
+                                        --libc-provider=auto
+                                        --mesa-drivers
+                                        --mesa-driver-deps
+                                        --x11-provider=auto
+                                        --libcapsule-provider=auto
+    --proposed-mesa                     Use proposed handling for Mesa:
+                                        --gl-provider=/
+                                        --libcapsule
+                                        --libc-provider=auto
+                                        --mesa-drivers
+                                        --no-mesa-driver-deps
+                                        --x11-provider=auto
+                                        --libcapsule-provider=auto
+    --proposed-nvidia                   Use proposed handling for binary
+                                        blob drivers like NVIDIA:
+                                        --gl-provider=/
+                                        --x11-provider=container
+                                        --libc-provider=container
+                                        --libcapsule-provider=container
+                                        --no-mesa-drivers
+                                        --no-mesa-driver-deps
+                                        --nvidia-only
+EOF
+        exit 0;
+    },
+) or die "Error parsing command-line options";
+
+if (defined $flatpak_app) {
+    die "--flatpak-app and --app are mutually exclusive" if defined $app;
+    $app = "$data_home/flatpak/app/$flatpak_app/active/files";
+}
+
+if (defined $flatpak_runtime) {
+    die "--flatpak-runtime and --container are mutually exclusive"
+        if defined $container_tree;
+    $container_tree = "$data_home/flatpak/runtime/$flatpak_runtime/active/files";
+}
+
+foreach my $tuple (@multiarch_tuples) {
+    assert_run_verbose([
+        'env', "PKG_CONFIG_PATH=/usr/lib/$tuple/pkgconfig",
+        'pkg-config', '--variable=CAPSULE_VERSION_TOOL', 'libcapsule-tools',
+    ], '>', \$stdout);
+    chomp $stdout;
+    $CAPSULE_VERSION_TOOLS{$tuple} = $stdout;
+    assert_run_verbose([
+        'env', "PKG_CONFIG_PATH=/usr/lib/$tuple/pkgconfig",
+        'pkg-config', '--variable=CAPSULE_CAPTURE_LIBS_TOOL',
+        'libcapsule-tools',
+    ], '>', \$stdout);
+    chomp $stdout;
+    $CAPSULE_CAPTURE_LIBS_TOOLS{$tuple} = $stdout;
+}
+
+$app = "$data_home/flatpak/app/org.debian.packages.mesa_utils/$arch/master/active/files"
+    unless defined $app;
+$container_tree = "$data_home/flatpak/runtime/net.debian.flatpak.Games.Platform/$arch/stretch/active/files"
+    unless defined $container_tree;
+
+assert_run_verbose(['glxinfo'], '>', \$stdout);
+diag_multiline($stdout);
+
+if ($stdout =~ /Error: couldn't find RGB GLX visual/) {
+    plan skip_all => 'Cannot run glxinfo here';
+}
+
+if ($stdout !~ /direct rendering: Yes/) {
+    plan skip_all => 'glxinfo reports no direct rendering available';
+}
+
+my $tmpdir = File::Temp->newdir(
+        TMPDIR => 1, TEMPLATE => 'capsule-test-temp-XXXXXXXX');
+
+my @bwrap = qw(
+    bwrap
+    --unshare-user
+    --unshare-pid
+    --unshare-uts --hostname bwrap
+    --dev-bind /dev /dev
+    --bind /dev/shm /dev/shm
+    --proc /proc
+    --ro-bind /sys /sys
+    --tmpfs /run
+    --tmpfs /tmp
+    --tmpfs /var
+    --tmpfs /var/tmp
+    --symlink ../run /var/run
+);
+push @bwrap, '--bind', $ENV{HOME}, $ENV{HOME};
+
+foreach my $mutable (qw(/etc /var/cache /var/lib)) {
+    if (-d "$container_tree$mutable") {
+        opendir(my $dir, "$container_tree$mutable");
+
+        while (defined(my $member = readdir $dir)) {
+            next if $member eq '.' || $member eq '..';
+            next if "$mutable/$member" eq '/etc/group';
+            next if "$mutable/$member" eq '/etc/host.conf';
+            next if "$mutable/$member" eq '/etc/hosts';
+            next if "$mutable/$member" eq '/etc/localtime';
+            next if "$mutable/$member" eq '/etc/machine-id';
+            next if "$mutable/$member" eq '/etc/passwd';
+            next if "$mutable/$member" eq '/etc/resolv.conf';
+            next if "$mutable/$member" eq '/var/lib/dbus';
+            next if "$mutable/$member" eq '/var/lib/dhcp';
+            next if "$mutable/$member" eq '/var/lib/sudo';
+            next if "$mutable/$member" eq '/var/lib/urandom';
+
+            no autodie 'readlink';
+            my $target = readlink "$container_tree$mutable/$member";
+
+            if (defined $target) {
+                push @bwrap, '--symlink', "$target", "$mutable/$member";
+            }
+            else {
+                push @bwrap, '--ro-bind', "$container_tree$mutable/$member", "$mutable/$member";
+            }
+        }
+
+        closedir($dir);
+    }
+}
+
+if (-e '/etc/machine-id') {
+    push @bwrap, '--ro-bind', '/etc/machine-id', '/etc/machine-id';
+    push @bwrap, '--symlink', '/etc/machine-id', '/var/lib/dbus/machine-id';
+}
+elsif (-e '/var/lib/dbus/machine-id') {
+    push @bwrap, '--ro-bind', '/var/lib/dbus/machine-id', '/etc/machine-id';
+    push @bwrap, '--symlink', '/etc/machine-id', '/var/lib/dbus/machine-id';
+}
+
+if (-e '/etc/localtime') {
+    no autodie 'readlink';
+    my $target = readlink '/etc/localtime';
+
+    if (defined $target && $target =~ m{^/usr/}) {
+        push @bwrap, '--symlink', $target, '/etc/localtime';
+    }
+    else {
+        push @bwrap, '--ro-bind', '/etc/localtime', '/etc/localtime';
+    }
+}
+
+foreach my $file (qw(resolv.conf host.conf hosts passwd group)) {
+    if (-e "/etc/$file") {
+        push @bwrap, '--ro-bind', "/etc/$file", "/etc/$file";
+    }
+}
+
+push @bwrap, bind_usr($container_tree);
+$gl_provider_tree = realpath($gl_provider_tree);
+my $capsule_prefix = "/run/host$gl_provider_tree";
+$capsule_prefix =~ s,/*$,,;
+push @bwrap, bind_usr($gl_provider_tree, $capsule_prefix);
+push @bwrap, '--setenv', 'CAPSULE_PREFIX', $capsule_prefix;
+
+my @ld_path;
+
+if (defined $app) {
+    foreach my $tuple (@multiarch_tuples) {
+        push @ld_path, "/app/lib/$tuple";
+    }
+
+    push @ld_path, '/app/lib';
+    push @bwrap, '--ro-bind', $app, '/app';
+    push @bwrap, '--setenv', 'PATH', '/app/bin:/app/sbin:/usr/bin:/bin:/usr/sbin:/sbin';
+}
+
+my $overrides_tree = "$tmpdir/overrides";
+make_path("$tmpdir/scratch", verbose => 1);
+make_path("$tmpdir/overrides", verbose => 1);
+
+foreach my $tuple (@multiarch_tuples) {
+    push @ld_path, "/overrides/lib/$tuple";
+    make_path("$overrides_tree/lib/$tuple", verbose => 1);
+}
+
+if ($libc_provider_tree eq 'auto') {
+    diag "Choosing GL provider's libc or container's libc automatically";
+    capture_libs($gl_provider_tree, $container_tree, \@multiarch_tuples,
+        $overrides_tree, ['soname:libc.so.6'], "$tmpdir/scratch", $capsule_prefix);
+
+    # See what we've got
+    foreach my $tuple (@multiarch_tuples) {
+        assert_run_verbose([
+            'ls', '-l', "$tmpdir/overrides/lib/$tuple",
+        ], '>', \$stdout);
+        diag_multiline $stdout;
+    }
+
+    if (-l "$overrides_tree/lib/$multiarch_tuples[0]/libc.so.6") {
+        diag "${ansi_bright}${ansi_green}GL provider's libc appears ".
+            "newer${ansi_reset}";
+        push @bwrap, use_ldso($gl_provider_tree, $container_tree,
+            \@multiarch_tuples, "$tmpdir/scratch");
+    }
+    else {
+        diag "${ansi_bright}${ansi_cyan}Container's libc appears ".
+            "newer${ansi_reset}";
+    }
+}
+elsif ($libc_provider_tree =~ m/^(?:gl-provider|host)$/) {
+    capture_libs($gl_provider_tree, $container_tree, \@multiarch_tuples,
+        $overrides_tree, ['even-if-older:soname:libc.so.6'],
+        "$tmpdir/scratch", $capsule_prefix);
+    diag "${ansi_bright}${ansi_green}Adding GL provider's libc via /overrides ".
+        "as requested${ansi_reset}";
+    push @bwrap, use_ldso($gl_provider_tree, $container_tree,
+        \@multiarch_tuples, "$tmpdir/scratch");
+}
+elsif ($libc_provider_tree eq 'container') {
+    diag "${ansi_bright}${ansi_cyan}Using container's libc as ".
+        "requested${ansi_reset}";
+}
+else {
+    diag "${ansi_bright}${ansi_magenta}Adding ${libc_provider_tree} libc ".
+        "via /overrides as requested${ansi_reset}";
+    $libc_provider_tree = realpath($libc_provider_tree);
+    capture_libs($libc_provider_tree, $container_tree, \@multiarch_tuples,
+        $overrides_tree, ['even-if-older:soname:libc.so.6'],
+        "$tmpdir/scratch", "/run/host$libc_provider_tree");
+    push @bwrap, use_ldso($libc_provider_tree, $container_tree,
+        \@multiarch_tuples, "$tmpdir/scratch");
+    push @bwrap, bind_usr($libc_provider_tree, "/run/host$libc_provider_tree");
+}
+
+my @dri_path;
+
+if (defined $gl_stack) {
+    diag "Using standalone GL stack $gl_stack";
+    $gl_stack = realpath($gl_stack);
+
+    push @bwrap, '--ro-bind', $gl_stack, "/run/host$gl_stack";
+
+    foreach my $tuple (@multiarch_tuples) {
+        next unless -d "$gl_stack/lib/$tuple";
+        opendir(my $dir, "$gl_stack/lib/$tuple");
+
+        while(defined(my $member = readdir $dir)) {
+            next if $member eq '.' || $member eq '..';
+            my $target = realpath("$gl_stack/lib/$tuple/$member");
+            symlink(
+                "/run/host$target",
+                "$overrides_tree/lib/$tuple/$member");
+        }
+
+        closedir($dir);
+    }
+}
+elsif ($nvidia_only) {
+    diag "Using NVIDIA-only GL stack from $gl_provider_tree";
+    capture_libs($gl_provider_tree, $container_tree,
+        \@multiarch_tuples, $overrides_tree, ['no-dependencies:nvidia:'],
+        "$tmpdir/scratch", $capsule_prefix);
+}
+else {
+    diag "Using GL stack from $gl_provider_tree";
+    capture_libs($gl_provider_tree, $container_tree,
+        \@multiarch_tuples, $overrides_tree, ['gl:'],
+        "$tmpdir/scratch", $capsule_prefix);
+
+    # If we're using Mesa, we need the DRI drivers too
+    if ($mesa_drivers || $mesa_driver_deps) {
+        foreach my $tuple (@multiarch_tuples) {
+            my $ldso = multiarch_tuple_to_ldso($tuple);
+            my $libdir;
+
+            my @search_path = ("/lib/$tuple", "/usr/lib/$tuple");
+
+            if (defined $ldso) {
+                # Add /lib64 and /usr/lib64 if applicable
+                (undef, $libdir, undef) = File::Spec->splitpath($ldso);
+                push @search_path, $libdir, "/usr$libdir";
+            }
+
+            foreach my $search (@search_path) {
+                if (-d "$gl_provider_tree$search/dri") {
+                    diag "Using GL provider's $search/dri";
+
+                    if ($mesa_drivers) {
+                        symlink("$capsule_prefix$search/dri",
+                            "$overrides_tree/lib/$tuple/dri");
+                        push @dri_path, "/overrides/lib/$tuple/dri";
+                    }
+
+                    if ($mesa_driver_deps) {
+                        capture_libs($gl_provider_tree, $container_tree,
+                            \@multiarch_tuples, $overrides_tree,
+                            ["only-dependencies:path-match:$search/dri/*"],
+                            "$tmpdir/scratch", $capsule_prefix);
+                    }
+                }
+            }
+        }
+    }
+}
+
+# These are libraries that can't have more than one instance in use.
+# TODO: We don't currently exclude libelf from being encapsulated.
+# Should we? Does it have global state?
+if ($libcapsule_tree eq 'auto') {
+    diag "Using libcapsule from $gl_provider_tree if newer";
+    capture_libs($gl_provider_tree, $container_tree,
+        \@multiarch_tuples, $overrides_tree, ['soname:libcapsule.so.0'],
+        "$tmpdir/scratch", $capsule_prefix);
+}
+elsif ($libcapsule_tree eq 'container') {
+    diag "${ansi_cyan}Using libcapsule from container as requested${ansi_reset}";
+}
+elsif ($libcapsule_tree =~ m/^(?:gl-provider|host)$/) {
+    diag "${ansi_green}Using libcapsule from GL provider as requested${ansi_reset}";
+    capture_libs($gl_provider_tree, $container_tree,
+        \@multiarch_tuples, $overrides_tree,
+        ['even-if-older:soname:libcapsule.so.0'],
+        "$tmpdir/scratch", $capsule_prefix);
+}
+else {
+    diag "${ansi_magenta}Using libcapsule from $libcapsule_tree${ansi_reset}";
+    $libcapsule_tree = realpath($libcapsule_tree);
+    capture_libs($libcapsule_tree, $container_tree,
+        \@multiarch_tuples, $overrides_tree,
+        ['even-if-older:soname:libcapsule.so.0'],
+        "$tmpdir/scratch", "/run/host$libcapsule_tree");
+    push @bwrap, bind_usr($libcapsule_tree, "/run/host$libcapsule_tree");
+}
+
+# These are more libraries that can't have more than one instance in use.
+# They also need to appear in libGL.so.1.excluded, so that the capsule
+# won't load the GL-provider copy if the container copy is newer.
+diag 'Adding singleton libraries from GL provider, if newer...';
+
+# TODO: We don't currently exclude libX11-xcb, libxcb-dri3,
+# libxcb-xfixes, libXau, libXdamage, libXdmcp, libXfixes, libXxf86vm
+# from being encapsulated. We should be consistent one way or the other
+my @XLIBS = qw(X11 X11-xcb xcb xcb-dri2 xcb-dri3 xcb-glx
+        xcb-present xcb-sync xcb-xfixes
+        Xau Xdamage Xdmcp Xext Xfixes Xxf86vm
+        );
+
+if ($x11_provider_tree eq 'auto') {
+    diag "Using X libraries from $gl_provider_tree if newer";
+    capture_libs($gl_provider_tree, $container_tree,
+        \@multiarch_tuples, $overrides_tree,
+        [map { "soname-match:lib$_.so.*" } @XLIBS],
+        "$tmpdir/scratch", $capsule_prefix);
+}
+elsif ($x11_provider_tree eq 'container') {
+    diag "${ansi_cyan}Using X libraries from container as requested${ansi_reset}";
+}
+elsif ($x11_provider_tree =~ m/^(?:gl-provider|host)$/) {
+    diag "${ansi_green}Using X libraries from GL provider as requested${ansi_reset}";
+    capture_libs($gl_provider_tree, $container_tree,
+        \@multiarch_tuples, $overrides_tree,
+        [map { "even-if-older:soname-match:lib$_.so.*" } @XLIBS],
+        "$tmpdir/scratch", $capsule_prefix);
+}
+else {
+    diag "${ansi_magenta}Using X libraries from $x11_provider_tree as requested${ansi_reset}";
+    $x11_provider_tree = realpath($x11_provider_tree);
+    capture_libs($x11_provider_tree, $container_tree,
+        \@multiarch_tuples, $overrides_tree,
+        [map { "even-if-older:soname-match:lib$_.so.*" } @XLIBS],
+        "$tmpdir/scratch", "/run/host$x11_provider_tree", $ansi_magenta);
+    push @bwrap, bind_usr($libcapsule_tree, "/run/host$x11_provider_tree")
+}
+
+foreach my $tuple (@multiarch_tuples) {
+    push @ld_path, "/overrides/lib/$tuple";
+}
+
+push @bwrap, '--ro-bind', $overrides_tree, '/overrides';
+
+if (@ld_path) {
+    push @bwrap, '--setenv', 'LD_LIBRARY_PATH', join(':', @ld_path);
+}
+
+if (@dri_path) {
+    push @bwrap, '--setenv', 'LIBGL_DRIVERS_PATH', join(':', @dri_path);
+}
+
+push @bwrap, '--setenv', 'XDG_RUNTIME_DIR', "/run/user/$<";
+
+# Simplified version of flatpak run --socket=X11
+push @bwrap, '--bind', '/tmp/.X11-unix', '/tmp/.X11-unix';
+push @bwrap, '--setenv', 'DISPLAY', $ENV{DISPLAY};
+
+foreach my $xauth ($ENV{XAUTHORITY}, "$ENV{HOME}/.Xauthority") {
+    if (defined $xauth && -e $xauth) {
+        push @bwrap, '--ro-bind', $xauth, "/run/user/$</Xauthority";
+        push @bwrap, '--setenv', 'XAUTHORITY', "/run/user/$</Xauthority";
+        last;
+    }
+}
+
+# Simplified version of flatpak run --socket=wayland
+my $wayland_display = $ENV{WAYLAND_DISPLAY};
+$wayland_display = 'wayland-0' unless defined $wayland_display;
+if (exists $ENV{XDG_RUNTIME_DIR} &&
+    -S "$ENV{XDG_RUNTIME_DIR}/$wayland_display") {
+    push @bwrap, '--bind', "$ENV{XDG_RUNTIME_DIR}/$wayland_display",
+        "/run/user/$</$wayland_display";
+}
+
+# Simplified version of flatpak run --socket=pulseaudio, modified to
+# support the SteamOS system-wide PulseAudio instance
+if (exists $ENV{XDG_RUNTIME_DIR} &&
+    -S "$ENV{XDG_RUNTIME_DIR}/pulse/native") {
+    push @bwrap, '--bind', "$ENV{XDG_RUNTIME_DIR}/pulse/native",
+        "/run/user/$</pulse/native";
+    push @bwrap, '--setenv', 'PULSE_SERVER', "unix:/run/user/$</pulse/native";
+}
+elsif (-S "/var/run/pulse/native") {
+    push @bwrap, '--bind', "/var/run/pulse/native",
+        "/run/user/$</pulse/native";
+    push @bwrap, '--setenv', 'PULSE_SERVER', "unix:/run/user/$</pulse/native";
+}
+
+# Simplified version of flatpak run --socket=system-bus
+if (exists $ENV{DBUS_SYSTEM_BUS_ADDRESS} &&
+    $ENV{DBUS_SYSTEM_BUS_ADDRESS} =~ m/^unix:path=([^,;]*)/ &&
+    -S $1) {
+    push @bwrap, '--bind', $1, '/var/run/dbus/system_bus_socket';
+    push @bwrap, '--unsetenv', 'DBUS_SYSTEM_BUS_ADDRESS';
+}
+elsif (-S '/var/run/dbus/system_bus_socket') {
+    push @bwrap, '--bind', '/var/run/dbus/system_bus_socket',
+        '/run/dbus/system_bus_socket';
+}
+
+# Simplified version of flatpak run --socket=session-bus
+if (exists $ENV{DBUS_SESSION_BUS_ADDRESS} &&
+    $ENV{DBUS_SESSION_BUS_ADDRESS} =~ m/^unix:path=([^,;]*)/ &&
+    -S $1) {
+    push @bwrap, '--bind', $1, "/run/user/$</bus";
+    push @bwrap, '--setenv', 'DBUS_SESSION_BUS_ADDRESS',
+        "unix:path=/run/user/$</bus";
+}
+elsif (exists $ENV{XDG_RUNTIME_DIR} &&
+    -S "$ENV{XDG_RUNTIME_DIR}/bus") {
+    push @bwrap, '--bind', "$ENV{XDG_RUNTIME_DIR}/bus", "/run/user/$</bus";
+    push @bwrap, '--setenv', 'DBUS_SESSION_BUS_ADDRESS',
+        "unix:path=/run/user/$</bus";
+}
+# ... else hope it's unix:abstract=... which will work because we don't
+# unshare networking
+
+# See what we've got
+foreach my $tuple (@multiarch_tuples) {
+    foreach my $dir ('overrides') {
+        next unless -d "$tmpdir/$dir/lib/$tuple";
+        assert_run_verbose([
+            'ls', '-l', "$tmpdir/$dir/lib/$tuple",
+        ], '>', \$stdout);
+        diag_multiline $stdout;
+    }
+}
+
+if (@ARGV) {
+    run_ok([@bwrap, @ARGV]);
+}
+else {
+    run_ok([@bwrap, 'glxinfo'], '>', \$stdout);
+    diag_multiline $stdout;
+    like($stdout, qr{direct rendering: Yes});
+}
+
+done_testing;
+
+# vim:set sw=4 sts=4 et:
diff --git a/subprojects/libcapsule/tests/notgl-dlopener.c b/subprojects/libcapsule/tests/notgl-dlopener.c
new file mode 100644
index 0000000000000000000000000000000000000000..6e8160d3cf4886de02eb4c4f2f28a200048dd474
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-dlopener.c
@@ -0,0 +1,174 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "notgl.h"
+#include "notgles.h"
+#include "notgl-helper.h"
+
+static void die( const char *format, ... )
+    __attribute__((noreturn))
+    __attribute__((format(printf, 1, 2)));
+
+static void
+die( const char *format, ... )
+{
+    va_list ap;
+
+    va_start( ap, format );
+    vfprintf( stderr, format, ap );
+    va_end( ap );
+    abort();
+}
+
+static void *
+xdlopen( const char *filename, int flags )
+{
+    void *handle = dlopen( filename, flags );
+
+    if( handle == NULL )
+        die( "dlopen(\"%s\", %d): %s", filename, flags, dlerror() );
+
+    return handle;
+}
+
+static void *
+xdlsym( void *handle, const char *symbol )
+{
+    void *value = dlsym( handle, symbol );
+
+    if( value == NULL )
+        die( "dlsym(%p, \"%s\"): %s", handle, symbol, dlerror() );
+
+    return value;
+}
+
+static void
+xdlclose( void *handle )
+{
+    if( dlclose( handle ) != 0 )
+        die( "dlclose(%p): %s", handle, dlerror() );
+}
+
+static notgl_extension_function
+get_extension( void *handle,
+               const char *name )
+{
+    return dlsym( handle, name );
+}
+
+int
+main ( int argc,
+       char **argv )
+{
+    void *gl;
+    void *gles;
+    notgl_extension_function f;
+
+    // Select line-buffering for output, in case we crash
+    setvbuf( stdout, NULL, _IOLBF, 0 );
+
+    gl = xdlopen( "libnotgl.so.0", RTLD_LAZY|RTLD_GLOBAL );
+    gles = xdlopen( "libnotgles.so.1", RTLD_NOW|RTLD_LOCAL );
+
+    f = xdlsym( gl, "notgl_get_implementation" );
+    printf( "NotGL implementation: %s\n", f() );
+    f = xdlsym( gl, "notgl_use_helper" );
+    printf( "NotGL helper implementation: %s\n", f() );
+
+    f = get_extension( RTLD_DEFAULT, "notgl_extension_both" );
+
+    if( f )
+        printf( "notgl_extension_both: %s\n", f() );
+    else
+        printf( "notgl_extension_both: (not found)\n" );
+
+    f = get_extension( RTLD_DEFAULT, "notgl_extension_red" );
+
+    if( f )
+        printf( "notgl_extension_red: %s\n", f() );
+    else
+        printf( "notgl_extension_red: (not found)\n" );
+
+    f = get_extension( RTLD_DEFAULT, "notgl_extension_green" );
+
+    if( f )
+        printf( "notgl_extension_green: %s\n", f() );
+    else
+        printf( "notgl_extension_green: (not found)\n" );
+
+    f = xdlsym( gles, "notgles_get_implementation" );
+    printf( "NotGLES implementation: %s\n", f() );
+    f = xdlsym( gles, "notgles_use_helper" );
+    printf( "NotGLES helper implementation: %s\n", f() );
+
+    f = get_extension( gles, "notgles_extension_both" );
+
+    if( f )
+        printf( "notgles_extension_both: %s\n", f() );
+    else
+        printf( "notgles_extension_both: (not found)\n" );
+
+    f = get_extension( gles, "notgles_extension_red" );
+
+    if( f )
+        printf( "notgles_extension_red: %s\n", f() );
+    else
+        printf( "notgles_extension_red: (not found)\n" );
+
+    f = get_extension( gles, "notgles_extension_green" );
+
+    if( f )
+        printf( "notgles_extension_green: %s\n", f() );
+    else
+        printf( "notgles_extension_green: (not found)\n" );
+
+    xdlclose( gl );
+    xdlclose( gles );
+
+    // Check that we can dlopen and dlclose repeatedly without crashing
+
+    for( int i = 1; i < 10; i++ )
+    {
+        printf( "dlopening and dlclosing %d times...\n", i );
+        // dlopen'd handles are refcounted; take i references
+        for( int j = 0; j < i; j++ )
+        {
+            // Arbitrary flags that happen to be oppositely paired
+            // compared with how we opened the libraries above
+            gl = xdlopen( "libnotgl.so.0", RTLD_LAZY|RTLD_LOCAL );
+            gles = xdlopen( "libnotgles.so.1", RTLD_NOW|RTLD_GLOBAL );
+            xdlsym( gl, "notgl_extension_both" );
+            xdlsym( gles, "notgles_extension_both" );
+            xdlsym( RTLD_DEFAULT, "notgles_extension_both" );
+        }
+
+        // Release all i references
+        for( int j = 0; j < i; j++ )
+        {
+            xdlclose( gl );
+            xdlclose( gles );
+        }
+    }
+
+    return 0;
+}
diff --git a/subprojects/libcapsule/tests/notgl-green.c b/subprojects/libcapsule/tests/notgl-green.c
new file mode 100644
index 0000000000000000000000000000000000000000..ec7d1bb1edb017370e0e0b82dde7754d04379a4c
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-green.c
@@ -0,0 +1,49 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgl-green — one of two implementations of libnotgl, taking the
+// role of the NVIDIA implementation of OpenGL
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgl.h"
+
+#include <dlfcn.h>
+
+#include "notgl-helper.h"
+
+const char *
+notgl_get_implementation( void )
+{
+    return "green";
+}
+
+const char *
+notgl_extension_both( void )
+{
+    return "green implementation of common extension";
+}
+
+const char *
+notgl_extension_green( void )
+{
+    return "green-only extension";
+}
+
+const char *
+notgl_use_helper( void )
+{
+    return helper_get_implementation();
+}
diff --git a/subprojects/libcapsule/tests/notgl-helper-green.c b/subprojects/libcapsule/tests/notgl-helper-green.c
new file mode 100644
index 0000000000000000000000000000000000000000..0cfd2c99adb7d9319bb7ba8e0572f1d768c2322d
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-helper-green.c
@@ -0,0 +1,27 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgl-helper-green — An implementation of libnotgl-helper in the
+// host, taking the role of the host's libstdc++
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgl-helper.h"
+
+const char *
+helper_get_implementation( void )
+{
+    return "host (green)";
+}
diff --git a/subprojects/libcapsule/tests/notgl-helper-red.c b/subprojects/libcapsule/tests/notgl-helper-red.c
new file mode 100644
index 0000000000000000000000000000000000000000..c0da1ee910ddb3bc9bbe3e0f70368bc2bd8aeef4
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-helper-red.c
@@ -0,0 +1,27 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgl-helper-red — An implementation of libnotgl-helper in the
+// host, taking the role of the host's libstdc++
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgl-helper.h"
+
+const char *
+helper_get_implementation( void )
+{
+    return "host (red)";
+}
diff --git a/subprojects/libcapsule/tests/notgl-helper-ref.c b/subprojects/libcapsule/tests/notgl-helper-ref.c
new file mode 100644
index 0000000000000000000000000000000000000000..29557c6c31e1cb72a0d90643352657f2cec37f6e
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-helper-ref.c
@@ -0,0 +1,27 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgl-helper-ref — The implementation of libnotgl-helper in the
+// container, taking the role of the container's libstdc++
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgl-helper.h"
+
+const char *
+helper_get_implementation( void )
+{
+    return "container (reference)";
+}
diff --git a/subprojects/libcapsule/tests/notgl-helper-user.c b/subprojects/libcapsule/tests/notgl-helper-user.c
new file mode 100644
index 0000000000000000000000000000000000000000..81a8e78fd0878d86705a8d2ddd84937d667a31ac
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-helper-user.c
@@ -0,0 +1,92 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "notgl.h"
+#include "notgles.h"
+#include "notgl-helper.h"
+
+static notgl_extension_function
+get_extension( const char *name )
+{
+    return dlsym( RTLD_DEFAULT, name );
+}
+
+int
+main ( int argc,
+       char **argv )
+{
+    notgl_extension_function f;
+
+    // Select line-buffering for output, in case we crash
+    setvbuf( stdout, NULL, _IOLBF, 0 );
+
+    printf( "NotGL implementation: %s\n", notgl_get_implementation() );
+    printf( "NotGL helper implementation: %s\n", notgl_use_helper() );
+
+    f = get_extension( "notgl_extension_both" );
+
+    if( f )
+        printf( "notgl_extension_both: %s\n", f() );
+    else
+        printf( "notgl_extension_both: (not found)\n" );
+
+    f = get_extension( "notgl_extension_red" );
+
+    if( f )
+        printf( "notgl_extension_red: %s\n", f() );
+    else
+        printf( "notgl_extension_red: (not found)\n" );
+
+    f = get_extension( "notgl_extension_green" );
+
+    if( f )
+        printf( "notgl_extension_green: %s\n", f() );
+    else
+        printf( "notgl_extension_green: (not found)\n" );
+
+    printf( "NotGLES implementation: %s\n", notgles_get_implementation() );
+    printf( "NotGLES helper implementation: %s\n", notgles_use_helper() );
+
+    f = get_extension( "notgles_extension_both" );
+
+    if( f )
+        printf( "notgles_extension_both: %s\n", f() );
+    else
+        printf( "notgles_extension_both: (not found)\n" );
+
+    f = get_extension( "notgles_extension_red" );
+
+    if( f )
+        printf( "notgles_extension_red: %s\n", f() );
+    else
+        printf( "notgles_extension_red: (not found)\n" );
+
+    f = get_extension( "notgles_extension_green" );
+
+    if( f )
+        printf( "notgles_extension_green: %s\n", f() );
+    else
+        printf( "notgles_extension_green: (not found)\n" );
+
+    printf( "NotGL helper implementation as seen by executable: %s\n", helper_get_implementation () );
+
+    return 0;
+}
diff --git a/subprojects/libcapsule/tests/notgl-helper.h b/subprojects/libcapsule/tests/notgl-helper.h
new file mode 100644
index 0000000000000000000000000000000000000000..2c622dc457a45235c5abc02f336f80eba51dad31
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-helper.h
@@ -0,0 +1,26 @@
+// Copyright © 2017 Collabora Ltd
+
+// libnotgl-helper — A library used by the OpenGL driver, taking the
+// role of libstdc++
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#define HELPER_PUBLIC __attribute__((visibility("default")))
+
+HELPER_PUBLIC
+const char *helper_get_implementation( void );
diff --git a/subprojects/libcapsule/tests/notgl-red.c b/subprojects/libcapsule/tests/notgl-red.c
new file mode 100644
index 0000000000000000000000000000000000000000..090f7187510ce3b4d8a337e1f4138b7bbd6dba1c
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-red.c
@@ -0,0 +1,49 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgl-red — one of two implementations of libnotgl, taking the
+// role of the AMD implementation of OpenGL
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgl.h"
+
+#include <dlfcn.h>
+
+#include "notgl-helper.h"
+
+const char *
+notgl_get_implementation( void )
+{
+    return "red";
+}
+
+const char *
+notgl_extension_both( void )
+{
+    return "red implementation of common extension";
+}
+
+const char *
+notgl_extension_red( void )
+{
+    return "red-only extension";
+}
+
+const char *
+notgl_use_helper( void )
+{
+    return helper_get_implementation ();
+}
diff --git a/subprojects/libcapsule/tests/notgl-ref.c b/subprojects/libcapsule/tests/notgl-ref.c
new file mode 100644
index 0000000000000000000000000000000000000000..a1c7993bed9d2663caf6d6e7be26a19e79e7fc05
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-ref.c
@@ -0,0 +1,43 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgl-ref — reference implementation of libnotgl, taking the
+// role of the (possibly outdated) implementation of OpenGL in the container
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgl.h"
+
+#include <dlfcn.h>
+
+#include "notgl-helper.h"
+
+const char *
+notgl_get_implementation( void )
+{
+    return "reference";
+}
+
+const char *
+notgl_extension_both( void )
+{
+    return "reference implementation of common extension";
+}
+
+const char *
+notgl_use_helper( void )
+{
+    return helper_get_implementation ();
+}
diff --git a/subprojects/libcapsule/tests/notgl-user.c b/subprojects/libcapsule/tests/notgl-user.c
new file mode 100644
index 0000000000000000000000000000000000000000..40f08e101a8a33171d8378d7a08c360c5219eea3
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl-user.c
@@ -0,0 +1,64 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "notgl.h"
+
+static notgl_extension_function
+get_extension( const char *name )
+{
+    return dlsym( RTLD_DEFAULT, name );
+}
+
+int
+main ( int argc,
+       char **argv )
+{
+    notgl_extension_function f;
+
+    // Select line-buffering for output, in case we crash
+    setvbuf( stdout, NULL, _IOLBF, 0 );
+
+    printf( "NotGL implementation: %s\n", notgl_get_implementation() );
+    printf( "NotGL helper implementation: %s\n", notgl_use_helper() );
+
+    f = get_extension( "notgl_extension_both" );
+
+    if( f )
+        printf( "notgl_extension_both: %s\n", f() );
+    else
+        printf( "notgl_extension_both: (not found)\n" );
+
+    f = get_extension( "notgl_extension_red" );
+
+    if( f )
+        printf( "notgl_extension_red: %s\n", f() );
+    else
+        printf( "notgl_extension_red: (not found)\n" );
+
+    f = get_extension( "notgl_extension_green" );
+
+    if( f )
+        printf( "notgl_extension_green: %s\n", f() );
+    else
+        printf( "notgl_extension_green: (not found)\n" );
+
+    return 0;
+}
diff --git a/subprojects/libcapsule/tests/notgl.h b/subprojects/libcapsule/tests/notgl.h
new file mode 100644
index 0000000000000000000000000000000000000000..42b84d5f196ad66fd830b62ee48e03c50a5b8855
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl.h
@@ -0,0 +1,35 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgl — a library not entirely unlike OpenGL
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#define NOTGL_PUBLIC __attribute__((visibility("default")))
+
+typedef const char *(*notgl_extension_function)( void );
+
+NOTGL_PUBLIC
+const char *notgl_get_implementation( void );
+NOTGL_PUBLIC
+const char *notgl_extension_both( void );
+NOTGL_PUBLIC
+const char *notgl_extension_red( void );
+NOTGL_PUBLIC
+const char *notgl_extension_green( void );
+NOTGL_PUBLIC
+const char *notgl_use_helper( void );
diff --git a/subprojects/libcapsule/tests/notgl.pl b/subprojects/libcapsule/tests/notgl.pl
new file mode 100755
index 0000000000000000000000000000000000000000..4dd14f6a2ffa52c61787557c22f7bc161325a443
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgl.pl
@@ -0,0 +1,321 @@
+#!/usr/bin/env perl
+
+# notgl.pl — exercise libcapsule using an imitation of libGLX
+#
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+use autodie;
+use warnings;
+use strict;
+
+use Cwd qw(realpath);
+use File::Temp qw();
+use IPC::Run qw(run);
+use Test::More;
+
+use FindBin;
+use lib $FindBin::Bin;
+
+use CapsuleTest;
+
+skip_all_unless_bwrap;
+
+my $temp = File::Temp->newdir();
+my $capsule_prefix = "$temp/prefix";
+mkdir $capsule_prefix;
+
+my $libs = '';
+my $notgl_user = "$builddir/tests/notgl-user";
+my $notgl_helper_user = "$builddir/tests/notgl-helper-user";
+my $notgl_dlopener = "$builddir/tests/notgl-dlopener";
+my $stdout;
+
+if (exists $ENV{CAPSULE_TESTS_UNINSTALLED}) {
+    diag 'Running uninstalled: yes';
+    # We need to bypass libtool because it plays around with
+    # LD_LIBRARY_PATH, and so do we.
+    $libs = '/.libs';
+    run_ok([
+        "$builddir/libtool", qw(--mode=execute ls -1),
+        "$builddir/tests/notgl-user"
+    ], '>', \$notgl_user) or BAIL_OUT 'Cannot find notgl-user: $?';
+    chomp $notgl_user;
+    run_ok([
+        "$builddir/libtool", qw(--mode=execute ls -1),
+        "$builddir/tests/notgl-helper-user"
+    ], '>', \$notgl_helper_user) or BAIL_OUT 'Cannot find notgl-helper-user: $?';
+    chomp $notgl_helper_user;
+    run_ok([
+        "$builddir/libtool", qw(--mode=execute ls -1),
+        "$builddir/tests/notgl-dlopener"
+    ], '>', \$notgl_dlopener) or BAIL_OUT 'Cannot find notgl-dlopener: $?';
+    chomp $notgl_dlopener;
+}
+else {
+    diag 'Running uninstalled: no';
+}
+
+diag 'Without special measures:';
+run_ok(['env',
+        'LD_LIBRARY_PATH='.join(':', realpath("$builddir/tests/lib$libs"),
+            realpath("$builddir/tests/helper$libs")),
+        $notgl_user],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: reference$/m);
+like($stdout, qr/^NotGL helper implementation: container \(reference\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: reference implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+
+diag 'Without special measures (linked to libhelper):';
+run_ok(['env',
+        'LD_LIBRARY_PATH='.join(':', realpath("$builddir/tests/lib$libs"),
+            realpath("$builddir/tests/helper$libs")),
+        $notgl_helper_user],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: reference$/m);
+like($stdout, qr/^NotGL helper implementation: container \(reference\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: reference implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+like($stdout,
+    qr/^NotGL helper implementation as seen by executable: container \(reference\)$/m);
+like($stdout, qr/^NotGLES implementation: reference$/m);
+like($stdout, qr/^NotGLES helper implementation: container \(reference\)$/m);
+like($stdout,
+    qr/^notgles_extension_both: reference implementation of common extension$/m);
+like($stdout, qr/^notgles_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgles_extension_green: \(not found\)$/m);
+like($stdout,
+    qr/^NotGL helper implementation as seen by executable: container \(reference\)$/m);
+
+diag 'Without special measures (using dlopen):';
+run_ok(['env',
+        'LD_LIBRARY_PATH='.join(':', realpath("$builddir/tests/lib$libs"),
+            realpath("$builddir/tests/helper$libs")),
+        $notgl_dlopener],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: reference$/m);
+like($stdout, qr/^NotGL helper implementation: container \(reference\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: reference implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+like($stdout, qr/^NotGLES implementation: reference$/m);
+like($stdout, qr/^NotGLES helper implementation: container \(reference\)$/m);
+like($stdout,
+    qr/^notgles_extension_both: reference implementation of common extension$/m);
+like($stdout, qr/^notgles_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgles_extension_green: \(not found\)$/m);
+
+diag 'With libcapsule loading red implementation:';
+# We mount the "host system" on $capsule_prefix.
+#
+# In the "container", the shim libnotgl is picked up from tests/shim because
+# it was prepended to the LD_LIBRARY_PATH. The helper library used by
+# libnotgl, libhelper, is picked up from tests/helper as usual, and
+# we mount a tmpfs over tests/lib to avoid libnotgl being picked up
+# from the RPATH (in older toolchains that produce those instead
+# of RUNPATH, and cannot be overridden by LD_LIBRARY_PATH):
+#
+# emulated container on /
+# $builddir/
+#   tests/
+#     helper/ (contains reference libhelper)
+#     lib/ (empty)
+#     shim/ (contains shim libnotgl)
+#
+# In the "host system", there is a tmpfs over the build or installation
+# directory to avoid tests/shim being found *again*, and the "red"
+# implementations of libnotgl and libhelper are mounted over tests/lib
+# and tests/helper:
+#
+# emulated host filesystem on $capsule_prefix
+# $builddir/
+#   tests/
+#     helper/ (contains red libnotgl and libhelper)
+#     lib/ (contains red libnotgl and libhelper)
+#     shim/ (does not exist)
+run_ok([qw(bwrap
+        --ro-bind / /
+        --dev-bind /dev /dev
+        --ro-bind /), $capsule_prefix,
+        '--tmpfs', realpath("$builddir/tests/lib$libs"),
+        '--tmpfs', $capsule_prefix.realpath($builddir),
+        '--ro-bind', realpath("$builddir/tests/red"),
+            $capsule_prefix.realpath("$builddir/tests/helper"),
+        '--ro-bind', realpath("$builddir/tests/red"),
+            $capsule_prefix.realpath("$builddir/tests/lib"),
+        '--setenv', 'CAPSULE_PREFIX', $capsule_prefix,
+        '--setenv', 'LD_LIBRARY_PATH', join(':',
+            realpath("$builddir/tests/shim$libs"),
+            realpath("$builddir/tests/helper$libs"),
+            realpath("$builddir/tests/lib$libs"),
+        ),
+        $notgl_user],
+    '>', \$stdout);
+diag_multiline $stdout;
+
+# Functions from libnotgl get dispatched through the shim to the "red"
+# implementation from the "host system". This mirrors functions from libGL
+# being dispatched through the shim to the AMD implementation of libGL.
+like($stdout, qr/^NotGL implementation: red$/m);
+
+# When the "red" implementation of libnotgl calls functions from libhelper,
+# implementation from the "host system". This mirrors functions from
+# libstdc++ that are called by the host libGL ending up in the host libstdc++.
+like($stdout, qr/^NotGL helper implementation: host \(red\)$/m);
+
+# We can dlsym() for an implemementation of an extension that is part of
+# the ABI of the shim and the reference implementation.
+like($stdout,
+    qr/^notgl_extension_both: red implementation of common extension$/m);
+
+# We can also dlsym() for an implemementation of an extension that is only
+# available in the "red" implementation.
+like($stdout, qr/^notgl_extension_red: red-only extension$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+
+diag 'With libcapsule loading green implementation:';
+# Similar to the above, but now the host system is using the "green"
+# implementation of libnotgl, mirroring the NVIDIA implementation of
+# libGL; and the binary is linked to libhelper, so we can demonstrate
+# that libcapsule gives us the necessary cognitive dissonance to have
+# two copies of libhelper loaded simultaneously.
+run_ok([qw(bwrap
+        --ro-bind / /
+        --dev-bind /dev /dev
+        --ro-bind /), $capsule_prefix,
+        '--tmpfs', realpath("$builddir/tests/lib$libs"),
+        '--tmpfs', $capsule_prefix.realpath($builddir),
+        '--ro-bind', realpath("$builddir/tests/green"),
+            $capsule_prefix.realpath("$builddir/tests/lib"),
+        '--setenv', 'CAPSULE_PREFIX', $capsule_prefix,
+        '--setenv', 'LD_LIBRARY_PATH', join(':',
+            realpath("$builddir/tests/shim$libs"),
+            realpath("$builddir/tests/helper$libs"),
+            realpath("$builddir/tests/lib$libs"),
+        ),
+        $notgl_helper_user],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: green$/m);
+like($stdout, qr/^NotGL helper implementation: host \(green\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: green implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgl_extension_green: green-only extension$/m);
+like($stdout, qr/^NotGLES implementation: green$/m);
+like($stdout, qr/^NotGLES helper implementation: host \(green\)$/m);
+like($stdout,
+    qr/^notgles_extension_both: green implementation of common extension$/m);
+like($stdout, qr/^notgles_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgles_extension_green: green-only extension$/m);
+
+# Also, this program is linked directly to libhelper, mirroring a program
+# that is linked directly to libstdc++ in the libGL case. It sees the
+# container's libhelper, not the host's - even though libnotgl sees the
+# host's libhelper when it looks up the same symbol. (This is the point
+# of libcapsule.)
+like($stdout,
+    qr/^NotGL helper implementation as seen by executable: container \(reference\)$/m);
+
+# Using dlopen also works.
+diag 'With libcapsule loading red implementation, via dlopen:';
+run_ok([qw(bwrap
+        --ro-bind / /
+        --dev-bind /dev /dev
+        --ro-bind /), $capsule_prefix,
+        '--tmpfs', realpath("$builddir/tests/lib$libs"),
+        '--tmpfs', $capsule_prefix.realpath($builddir),
+        '--ro-bind', realpath("$builddir/tests/red"),
+            $capsule_prefix.realpath("$builddir/tests/lib"),
+        '--setenv', 'CAPSULE_PREFIX', $capsule_prefix,
+        '--setenv', 'LD_LIBRARY_PATH', join(':',
+            realpath("$builddir/tests/shim$libs"),
+            realpath("$builddir/tests/helper$libs"),
+            realpath("$builddir/tests/lib$libs"),
+        ),
+        $notgl_dlopener],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: red$/m);
+like($stdout, qr/^NotGL helper implementation: host \(red\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: red implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: red-only extension$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+like($stdout, qr/^NotGLES implementation: red$/m);
+like($stdout, qr/^NotGLES helper implementation: host \(red\)$/m);
+like($stdout,
+    qr/^notgles_extension_both: red implementation of common extension$/m);
+like($stdout, qr/^notgles_extension_red: red-only extension$/m);
+like($stdout, qr/^notgles_extension_green: \(not found\)$/m);
+
+# We can use separate prefixes for different encapsulated libraries.
+my $red_capsule_prefix = "$temp/red";
+mkdir $red_capsule_prefix;
+my $green_capsule_prefix = "$temp/green";
+mkdir $green_capsule_prefix;
+
+diag 'With libcapsule loading disparate implementations, via dlopen:';
+run_ok([qw(bwrap
+        --ro-bind / /
+        --dev-bind /dev /dev
+        --ro-bind /), $red_capsule_prefix,
+        qw(--ro-bind /), $green_capsule_prefix,
+        '--tmpfs', realpath("$builddir/tests/lib$libs"),
+        '--tmpfs', $red_capsule_prefix.realpath($builddir),
+        '--tmpfs', $green_capsule_prefix.realpath($builddir),
+        '--ro-bind', realpath("$builddir/tests/red"),
+            $red_capsule_prefix.realpath("$builddir/tests/lib"),
+        '--ro-bind', realpath("$builddir/tests/green"),
+            $green_capsule_prefix.realpath("$builddir/tests/lib"),
+        '--setenv', 'CAPSULE_LIBNOTGL_SO_0_PREFIX', $red_capsule_prefix,
+        # We don't specify CAPSULE_LIBNOTGLES_SO_1_PREFIX, so the
+        # generic version gets used.
+        '--setenv', 'CAPSULE_PREFIX', $green_capsule_prefix,
+        '--setenv', 'LD_LIBRARY_PATH', join(':',
+            realpath("$builddir/tests/shim$libs"),
+            realpath("$builddir/tests/helper$libs"),
+            realpath("$builddir/tests/lib$libs"),
+        ),
+        $notgl_dlopener],
+    '>', \$stdout);
+diag_multiline $stdout;
+like($stdout, qr/^NotGL implementation: red$/m);
+like($stdout, qr/^NotGL helper implementation: host \(red\)$/m);
+like($stdout,
+    qr/^notgl_extension_both: red implementation of common extension$/m);
+like($stdout, qr/^notgl_extension_red: red-only extension$/m);
+like($stdout, qr/^notgl_extension_green: \(not found\)$/m);
+like($stdout, qr/^NotGLES implementation: green$/m);
+like($stdout, qr/^NotGLES helper implementation: host \(green\)$/m);
+like($stdout,
+    qr/^notgles_extension_both: green implementation of common extension$/m);
+like($stdout, qr/^notgles_extension_red: \(not found\)$/m);
+like($stdout, qr/^notgles_extension_green: green-only extension$/m);
+
+done_testing;
+
+# vim:set sw=4 sts=4 et:
diff --git a/subprojects/libcapsule/tests/notgles-green.c b/subprojects/libcapsule/tests/notgles-green.c
new file mode 100644
index 0000000000000000000000000000000000000000..1b2f735284ee305cc7ee91a5598b46419b830b03
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgles-green.c
@@ -0,0 +1,49 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgles-green — one of two implementations of libnotgles, taking the
+// role of the NVIDIA implementation of GLES
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgles.h"
+
+#include <dlfcn.h>
+
+#include "notgl-helper.h"
+
+const char *
+notgles_get_implementation( void )
+{
+    return "green";
+}
+
+const char *
+notgles_extension_both( void )
+{
+    return "green implementation of common extension";
+}
+
+const char *
+notgles_extension_green( void )
+{
+    return "green-only extension";
+}
+
+const char *
+notgles_use_helper( void )
+{
+    return helper_get_implementation();
+}
diff --git a/subprojects/libcapsule/tests/notgles-red.c b/subprojects/libcapsule/tests/notgles-red.c
new file mode 100644
index 0000000000000000000000000000000000000000..a56dd1fb10b8a9d070f41cce009951a8422155dc
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgles-red.c
@@ -0,0 +1,49 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgles-red — one of two implementations of libnotgles, taking the
+// role of the AMD implementation of GLES
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgles.h"
+
+#include <dlfcn.h>
+
+#include "notgl-helper.h"
+
+const char *
+notgles_get_implementation( void )
+{
+    return "red";
+}
+
+const char *
+notgles_extension_both( void )
+{
+    return "red implementation of common extension";
+}
+
+const char *
+notgles_extension_red( void )
+{
+    return "red-only extension";
+}
+
+const char *
+notgles_use_helper( void )
+{
+    return helper_get_implementation ();
+}
diff --git a/subprojects/libcapsule/tests/notgles-ref.c b/subprojects/libcapsule/tests/notgles-ref.c
new file mode 100644
index 0000000000000000000000000000000000000000..df8a6bf1d52ed6c95aeb0ec4881323dfd1c9fd79
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgles-ref.c
@@ -0,0 +1,43 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgles-ref — reference implementation of libnotgles, taking the
+// role of the (possibly outdated) implementation of GLES in the container
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "notgles.h"
+
+#include <dlfcn.h>
+
+#include "notgl-helper.h"
+
+const char *
+notgles_get_implementation( void )
+{
+    return "reference";
+}
+
+const char *
+notgles_extension_both( void )
+{
+    return "reference implementation of common extension";
+}
+
+const char *
+notgles_use_helper( void )
+{
+    return helper_get_implementation ();
+}
diff --git a/subprojects/libcapsule/tests/notgles.h b/subprojects/libcapsule/tests/notgles.h
new file mode 100644
index 0000000000000000000000000000000000000000..ad5fe26db2ab74d24f1095485f0ccac95b63be4f
--- /dev/null
+++ b/subprojects/libcapsule/tests/notgles.h
@@ -0,0 +1,35 @@
+// Copyright © 2017 Collabora Ltd
+
+// notgles — a library not entirely unlike GLES
+
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#define NOTGLES_PUBLIC __attribute__((visibility("default")))
+
+typedef const char *(*notgles_extension_function)( void );
+
+NOTGLES_PUBLIC
+const char *notgles_get_implementation( void );
+NOTGLES_PUBLIC
+const char *notgles_extension_both( void );
+NOTGLES_PUBLIC
+const char *notgles_extension_red( void );
+NOTGLES_PUBLIC
+const char *notgles_extension_green( void );
+NOTGLES_PUBLIC
+const char *notgles_use_helper( void );
diff --git a/subprojects/libcapsule/tests/shim/libnotgl.so.0.excluded b/subprojects/libcapsule/tests/shim/libnotgl.so.0.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/tests/shim/libnotgl.so.0.shared b/subprojects/libcapsule/tests/shim/libnotgl.so.0.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/tests/shim/libnotgl.so.0.symbols b/subprojects/libcapsule/tests/shim/libnotgl.so.0.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..338681e486b56f67aff9b164aa9fb254081217e3
--- /dev/null
+++ b/subprojects/libcapsule/tests/shim/libnotgl.so.0.symbols
@@ -0,0 +1,2 @@
+notgl_get_implementation
+notgl_use_helper
diff --git a/subprojects/libcapsule/tests/shim/libnotgles.so.1.excluded b/subprojects/libcapsule/tests/shim/libnotgles.so.1.excluded
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/tests/shim/libnotgles.so.1.shared b/subprojects/libcapsule/tests/shim/libnotgles.so.1.shared
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/subprojects/libcapsule/tests/shim/libnotgles.so.1.symbols b/subprojects/libcapsule/tests/shim/libnotgles.so.1.symbols
new file mode 100644
index 0000000000000000000000000000000000000000..8662801fadc045c0ca359a5273b13a7320e0efcd
--- /dev/null
+++ b/subprojects/libcapsule/tests/shim/libnotgles.so.1.symbols
@@ -0,0 +1,2 @@
+notgles_get_implementation
+notgles_use_helper
diff --git a/subprojects/libcapsule/tests/symbols.pl b/subprojects/libcapsule/tests/symbols.pl
new file mode 100755
index 0000000000000000000000000000000000000000..3d19896cee2551a4c5abb4c2538938c650a8582d
--- /dev/null
+++ b/subprojects/libcapsule/tests/symbols.pl
@@ -0,0 +1,90 @@
+#!/usr/bin/perl
+
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+use autodie;
+use warnings;
+use strict;
+
+use File::Temp qw();
+use IPC::Run qw(run);
+use Test::More;
+
+use FindBin;
+use lib $FindBin::Bin;
+
+use CapsuleTest;
+
+skip_all_unless_bwrap;
+
+my $test_tempdir = File::Temp->newdir();
+diag "Working directory: $test_tempdir";
+my $host = "${test_tempdir}/host";
+mkdir($host);
+
+if (length $ENV{CAPSULE_TESTS_UNINSTALLED}) {
+    my $path = $ENV{LD_LIBRARY_PATH};
+
+    if (length $path) {
+        $path = "$ENV{G_TEST_BUILDDIR}/.libs:$path";
+    }
+    else {
+        $path = $ENV{G_TEST_BUILDDIR};
+    }
+
+    $ENV{LD_LIBRARY_PATH} = $path;
+
+    TODO: {
+        local $TODO = "capsule-symbols doesn't respect LD_LIBRARY_PATH";
+        fail 'Re-enable this later';
+    }
+}
+else {
+    # We need a well-behaved library with a simple ABI to inspect. Let's use
+    # libcapsule itself :-)
+    my $output;
+    run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+            qw(--dev-bind /dev /dev),
+            $CAPSULE_SYMBOLS_TOOL, 'libcapsule.so.0', $host], '>', \$output);
+    diag_multiline $output;
+    return unless $output;
+
+    like($output, qr{^capsule_init $}m, "capsule_init is an unversioned symbol");
+    unlike($output, qr{^(?!capsule)}m, "all of libcapsule's ABI matches /^capsule/");
+
+    # Try the same thing without a prefix
+    run_ok([$CAPSULE_SYMBOLS_TOOL, 'libcapsule.so.0', '/'], '>', \$output);
+    diag_multiline $output;
+
+    like($output, qr{^capsule_init $}m, "capsule_init is an unversioned symbol");
+    unlike($output, qr{^(?!capsule)}m, "all of libcapsule's ABI matches /^capsule/");
+}
+
+# How about versioned symbols?
+my $output;
+run_ok([$CAPSULE_SYMBOLS_TOOL, 'libxml2.so.2', '/'], '>', \$output);
+diag_multiline $output;
+
+like($output, qr{^xmlFree \@\@LIBXML2_2\.4\.30$}m,
+    "xmlFree is a versioned symbol");
+unlike($output, qr{^xmlFree (?!\@\@LIBXML2_2\.4\.30)}m,
+    "xmlFree does not appear unversioned");
+
+done_testing;
+
+# vim:set sw=4 sts=4 et:
diff --git a/subprojects/libcapsule/tests/test-helpers.c b/subprojects/libcapsule/tests/test-helpers.c
new file mode 100644
index 0000000000000000000000000000000000000000..3281b71f8907e4d924d465a1a0e4f0e9a5e20fa8
--- /dev/null
+++ b/subprojects/libcapsule/tests/test-helpers.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright © 2019-2020 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "test-helpers.h"
+
+#include <ftw.h>
+#include <stdio.h>
+
+static int
+ftw_remove (const char *path,
+            const struct stat *sb,
+            int typeflags,
+            struct FTW *ftwbuf)
+{
+  if (remove (path) < 0)
+    return -1;
+
+  return 0;
+}
+
+gboolean
+rm_rf (const char *path)
+{
+  if (nftw (path, ftw_remove, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) < 0)
+    return FALSE;
+
+  return TRUE;
+}
diff --git a/subprojects/libcapsule/tests/test-helpers.h b/subprojects/libcapsule/tests/test-helpers.h
new file mode 100644
index 0000000000000000000000000000000000000000..b19be776608b6c59ce13cc61125bbc661752e761
--- /dev/null
+++ b/subprojects/libcapsule/tests/test-helpers.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright © 2019 Collabora Ltd.
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+gboolean rm_rf (const char *path);
+
+/*
+ * Simplified implementations of some of the GLib test assertion macros,
+ * for use with older GLib versions.
+ */
+
+#ifndef g_assert_true
+#define g_assert_true(x) g_assert ((x))
+#endif
+
+#ifndef g_assert_false
+#define g_assert_false(x) g_assert (!(x))
+#endif
+
+#ifndef g_assert_cmpint
+#define g_assert_cmpint(a, op, b) g_assert ((a) op (b))
+#endif
+
+#ifndef g_assert_cmpmem
+#define g_assert_cmpmem(m1, l1, m2, l2) \
+    g_assert (l1 == l2 && memcmp (m1, m2, l1) == 0)
+#endif
+
+#ifndef g_assert_cmpstr
+#define g_assert_cmpstr(a, op, b) g_assert (g_strcmp0 ((a), (b)) op 0)
+#endif
+
+#ifndef g_assert_nonnull
+#define g_assert_nonnull(x) g_assert ((x) != NULL)
+#endif
+
+#ifndef g_assert_null
+#define g_assert_null(x) g_assert ((x) == NULL)
+#endif
+
+#if !GLIB_CHECK_VERSION(2, 38, 0)
+#define g_test_skip(msg) g_test_message ("SKIP: %s", msg)
+#endif
diff --git a/subprojects/libcapsule/tests/utils.c b/subprojects/libcapsule/tests/utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..deb0e03ae3bda36302dd2633e0c793f636ddc312
--- /dev/null
+++ b/subprojects/libcapsule/tests/utils.c
@@ -0,0 +1,820 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+#include "tests/test-helpers.h"
+#include "utils/library-cmp.h"
+#include "utils/utils.h"
+
+static const char *argv0;
+
+#define assert_with_errno(expr) \
+  do { \
+    errno = 0; \
+    \
+    if (!(expr)) \
+      g_error ("Assertion failed: %s: %s", #expr, g_strerror (errno)); \
+  } while (0)
+
+static void
+touch (const char *path)
+{
+  FILE *fh;
+
+  assert_with_errno((fh = fopen (path, "w")) != NULL);
+  fclose (fh);
+}
+
+typedef struct
+{
+  gchar *srcdir;
+  gchar *builddir;
+  gboolean uninstalled;
+} Fixture;
+
+static void
+setup (Fixture *f,
+       gconstpointer data)
+{
+  f->srcdir = g_strdup (g_getenv ("G_TEST_SRCDIR"));
+  f->builddir = g_strdup (g_getenv ("G_TEST_BUILDDIR"));
+
+  if (f->srcdir == NULL)
+    f->srcdir = g_path_get_dirname (argv0);
+
+  if (f->builddir == NULL)
+    f->builddir = g_path_get_dirname (argv0);
+
+  f->uninstalled = (g_getenv ("CAPSULE_TESTS_UNINSTALLED") != NULL);
+}
+
+static struct
+{
+  const char * const parts[3];
+  const char *expected;
+} filename_tests[] =
+{
+    { { "/host", "/usr/lib", "libc.so.6" }, "/host/usr/lib/libc.so.6" },
+    { { "/usr/lib", "/libc.so.6", NULL }, "/usr/lib/libc.so.6" },
+    { { "", "/usr/lib", "libc.so.6" }, "/usr/lib/libc.so.6" },
+    { { "/", "usr/lib", "libc.so.6" }, "/usr/lib/libc.so.6" },
+    { { "///host///", "///usr/lib///", "///libc.so.6" },
+      "/host/usr/lib/libc.so.6" },
+    { { NULL, "xxxxxxxxxxxxxxxx", NULL }, "" },
+    { { "", NULL }, "" },
+    { { "", "/etc/ld.so.cache", NULL }, "/etc/ld.so.cache" },
+    { { "", "etc/ld.so.cache", NULL }, "/etc/ld.so.cache" },
+    { { "/", "/etc/ld.so.cache", NULL }, "/etc/ld.so.cache" },
+    { { "/", "etc/ld.so.cache", NULL }, "/etc/ld.so.cache" },
+    { { "foo", "/bar", NULL }, "foo/bar" },
+    { { "foo", "bar", NULL }, "foo/bar" },
+};
+
+static void
+test_build_filename (Fixture *f,
+                     gconstpointer data)
+{
+  unsigned i;
+
+  for (i = 0; i < G_N_ELEMENTS (filename_tests); i++)
+    {
+      const char * const *parts = filename_tests[i].parts;
+      const char *expected = filename_tests[i].expected;
+      const size_t allocated = strlen (expected) + 5;
+      gchar *buf = g_malloc (allocated);
+      char *str = NULL;
+      size_t used;
+      size_t len = allocated;
+
+      if (parts[1] == NULL)
+        str = build_filename_alloc (parts[0], NULL);
+      else if (parts[2] == NULL)
+        str = build_filename_alloc (parts[0], parts[1], NULL);
+      else
+        str = build_filename_alloc (parts[0], parts[1], parts[2], NULL);
+
+      g_assert_cmpstr (str, ==, expected);
+
+      do
+        {
+          size_t j;
+
+          memset (buf, '\xaa', allocated);
+
+          if (parts[1] == NULL)
+            {
+              used = build_filename (buf, len, parts[0], NULL);
+            }
+          else if (parts[2] == NULL)
+            {
+              used = build_filename (buf, len, parts[0], parts[1], NULL);
+            }
+          else
+            {
+              used = build_filename (buf, len, parts[0], parts[1], parts[2],
+                                     NULL);
+            }
+
+          g_test_message ("\"%s\", \"%s\", \"%s\" -> %zu \"%s\"",
+                          parts[0], parts[1], parts[2], used,
+                          len == 0 ? NULL : buf);
+
+          g_assert_cmpuint (used, ==, strlen (expected));
+
+          if (len == 0)
+            {
+              /* Stupid corner case: we can't write anything into the buffer */
+            }
+          else if (used >= len)
+            {
+              gchar *truncated = g_strndup (expected, len - 1);
+
+              g_assert_cmpstr (buf, ==, truncated);
+              g_free (truncated);
+            }
+          else
+            {
+              g_assert_cmpstr (buf, ==, expected);
+            }
+
+          /* The rest of the buffer is untouched (we didn't overflow) */
+          for (j = len; j < allocated; j++)
+            {
+              g_assert_cmpint (buf[j], ==, '\xaa');
+            }
+        }
+      while (--len > 0);
+
+      g_free (buf);
+      free (str);
+    }
+}
+
+typedef struct
+{
+  const char *soname;
+  const char *in_container;
+  char cmp;
+  const char *in_provider;
+} CmpByNameTest;
+
+static const CmpByNameTest cmp_by_name_tests[] =
+{
+  { "libdbus-1.so.3", "libdbus-1.so.3.1", '<', "libdbus-1.so.3.2" },
+  { "libdbus-1.so.3", "libdbus-1.so.3.1.2", '>', "libdbus-1.so.3.1.1" },
+  { "libdbus-1.so.3", "libdbus-1.so.3.1", '=', "libdbus-1.so.3.1" },
+  { "libc.so.6", "libc-2.19.so", '<', "libc-2.22.so" },
+  { "libgcc_s.so.1", "libgcc_s-20200703.so.1", '>', "libgcc_s-20120401.so.1" },
+  { "libgcc_s.so.1", "libgcc_s-20200703.so.1", '=', NULL },
+  { "libgcc_s.so.1", NULL, '=', "libgcc_s-20200703.so.1" },
+  { "libgcc_s.so.1", NULL, '=', NULL },
+};
+
+static void
+test_library_cmp_by_name (Fixture *f,
+                          gconstpointer data)
+{
+  GError *error = NULL;
+  gchar *tmpdir = g_dir_make_tmp ("libcapsule.XXXXXX", &error);
+  gchar *container;
+  gchar *provider;
+  gsize i;
+  library_cmp_function *comparators;
+
+  g_assert_no_error (error);
+  g_assert_nonnull (tmpdir);
+
+  comparators = library_cmp_list_from_string ("name", ",", NULL, NULL);
+  g_assert_nonnull (comparators);
+
+  container = g_build_filename (tmpdir, "c", NULL);
+  provider = g_build_filename (tmpdir, "p", NULL);
+
+  assert_with_errno (g_mkdir (container, 0755) == 0);
+  assert_with_errno (g_mkdir (provider, 0755) == 0);
+
+  for (i = 0; i < G_N_ELEMENTS (cmp_by_name_tests); i++)
+    {
+      const CmpByNameTest *test = &cmp_by_name_tests[i];
+      gchar *container_file = NULL;
+      gchar *container_lib = g_build_filename (container, test->soname, NULL);
+      gchar *provider_file = NULL;
+      gchar *provider_lib = g_build_filename (provider, test->soname, NULL);
+      gchar *soname_copy = g_strdup (test->soname);
+      int result;
+      const library_details comparator_details = {
+        .name = soname_copy,
+        .comparators = comparators,
+        .public_symbol_versions = NULL,
+        .public_symbols = NULL,
+      };
+
+      assert_with_errno (g_unlink (container_lib) == 0 || errno == ENOENT);
+      assert_with_errno (g_unlink (provider_lib) == 0 || errno == ENOENT);
+
+      if (test->in_container == NULL)
+        {
+          touch (container_lib);
+        }
+      else
+        {
+          container_file = g_build_filename (container, test->in_container, NULL);
+          assert_with_errno (g_unlink (container_file) == 0 || errno == ENOENT);
+          touch (container_file);
+          assert_with_errno (symlink (test->in_container, container_lib) == 0);
+        }
+
+      if (test->in_provider == NULL)
+        {
+          touch (provider_lib);
+        }
+      else
+        {
+          provider_file = g_build_filename (provider, test->in_provider, NULL);
+          assert_with_errno (g_unlink (provider_file) == 0 || errno == ENOENT);
+          touch (provider_file);
+          assert_with_errno (symlink (test->in_provider, provider_lib) == 0);
+        }
+
+      result = library_cmp_list_iterate (&comparator_details,
+                                         container_lib, container,
+                                         provider_lib, provider);
+
+      switch (test->cmp)
+        {
+          case '<':
+            if (result >= 0)
+              g_error ("Expected %s (%s) < %s (%s), got %d",
+                       container_lib,
+                       container_file ? container_file : "regular file",
+                       provider_lib,
+                       provider_file ? provider_file : "regular file",
+                       result);
+            break;
+
+          case '>':
+            if (result <= 0)
+              g_error ("Expected %s (%s) > %s (%s), got %d",
+                       container_lib,
+                       container_file ? container_file : "regular file",
+                       provider_lib,
+                       provider_file ? provider_file : "regular file",
+                       result);
+            break;
+
+          case '=':
+            if (result != 0)
+              g_error ("Expected %s (%s) == %s (%s), got %d",
+                       container_lib,
+                       container_file ? container_file : "regular file",
+                       provider_lib,
+                       provider_file ? provider_file : "regular file",
+                       result);
+            break;
+
+          default:
+            g_assert_not_reached ();
+        }
+
+      g_free (container_file);
+      g_free (container_lib);
+      g_free (provider_file);
+      g_free (provider_lib);
+      g_free (soname_copy);
+    }
+
+  assert_with_errno (rm_rf (tmpdir));
+  g_free (container);
+  g_free (provider);
+  g_free (tmpdir);
+  free (comparators);
+}
+
+typedef struct
+{
+  const char *soname;
+  const char *spec;
+  const char *public_symbol_versions[8];
+  const char *public_symbols[8];
+  /* It compares such that "version1 cmp version2" is true, except for
+   * the special case that "c" and "p" mean we choose the container version
+   * and provider version respectively */
+  char cmp;
+} CmpTest;
+
+#ifdef ENABLE_SHARED
+static const CmpTest cmp_tests[] =
+{
+  /* BY SYMBOLS */
+
+  /* This adds one symbol and removes one symbol, so we can't tell which
+   * was meant to be newer */
+  { "libunversionedabibreak.so.1", "symbols", {}, {}, '=' },
+  { "libversionedabibreak.so.1", "symbols", {}, {}, '=' },
+
+  /* The only difference here is the tail of the filename, which this
+   * comparator doesn't look at */
+  { "libunversionednumber.so.1", "symbols", {}, {}, '=' },
+  { "libversionednumber.so.1", "symbols", {}, {}, '=' },
+
+  /* This is the situation this comparator handles */
+  { "libunversionedsymbols.so.1", "symbols", {}, {}, '<' },
+  { "libversionedsymbols.so.1", "symbols", {}, {}, '<' },
+  { "libversionedupgrade.so.1", "symbols", {}, {}, '<' },
+  { "libversionedlikeglibc.so.1", "symbols", {}, {}, '<' },
+  { "libversionedprivatenothidden.so.1", "symbols", {}, {}, '<' },
+
+  /* We can't currently tell which one is newer because the private symbols
+   * confuse us */
+  { "libversionedlikedbus.so.1", "symbols", {}, {}, '=' },
+
+  /* After filtering the private symbols, they have the same symbols */
+  { "libversionedprivatenothidden.so.1", "symbols",
+    {}, { "!_private*", "!LIBVERSIONEDPRIVATE*", "*", NULL }, '=' },
+
+  /* Same as before, but specify the exact symbols name */
+  { "libversionedprivatenothidden.so.1", "symbols",
+    {},
+    { "!_private1", "!_private2", "!LIBVERSIONEDPRIVATE_1_4_0", "!LIBVERSIONEDPRIVATE_2_1_0", "*", NULL },
+    '=' },
+
+  /* Same as above, but test listing public symbol versions */
+  { "libversionedprivatenothidden.so.1", "symbols",
+    {}, { "symbol*", "!*", NULL }, '=' },
+
+  /* Same as above, but use the "after this point is just a guess" special pattern */
+  { "libversionedprivatenothidden.so.1", "symbols",
+    {}, { "symbol*", "!", "!*", NULL }, '=' },
+
+  /* BY VERSION DEFINITIONS */
+
+  /* All of these have no symbol-versioning, so we can't tell a
+   * difference with this comparator */
+  { "libunversionedabibreak.so.1", "versions", {}, {}, '=' },
+  { "libunversionednumber.so.1", "versions", {}, {}, '=' },
+  { "libunversionedsymbols.so.1", "versions", {}, {}, '=' },
+
+  /* This adds one verdef and removes one verdef, so we can't tell which
+   * was meant to be newer */
+  { "libversionedabibreak.so.1", "versions", {}, {}, '=' },
+
+  /* The only difference here is the tail of the filename, which this
+   * comparator doesn't look at */
+  { "libversionednumber.so.1", "versions", {}, {}, '=' },
+
+  /* This is simple "version ~= SONAME" symbol-versioning, like in libtiff
+   * and libpng, so this comparator can't tell any difference */
+  { "libversionedsymbols.so.1", "versions", {}, {}, '=' },
+
+  /* This one has version-specific verdefs like libmount, libgcab, OpenSSL,
+   * telepathy-glib etc., so we can tell it's an upgrade */
+  { "libversionedupgrade.so.1", "versions", {}, {}, '<' },
+
+  /* This one has the same symbol listed in more than one verdef,
+   * like glibc - we can tell this is an upgrade */
+  { "libversionedlikeglibc.so.1", "versions", {}, {}, '<' },
+
+  /* We can't currently tell which one is newer because the private verdefs
+   * confuse us */
+  { "libversionedlikedbus.so.1", "versions", {}, {}, '=' },
+
+  /* This one has more private verdefs, but we don't know that they are
+     private */
+  { "libversionedprivatenothidden.so.1", "versions", {}, {}, '<' },
+
+  /* After filtering the private verdefs, they have the same public verdefs */
+  { "libversionedprivatenothidden.so.1", "versions",
+    { "!LIBVERSIONEDPRIVATE*", "*", NULL }, {}, '=' },
+
+  /* Same as above, but test using multiple private symbol versions filters */
+  { "libversionedprivatenothidden.so.1", "versions",
+    { "!LIBVERSIONEDPRIVATE_1_4_0", "!LIBVERSIONEDPRIVATE_2_1_0", "!MISSING*", "*", NULL },
+    {}, '=' },
+
+  /* Same as above, but test listing public symbol versions */
+  { "libversionedprivatenothidden.so.1", "versions",
+    { "LIBVERSIONED1*", "!*", NULL }, {}, '=' },
+
+  /* After partially filtering the private verdefs, we think it's an upgrade
+   * because it has more verdefs */
+  { "libversionedprivatenothidden.so.1", "versions",
+    { "!LIBVERSIONEDPRIVATE_1*", "*", NULL }, {}, '<' },
+
+  /* BY NAME */
+
+  /* These have the version number in the filename. */
+  { "libunversionednumber.so.1", "name", {}, {}, '<' },
+  { "libversionedlikedbus.so.1", "name", {}, {}, '<' },
+  { "libversionednumber.so.1", "name", {}, {}, '<' },
+
+  /* These have the same filename in both versions, so we can't tell. */
+  { "libunversionedabibreak.so.1", "name", {}, {}, '=' },
+  { "libunversionedsymbols.so.1", "name", {}, {}, '=' },
+  { "libversionedabibreak.so.1", "name", {}, {}, '=' },
+  { "libversionedsymbols.so.1", "name", {}, {}, '=' },
+  { "libversionedupgrade.so.1", "name", {}, {}, '=' },
+  { "libversionedlikeglibc.so.1", "name", {}, {}, '=' },
+
+  /* BY MORE THAN ONE FACTOR */
+
+  { "libversionedlikeglibc.so.1", "name,versions", {}, {}, '<' },
+  { "libversionedlikeglibc.so.1", "name,container", {}, {}, 'c' },
+  { "libversionedlikeglibc.so.1", "name,container,provider", {}, {}, 'c' },
+  { "libversionedlikeglibc.so.1", "name,provider", {}, {}, 'p' },
+  { "libversionedlikeglibc.so.1", "name,provider,container", {}, {}, 'p' },
+  { "libversionedlikeglibc.so.1", "versions,name", {}, {}, '<' },
+  { "libversionedlikeglibc.so.1", "versions,container", {}, {}, '<' },
+  { "libversionedlikeglibc.so.1", "versions,provider", {}, {}, '<' },
+  { "libversionedlikeglibc.so.1", "name,symbols", {}, {}, '<' },
+  { "libversionedlikeglibc.so.1", "symbols,provider", {}, {}, '<' },
+  { "libversionedprivatenothidden.so.1", "versions,symbols",
+    { "!LIBVERSIONEDPRIVATE*", "*", NULL },
+    { "!_private*", "!LIBVERSIONED*", "*", NULL }, '=' },
+
+  /* This one is a stand-in for libgcc_s.so.1 */
+  { "libversionedupgrade.so.1", "name", {}, {}, '=' },
+  { "libversionedupgrade.so.1", "versions,name,symbols", {}, {}, '<' },
+  { "libversionedupgrade.so.1", "name,versions", {}, {}, '<' },
+
+  /* These are obviously silly - just assert that they don't crash. */
+  { "libversionedupgrade.so.1", "", {}, {}, '=' },
+  { "libversionedupgrade.so.1", ",,,,,,", {}, {}, '=' },
+  { "libversionedupgrade.so.1", "name,,,,,,name,,,,,,,,,,,,,,,,,,,", {}, {}, '=' },
+  { "libversionedupgrade.so.1",
+    "name,name,name,name,name,name,name,name,name,name,name,name,name,name",
+    {}, {}, '=' },
+};
+
+static void
+test_library_cmp (Fixture *f,
+                  gconstpointer data)
+{
+  gsize i;
+
+  for (i = 0; i < G_N_ELEMENTS (cmp_tests); i++)
+    {
+      const CmpTest *test = &cmp_tests[i];
+      library_cmp_function *comparators;
+      gchar *v1;
+      gchar *v1_lib;
+      gchar *v2;
+      gchar *v2_lib;
+      gchar *soname_copy = g_strdup (test->soname);
+      int result;
+
+      comparators = library_cmp_list_from_string (test->spec, ",", NULL, NULL);
+      g_assert_nonnull (comparators);
+
+      const library_details comparator_details = {
+        .name = soname_copy,
+        .comparators = comparators,
+        .public_symbol_versions = test->public_symbol_versions[0] != NULL ? (char **) test->public_symbol_versions: NULL,
+        .public_symbols = test->public_symbols[0] != NULL ? (char **) test->public_symbols: NULL,
+      };
+
+      v1 = g_build_filename (f->builddir, "tests", "version1", NULL);
+      v1_lib = g_build_filename (v1, f->uninstalled ? ".libs" : ".",
+                                 test->soname, NULL);
+      v2 = g_build_filename (f->builddir, "tests", "version2", NULL);
+      v2_lib = g_build_filename (v2, f->uninstalled ? ".libs" : ".",
+                                 test->soname, NULL);
+
+      result = library_cmp_list_iterate (&comparator_details,
+                                         v1_lib, v1,
+                                         v2_lib, v2);
+
+      switch (test->cmp)
+        {
+          case '<':   // v1 < v2
+          case 'p':   // choose provider, i.e. v2 here
+            if (result >= 0)
+              g_error ("Expected %s < %s by (%s), got %d",
+                       v1_lib, v2_lib, test->spec, result);
+            break;
+
+          case '>':   // v1 > v2
+          case 'c':   // choose container, i.e. v1 here
+            if (result <= 0)
+              g_error ("Expected %s > %s by (%s), got %d",
+                       v1_lib, v2_lib, test->spec, result);
+            break;
+
+          case '=':
+            if (result != 0)
+              g_error ("Expected %s == %s by (%s), got %d",
+                       v1_lib, v2_lib, test->spec, result);
+            break;
+
+          default:
+            g_assert_not_reached ();
+        }
+
+      /* We get the reverse result when we do it the other way round -
+       * unless the expected result is "c" or "p", in which case we
+       * get the same result, because those results are determined by
+       * the position of the arguments rather than by their content. */
+      result = library_cmp_list_iterate (&comparator_details,
+                                         v2_lib, v2,
+                                         v1_lib, v1);
+
+      switch (test->cmp)
+        {
+          case '>':   // v1 > v2, i.e. v2 < v1
+          case 'p':   // choose provider, i.e. v1 here
+            if (result >= 0)
+              g_error ("Expected %s < %s by (%s), got %d",
+                       v2_lib, v1_lib, test->spec, result);
+            break;
+
+          case '<':   // v1 < v2, i.e. v2 > v1
+          case 'c':   // choose container, i.e. v2 here
+            if (result <= 0)
+              g_error ("Expected %s > %s by (%s), got %d",
+                       v2_lib, v1_lib, test->spec, result);
+            break;
+
+          case '=':
+            if (result != 0)
+              g_error ("Expected %s == %s by (%s), got %d",
+                       v2_lib, v1_lib, test->spec, result);
+            break;
+
+          default:
+            g_assert_not_reached ();
+        }
+
+
+      g_free (v1);
+      g_free (v1_lib);
+      g_free (v2);
+      g_free (v2_lib);
+      g_free (soname_copy);
+      free (comparators);
+    }
+}
+#endif
+
+char library_ini_part_1[] =
+"# Configuration for capsule-capture-libs\n"
+"\n"
+"[Library libc.so.6]\n"
+"CompareBy=versions;symbols;name;\n"
+"\n"
+"[Library libgcc_s.so.1]\n"
+"CompareBy=versions;\n"
+"\n"
+"[Library libwhatsmynameagain.so.0]\n"
+"CompareBy=name;\n"
+"\n"
+"[Library libcountmysymbols.so.0]\n"
+"CompareBy=symbols;\n"
+"\n"
+"[Not a library]\n"
+"# Ignore this\n"
+"CompareBy=By guesswork; by magic; randomly\n"
+"whatever=Whatever\n"
+"\n"
+"[Library libdbus-1.so.3]\n"
+"\n"
+"[Library libdbus-1.so.3]\n"
+"CompareBy=provider;container;\n"
+"";
+
+char library_ini_part_2[] =
+"[Library libglib-2.0.so.0]\n"
+"\n"
+"[Library libdbus-1.so.3]\n"
+"CompareBy=versions;name;symbols;\n"
+"private-symbols=_*;dbus_internal_do_not_use_*;\n"
+"private-versions=LIBDBUS_PRIVATE_*;\n"
+"";
+
+static void
+test_library_knowledge_good (Fixture *f,
+                             gconstpointer data)
+{
+  library_knowledge knowledge = LIBRARY_KNOWLEDGE_INIT;
+  const library_details *libc;
+  const library_details *libgcc;
+  const library_details *name;
+  const library_details *symbols;
+  const library_details *libdbus;
+  const library_details *glib;
+  const library_details *nope;
+  int code = -1;
+  char *message = NULL;
+  FILE *fh;
+
+  assert_with_errno ((fh = fmemopen ((char *) library_ini_part_1,
+                                     sizeof (library_ini_part_1), "r")) != NULL);
+
+  if (!library_knowledge_load_from_stream (&knowledge, fh, "library.ini.1",
+                                           &code, &message))
+    g_error ("%s", message);
+
+  g_assert_cmpstr (message, ==, NULL);
+  g_assert_cmpuint (code, ==, -1);
+  assert_with_errno (fclose (fh) == 0);
+
+  assert_with_errno ((fh = fmemopen ((char *) library_ini_part_2,
+                                     sizeof (library_ini_part_2), "r")) != NULL);
+
+  if (!library_knowledge_load_from_stream (&knowledge, fh, "library.ini.2",
+                                           &code, &message))
+    g_error ("%s", message);
+
+  g_assert_cmpstr (message, ==, NULL);
+  g_assert_cmpuint (code, ==, -1);
+  assert_with_errno (fclose (fh) == 0);
+
+  libc = library_knowledge_lookup (&knowledge, "libc.so.6");
+  g_assert_nonnull (libc);
+  g_assert_cmpstr (libc->name, ==, "libc.so.6");
+  libgcc = library_knowledge_lookup (&knowledge, "libgcc_s.so.1");
+  g_assert_nonnull (libgcc);
+  g_assert_cmpstr (libgcc->name, ==, "libgcc_s.so.1");
+  name = library_knowledge_lookup (&knowledge, "libwhatsmynameagain.so.0");
+  g_assert_nonnull (name);
+  g_assert_cmpstr (name->name, ==, "libwhatsmynameagain.so.0");
+  symbols = library_knowledge_lookup (&knowledge, "libcountmysymbols.so.0");
+  g_assert_nonnull (symbols);
+  g_assert_cmpstr (symbols->name, ==, "libcountmysymbols.so.0");
+  libdbus = library_knowledge_lookup (&knowledge, "libdbus-1.so.3");
+  g_assert_nonnull (libdbus);
+  g_assert_cmpstr (libdbus->name, ==, "libdbus-1.so.3");
+  glib = library_knowledge_lookup (&knowledge, "libglib-2.0.so.0");
+  g_assert_nonnull (glib);
+  g_assert_cmpstr (libdbus->name, ==, "libdbus-1.so.3");
+  nope = library_knowledge_lookup (&knowledge, "libdbus-glib-1.so.2");
+  g_assert_null (nope);
+
+  g_assert_null (glib->comparators);
+  g_assert_nonnull (name->comparators);
+  g_assert_nonnull (name->comparators[0]);
+  g_assert_null (name->comparators[1]);
+  g_assert_nonnull (symbols->comparators);
+  g_assert_nonnull (symbols->comparators[0]);
+  g_assert_null (symbols->comparators[1]);
+  g_assert_nonnull (libgcc->comparators);
+  g_assert_nonnull (libgcc->comparators[0]);
+  g_assert_null (libgcc->comparators[1]);
+
+  g_assert_nonnull (libc->comparators);
+  g_assert_true (libc->comparators[0] == libgcc->comparators[0]);
+  g_assert_true (libc->comparators[1] == symbols->comparators[0]);
+  g_assert_true (libc->comparators[2] == name->comparators[0]);
+  g_assert_null (libc->comparators[3]);
+
+  g_assert_nonnull (libdbus->comparators);
+  g_assert_true (libdbus->comparators[0] == libgcc->comparators[0]);
+  g_assert_true (libdbus->comparators[1] == name->comparators[0]);
+  g_assert_true (libdbus->comparators[2] == symbols->comparators[0]);
+  g_assert_null (libdbus->comparators[3]);
+
+  library_knowledge_clear (&knowledge);
+}
+
+static const char * const library_knowledge_bad_strings[] =
+{
+  "nope",
+  "[nope",
+  "foo=bar",
+  "[nope]\n[no",
+};
+
+static void
+test_library_knowledge_bad (Fixture *f,
+                            gconstpointer data)
+{
+  gsize i;
+  library_knowledge knowledge = LIBRARY_KNOWLEDGE_INIT;
+
+  for (i = 0; i < G_N_ELEMENTS (library_knowledge_bad_strings); i++)
+    {
+      const char *s = library_knowledge_bad_strings[i];
+      int code = -1;
+      char *message = NULL;
+      FILE *fh;
+      gchar *description;
+      bool ok;
+
+      description = g_strescape (s, NULL);
+      assert_with_errno ((fh = fmemopen ((char *) s, strlen (s), "r")) != NULL);
+
+      ok = library_knowledge_load_from_stream (&knowledge, fh, description,
+                                               &code, &message);
+      g_assert_cmpstr (message, !=, NULL);
+      g_assert_cmpuint (code, !=, -1);
+      g_assert_false (ok);
+      g_test_message ("code %d: %s", code, message);
+      free (message);
+      assert_with_errno (fclose (fh) == 0);
+      library_knowledge_clear (&knowledge);
+
+      assert_with_errno ((fh = fmemopen ((char *) s, strlen (s), "r")) != NULL);
+      ok = library_knowledge_load_from_stream (&knowledge, fh, description,
+                                               NULL, NULL);
+      g_assert_false (ok);
+      assert_with_errno (fclose (fh) == 0);
+      library_knowledge_clear (&knowledge);
+
+      g_free (description);
+    }
+}
+
+static void
+test_ptr_list (Fixture *f,
+               gconstpointer data)
+{
+  size_t n;
+  ptr_list *list;
+  void **array;
+
+  list = ptr_list_alloc (0);
+  ptr_list_push_ptr (list, (char *) "hello");
+  ptr_list_add_ptr (list, (char *) "world", g_str_equal);
+  ptr_list_add_ptr (list, (char *) "hello", g_str_equal);    // duplicate, not added
+  ptr_list_add_ptr (list, (char *) "world", g_str_equal);    // duplicate, not added
+  ptr_list_push_ptr (list, (char *) "hello");
+  ptr_list_push_ptr (list, NULL);
+  ptr_list_push_addr (list, 23);
+
+  g_assert_cmpstr (ptr_list_nth_ptr (list, 0), ==, "hello");
+  g_assert_cmpstr (ptr_list_nth_ptr (list, 1), ==, "world");
+  g_assert_cmpstr (ptr_list_nth_ptr (list, 2), ==, "hello");
+  g_assert_cmpstr (ptr_list_nth_ptr (list, 3), ==, NULL);
+  g_assert_cmpuint (GPOINTER_TO_SIZE (ptr_list_nth_ptr (list, 4)), ==, 23);
+  g_assert_cmpstr (ptr_list_nth_ptr (list, 5), ==, NULL);
+  g_assert_cmpstr (ptr_list_nth_ptr (list, 47), ==, NULL);
+  g_assert_true (ptr_list_contains (list, 23));
+  g_assert_true (ptr_list_contains (list, (ElfW(Addr)) ptr_list_nth_ptr (list, 1)));
+  g_assert_false (ptr_list_contains (list, 1));
+
+  array = ptr_list_free_to_array (list, &n);
+  g_assert_cmpint (n, ==, 5);
+  g_assert_cmpstr (array[0], ==, "hello");
+  g_assert_cmpstr (array[1], ==, "world");
+  g_assert_cmpstr (array[2], ==, "hello");
+  g_assert_cmpstr (array[3], ==, NULL);
+  g_assert_cmpuint (GPOINTER_TO_SIZE (array[4]), ==, 23);
+  g_assert_cmpstr (array[5], ==, NULL);
+  free (array);
+
+  list = ptr_list_alloc (0);
+  ptr_list_free (list);
+}
+
+static void
+teardown (Fixture *f,
+          gconstpointer data)
+{
+  g_free (f->srcdir);
+  g_free (f->builddir);
+}
+
+int
+main (int argc,
+      char **argv)
+{
+  argv0 = argv[0];
+  g_test_init (&argc, &argv, NULL);
+  g_test_set_nonfatal_assertions ();
+
+  set_debug_flags (g_getenv("CAPSULE_DEBUG"));
+
+  g_test_add ("/build-filename", Fixture, NULL, setup,
+              test_build_filename, teardown);
+#ifdef ENABLE_SHARED
+  g_test_add ("/library-cmp/configurable", Fixture, NULL,
+              setup, test_library_cmp, teardown);
+#endif
+  g_test_add ("/library-cmp/name", Fixture, NULL,
+              setup, test_library_cmp_by_name, teardown);
+  g_test_add ("/library-knowledge/bad", Fixture, NULL,
+              setup, test_library_knowledge_bad, teardown);
+  g_test_add ("/library-knowledge/good", Fixture, NULL,
+              setup, test_library_knowledge_good, teardown);
+  g_test_add ("/ptr-list", Fixture, NULL, setup, test_ptr_list, teardown);
+
+  return g_test_run ();
+}
diff --git a/subprojects/libcapsule/tests/version.pl b/subprojects/libcapsule/tests/version.pl
new file mode 100755
index 0000000000000000000000000000000000000000..100be93828ff86d51b8710f86567753e2d8789fb
--- /dev/null
+++ b/subprojects/libcapsule/tests/version.pl
@@ -0,0 +1,118 @@
+#!/usr/bin/perl
+
+# Copyright © 2017 Collabora Ltd
+#
+# This file is part of libcapsule.
+#
+# libcapsule is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 2.1 of the
+# License, or (at your option) any later version.
+#
+# libcapsule 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 Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+use autodie;
+use warnings;
+use strict;
+
+use File::Temp qw();
+use IPC::Run qw(run);
+use Test::More;
+
+use FindBin;
+use lib $FindBin::Bin;
+
+use CapsuleTest;
+
+my $LIBDIR = qr{/(?:usr/(?:\w+-(?:\w+-)?linux-gnu\w*/)?)?lib(?:32|64|x32)?(?:/\w+-linux-gnu\w*)?(?:/i686)?(?:/cmov)?};
+
+skip_all_unless_bwrap;
+
+my $test_tempdir = File::Temp->newdir();
+diag "Working directory: $test_tempdir";
+my $host = "${test_tempdir}/host";
+mkdir($host);
+
+my $output;
+my ($root, $soname, $version, $path);
+run_ok([qw(bwrap --ro-bind / / --ro-bind /), $host,
+        qw(--dev-bind /dev /dev), $CAPSULE_VERSION_TOOL, 'libz.so.1',
+        $host], '>', \$output);
+diag("→ $output");
+like($output, qr{^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$});
+$output =~ m{^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$};
+$root = $1;
+$soname = $2;
+$version = $3;
+$path = $4;
+is($root, $host);
+is($soname, 'libz.so.1');
+like($version, qr{^1\.[0-9]+\.[0-9]+$});
+like($path, qr{^\Q$host\E($LIBDIR/libz\.so\.\Q$version\E)$});
+$path =~ s/^\Q$host\E//;
+ok(-e $path);
+
+# Try the same thing without a prefix
+run_ok([$CAPSULE_VERSION_TOOL, 'libz.so.1', '/'], '>', \$output);
+diag("→ $output");
+like($output, qr{^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$});
+$output =~ m{^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$};
+$root = $1;
+$soname = $2;
+$version = $3;
+$path = $4;
+is($root, '/');
+is($soname, 'libz.so.1');
+TODO: {
+    local $TODO = 'returns 1 for some reason';
+    like($version, qr{^1\.[0-9]+\.[0-9]+$});
+}
+like($path, qr{^($LIBDIR/libz\.so\.\Q$version\E)$});
+ok(-e $1);
+
+# A different way
+run_ok([$CAPSULE_VERSION_TOOL, 'libz.so.1', ''], '>', \$output);
+diag("→ $output");
+like($output, qr{^(\S*)\s+(\S+)\s+(\S+)\s+(\S+)$});
+$output =~ m{^(\S*)\s+(\S+)\s+(\S+)\s+(\S+)$};
+$root = $1;
+$soname = $2;
+$version = $3;
+$path = $4;
+like($root, qr{/?});
+is($soname, 'libz.so.1');
+TODO: {
+    local $TODO = 'returns 1 for some reason';
+    like($version, qr{^1\.[0-9]+\.[0-9]+$});
+}
+like($path, qr{^($LIBDIR/libz\.so\.\Q$version\E)$});
+ok(-e $path);
+
+# Another different way
+run_ok([$CAPSULE_VERSION_TOOL, 'libz.so.1'], '>', \$output);
+diag("→ $output");
+like($output, qr{^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$});
+$output =~ m{^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)$};
+$root = $1;
+$soname = $2;
+$version = $3;
+$path = $4;
+# TODO: This should probably not try to print NULL
+unlike($root, qr{^/.+$});   # actually it's (null)
+is($soname, 'libz.so.1');
+TODO: {
+    local $TODO = 'returns 1 for some reason';
+    like($version, qr{^1\.[0-9]+\.[0-9]+$});
+}
+like($path, qr{^($LIBDIR/libz\.so\.\Q$3\E)$});
+ok(-e $path);
+
+done_testing;
+
+# vim:set sw=4 sts=4 et:
diff --git a/subprojects/libcapsule/tests/version1/libunversionedabibreak.c b/subprojects/libcapsule/tests/version1/libunversionedabibreak.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libunversionedabibreak.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libunversionednumber.c b/subprojects/libcapsule/tests/version1/libunversionednumber.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libunversionednumber.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libunversionedsymbols.c b/subprojects/libcapsule/tests/version1/libunversionedsymbols.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libunversionedsymbols.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libversionedabibreak.c b/subprojects/libcapsule/tests/version1/libversionedabibreak.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedabibreak.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libversionedabibreak.map b/subprojects/libcapsule/tests/version1/libversionedabibreak.map
new file mode 100644
index 0000000000000000000000000000000000000000..72eb3014514adb75ee9bab73e98f34ddbb231c5d
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedabibreak.map
@@ -0,0 +1,4 @@
+LIBVERSIONED1 {
+  global:
+    *;
+};
diff --git a/subprojects/libcapsule/tests/version1/libversionedlikedbus.c b/subprojects/libcapsule/tests/version1/libversionedlikedbus.c
new file mode 100644
index 0000000000000000000000000000000000000000..96302ccbf2ec8e2cadb6311b019808ab6b86ae31
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedlikedbus.c
@@ -0,0 +1,6 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
+__attribute__((__visibility__("default"))) int _private1(int);
+int _private1(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libversionedlikedbus.map b/subprojects/libcapsule/tests/version1/libversionedlikedbus.map
new file mode 100644
index 0000000000000000000000000000000000000000..f96c71c41988f3d8dfcc71dd3c4b85a1a36a4daf
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedlikedbus.map
@@ -0,0 +1,8 @@
+LIBVERSIONED1 {
+  global:
+    symbol*;
+};
+LIBVERSIONEDPRIVATE_1_0_0 {
+  global:
+    _private*;
+};
diff --git a/subprojects/libcapsule/tests/version1/libversionedlikeglibc.c b/subprojects/libcapsule/tests/version1/libversionedlikeglibc.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedlikeglibc.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libversionedlikeglibc.map b/subprojects/libcapsule/tests/version1/libversionedlikeglibc.map
new file mode 100644
index 0000000000000000000000000000000000000000..d38767289b0510130a989280532068d35ec4f187
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedlikeglibc.map
@@ -0,0 +1,4 @@
+LIBVERSIONED1 {
+  global:
+    symbol*;
+};
diff --git a/subprojects/libcapsule/tests/version1/libversionednumber.c b/subprojects/libcapsule/tests/version1/libversionednumber.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionednumber.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libversionedprivatenothidden.c b/subprojects/libcapsule/tests/version1/libversionedprivatenothidden.c
new file mode 100644
index 0000000000000000000000000000000000000000..96302ccbf2ec8e2cadb6311b019808ab6b86ae31
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedprivatenothidden.c
@@ -0,0 +1,6 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
+__attribute__((__visibility__("default"))) int _private1(int);
+int _private1(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libversionedprivatenothidden.map b/subprojects/libcapsule/tests/version1/libversionedprivatenothidden.map
new file mode 100644
index 0000000000000000000000000000000000000000..86acd233702104a6992a6c3994182eeb10b72745
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedprivatenothidden.map
@@ -0,0 +1,8 @@
+LIBVERSIONED1 {
+  global:
+    symbol*;
+};
+LIBVERSIONEDPRIVATE_1_4_0 {
+  global:
+    _private1;
+};
diff --git a/subprojects/libcapsule/tests/version1/libversionedsymbols.c b/subprojects/libcapsule/tests/version1/libversionedsymbols.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedsymbols.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libversionedupgrade.c b/subprojects/libcapsule/tests/version1/libversionedupgrade.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedupgrade.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version1/libversionedupgrade.map b/subprojects/libcapsule/tests/version1/libversionedupgrade.map
new file mode 100644
index 0000000000000000000000000000000000000000..72eb3014514adb75ee9bab73e98f34ddbb231c5d
--- /dev/null
+++ b/subprojects/libcapsule/tests/version1/libversionedupgrade.map
@@ -0,0 +1,4 @@
+LIBVERSIONED1 {
+  global:
+    *;
+};
diff --git a/subprojects/libcapsule/tests/version2/libunversionedabibreak.c b/subprojects/libcapsule/tests/version2/libunversionedabibreak.c
new file mode 100644
index 0000000000000000000000000000000000000000..72d6338c811c80669c64f87c6fdea0ccd004c6f6
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libunversionedabibreak.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol3(int);
+int symbol3(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libunversionednumber.c b/subprojects/libcapsule/tests/version2/libunversionednumber.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libunversionednumber.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libunversionedsymbols.c b/subprojects/libcapsule/tests/version2/libunversionedsymbols.c
new file mode 100644
index 0000000000000000000000000000000000000000..903b505e9097c255b121368dd85e20f7c28de760
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libunversionedsymbols.c
@@ -0,0 +1,6 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol3(int);
+int symbol3(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libversionedabibreak.c b/subprojects/libcapsule/tests/version2/libversionedabibreak.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedabibreak.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libversionedabibreak.map b/subprojects/libcapsule/tests/version2/libversionedabibreak.map
new file mode 100644
index 0000000000000000000000000000000000000000..5d4b411c3c4c176681befc453cae1e6547cd4963
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedabibreak.map
@@ -0,0 +1,4 @@
+LIBVERSIONED2 {
+  global:
+    *;
+};
diff --git a/subprojects/libcapsule/tests/version2/libversionedlikedbus.c b/subprojects/libcapsule/tests/version2/libversionedlikedbus.c
new file mode 100644
index 0000000000000000000000000000000000000000..c47e5ee3a5e21cc9740f7f82c2dca2fa6782c374
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedlikedbus.c
@@ -0,0 +1,8 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol3(int);
+int symbol3(int x) { return x; }
+__attribute__((__visibility__("default"))) int _private1(int);
+int _private1(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libversionedlikedbus.map b/subprojects/libcapsule/tests/version2/libversionedlikedbus.map
new file mode 100644
index 0000000000000000000000000000000000000000..c8fd609ca7dda89f2eaf4d0ba6ea7e9d58d1047b
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedlikedbus.map
@@ -0,0 +1,8 @@
+LIBVERSIONED1 {
+  global:
+    symbol*;
+};
+LIBVERSIONEDPRIVATE_1_2_0 {
+  global:
+    _private*;
+};
diff --git a/subprojects/libcapsule/tests/version2/libversionedlikeglibc.c b/subprojects/libcapsule/tests/version2/libversionedlikeglibc.c
new file mode 100644
index 0000000000000000000000000000000000000000..24c1e42a69935d2c7de867ed6163b7402ce7b1a5
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedlikeglibc.c
@@ -0,0 +1,8 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__asm__(".symver _original_symbol2,symbol2@LIBVERSIONED1");
+__attribute__((__visibility__("default"))) int _original_symbol2(int);
+int _original_symbol2(int x) { return x; }
+__asm__(".symver _new_symbol2,symbol2@@LIBVERSIONED2");
+__attribute__((__visibility__("default"))) int _new_symbol2(int);
+int _new_symbol2(int x) { return x + 1; }
diff --git a/subprojects/libcapsule/tests/version2/libversionedlikeglibc.map b/subprojects/libcapsule/tests/version2/libversionedlikeglibc.map
new file mode 100644
index 0000000000000000000000000000000000000000..a80ed396df8211c191680bc8c170abb48c84b75e
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedlikeglibc.map
@@ -0,0 +1,6 @@
+LIBVERSIONED1 {
+  global:
+    symbol*;
+};
+LIBVERSIONED2 {
+} LIBVERSIONED1;
diff --git a/subprojects/libcapsule/tests/version2/libversionednumber.c b/subprojects/libcapsule/tests/version2/libversionednumber.c
new file mode 100644
index 0000000000000000000000000000000000000000..6ab82e7e9129e60447a3678f618fb7ab40ea0931
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionednumber.c
@@ -0,0 +1,4 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libversionedprivatenothidden.c b/subprojects/libcapsule/tests/version2/libversionedprivatenothidden.c
new file mode 100644
index 0000000000000000000000000000000000000000..ee67245af166757da0a139208a208a6735ca0940
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedprivatenothidden.c
@@ -0,0 +1,8 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
+__attribute__((__visibility__("default"))) int _private1(int);
+int _private1(int x) { return x; }
+__attribute__((__visibility__("default"))) int _private2(int);
+int _private2(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libversionedprivatenothidden.map b/subprojects/libcapsule/tests/version2/libversionedprivatenothidden.map
new file mode 100644
index 0000000000000000000000000000000000000000..4ef4780458e0a5bfc37251b9ac5133f412ee8564
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedprivatenothidden.map
@@ -0,0 +1,12 @@
+LIBVERSIONED1 {
+  global:
+    symbol*;
+};
+LIBVERSIONEDPRIVATE_1_4_0 {
+  global:
+    _private1;
+};
+LIBVERSIONEDPRIVATE_2_1_0 {
+  global:
+    _private2;
+};
diff --git a/subprojects/libcapsule/tests/version2/libversionedsymbols.c b/subprojects/libcapsule/tests/version2/libversionedsymbols.c
new file mode 100644
index 0000000000000000000000000000000000000000..903b505e9097c255b121368dd85e20f7c28de760
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedsymbols.c
@@ -0,0 +1,6 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol3(int);
+int symbol3(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libversionedupgrade.c b/subprojects/libcapsule/tests/version2/libversionedupgrade.c
new file mode 100644
index 0000000000000000000000000000000000000000..903b505e9097c255b121368dd85e20f7c28de760
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedupgrade.c
@@ -0,0 +1,6 @@
+__attribute__((__visibility__("default"))) int symbol1(int);
+int symbol1(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol2(int);
+int symbol2(int x) { return x; }
+__attribute__((__visibility__("default"))) int symbol3(int);
+int symbol3(int x) { return x; }
diff --git a/subprojects/libcapsule/tests/version2/libversionedupgrade.map b/subprojects/libcapsule/tests/version2/libversionedupgrade.map
new file mode 100644
index 0000000000000000000000000000000000000000..787c3302548ea0f6cd340a35e2f6de79cfed529f
--- /dev/null
+++ b/subprojects/libcapsule/tests/version2/libversionedupgrade.map
@@ -0,0 +1,9 @@
+LIBVERSIONED1 {
+  global:
+    symbol1;
+    symbol2;
+};
+LIBVERSIONED2 {
+  global:
+    symbol3;
+} LIBVERSIONED1;
diff --git a/subprojects/libcapsule/utils/capture-libs.c b/subprojects/libcapsule/utils/capture-libs.c
new file mode 100644
index 0000000000000000000000000000000000000000..2de227364017d868024d7b69627d98e03f603bfd
--- /dev/null
+++ b/subprojects/libcapsule/utils/capture-libs.c
@@ -0,0 +1,1371 @@
+// Copyright © 2017 Collabora Ltd
+//
+// This file is part of libcapsule.
+//
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+//
+// libcapsule 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 Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <getopt.h>
+#include <glob.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "debug.h"
+#include "ld-libs.h"
+#include "library-cmp.h"
+#include "tools.h"
+#include "utils.h"
+
+// We only really care about x86 here because that's the only thing
+// libcapsule supports, but we might as well be a bit more complete.
+// See https://sourceware.org/glibc/wiki/ABIList
+#if defined(__x86_64__) && defined(__ILP32__)
+# define LD_SO "/libx32/ld-linux-x32.so.2"
+#elif defined(__x86_64__)
+# define LD_SO "/lib64/ld-linux-x86-64.so.2"
+#elif defined(__sparc__) && defined(__arch64__)
+# define LD_SO "/lib64/ld-linux.so.2"
+#elif defined(__i386__) || defined(__alpha__) || defined(__sh__) || \
+    defined(__sparc__)
+# define LD_SO "/lib/ld-linux.so.2"
+#elif defined(__aarch64__) && __BYTE_ORDER == __BIG_ENDIAN
+# define LD_SO "/lib/ld-linux-aarch64_be.so.1"
+#elif defined(__aarch64__)
+# define LD_SO "/lib/ld-linux-aarch64.so.1"
+#elif defined(__arm__) && defined(__ARM_EABI__) && defined(_ARM_PCS_VFP)
+# define LD_SO "/lib/ld-linux-armhf.so.3"
+#elif defined(__arm__) && defined(__ARM_EABI__)
+# define LD_SO "/lib/ld-linux.so.3"
+#elif defined(__hppa__) || defined(__m68k__) || defined(__powerpc__) || \
+      defined(__s390__)
+// microblaze is also /lib/ld.so.1
+// mips classic NaN, o32 is also /lib/ld.so.1
+# define LD_SO "/lib/ld.so.1"
+#elif defined(__powerpc64__) && __BYTE_ORDER == __LITTLE_ENDIAN
+# define LD_SO "/lib/ld64.so.2"
+#elif defined(__s390x__) || defined(__powerpc64__)
+# define LD_SO "/lib/ld64.so.1"
+// Others not supported here because we don't know which predefined macros
+// can be used to detect them:
+// C-SKY hardfloat: /lib/ld-linux-cskyv2-hf.so.1
+// C-SKY softfloat: /lib/ld-linux-cskyv2.so.1
+// ia64: /lib/ld-linux-ia64.so.2
+// mips classic NaN n32: /lib32/ld.so.1
+// mips classic NaN n64: /lib64/ld.so.1
+// mips NaN2008: as for classic NaN but basename is ld-linux-mipsn8.so.1
+// nios2: /lib/ld-linux-nios2.so.1
+// riscv64 softfloat: /lib/ld-linux-riscv64-lp64.so.1
+// riscv64 hardfloat: /lib/ld-linux-riscv64-lp64d.so.1
+#else
+# error Unsupported architecture: we do not know where ld.so is
+#endif
+
+static const char * const libc_patterns[] = {
+    "soname:libBrokenLocale.so.1",
+    "soname:libanl.so.1",
+    "soname:libc.so.6",
+    "soname:libcidn.so.1",
+    "soname:libcrypt.so.1",
+    "soname:libdl.so.2",
+    "soname:libm.so.6",
+    "soname:libmemusage.so",
+    "soname:libmvec.so.1",
+    "soname:libnsl.so.1",
+    "soname:libpcprofile.so",
+    "soname:libpthread.so.0",
+    "soname:libresolv.so.2",
+    "soname:librt.so.1",
+    "soname:libthread_db.so.1",
+    "soname:libutil.so.1",
+    NULL
+};
+
+enum
+{
+  OPTION_COMPARE_BY,
+  OPTION_CONTAINER,
+  OPTION_DEST,
+  OPTION_LIBRARY_KNOWLEDGE,
+  OPTION_LINK_TARGET,
+  OPTION_NO_GLIBC,
+  OPTION_PRINT_LD_SO,
+  OPTION_PROVIDER,
+  OPTION_REMAP_LINK_PREFIX,
+  OPTION_RESOLVE_LD_SO,
+  OPTION_VERSION,
+};
+
+typedef struct {
+    char *from;
+    const char *to;
+} remap_tuple;
+
+static const char * const *arg_patterns = NULL;
+static const char *option_container = "/";
+static const char *option_dest = ".";
+static const char *option_provider = "/";
+static const char *option_link_target = NULL;
+static remap_tuple *remap_prefix = NULL;
+static bool option_glibc = true;
+
+static struct option long_options[] =
+{
+    { "compare-by", required_argument, NULL, OPTION_COMPARE_BY },
+    { "container", required_argument, NULL, OPTION_CONTAINER },
+    { "dest", required_argument, NULL, OPTION_DEST },
+    { "help", no_argument, NULL, 'h' },
+    { "library-knowledge", required_argument, NULL, OPTION_LIBRARY_KNOWLEDGE },
+    { "link-target", required_argument, NULL, OPTION_LINK_TARGET },
+    { "no-glibc", no_argument, NULL, OPTION_NO_GLIBC },
+    { "print-ld.so", no_argument, NULL, OPTION_PRINT_LD_SO },
+    { "provider", required_argument, NULL, OPTION_PROVIDER },
+    { "remap-link-prefix", required_argument, NULL, OPTION_REMAP_LINK_PREFIX },
+    { "resolve-ld.so", required_argument, NULL, OPTION_RESOLVE_LD_SO },
+    { "version", no_argument, NULL, OPTION_VERSION },
+    { NULL }
+};
+
+static int dest_fd = -1;
+
+static bool resolve_ld_so ( const char *prefix,
+                            char path[PATH_MAX],
+                            const char **within_prefix,
+                            int *code,
+                            char **message )
+{
+    size_t prefix_len = strlen( prefix );
+
+    if( build_filename( path, PATH_MAX, prefix, LD_SO,
+                        NULL ) >= PATH_MAX )
+    {
+        _capsule_set_error( code, message, E2BIG,
+                            "prefix \"%s\" is too long",
+                            prefix );
+        return false;
+    }
+
+    DEBUG( DEBUG_TOOL, "Starting with %s", path );
+
+    while( resolve_link( prefix, path ) )
+    {
+        DEBUG( DEBUG_TOOL, "-> %s", path );
+    }
+
+    if( strcmp( prefix, "/" ) == 0 )
+    {
+        prefix_len = 0;
+    }
+
+    if( ( prefix_len > 0 &&
+          strncmp( path, prefix, prefix_len ) != 0 ) ||
+        path[prefix_len] != '/' )
+    {
+        _capsule_set_error( code, message, EXDEV,
+                            "\"%s\" is not within prefix \"%s\"",
+                            path, prefix );
+        return false;
+    }
+
+    if( within_prefix != NULL)
+        *within_prefix = path + prefix_len;
+
+    return true;
+}
+
+static void usage (int code) __attribute__((noreturn));
+static void usage (int code)
+{
+  FILE *fh;
+
+  if( code == 0 )
+  {
+      fh = stdout;
+  }
+  else
+  {
+      fh = stderr;
+      // Assume we already printed a warning; make it stand out more
+      fprintf( fh, "\n" );
+  }
+
+  fprintf( fh, "Usage:\n"
+               "%s [OPTIONS] PATTERN...\n",
+           program_invocation_short_name );
+  fprintf( fh, "\tCreate symbolic links in LIBDIR that will make the\n"
+               "\tPATTERNs from PROVIDER available, assuming LIBDIR\n"
+               "\twill be added to the container's LD_LIBRARY_PATH.\n" );
+  fprintf( fh, "\n" );
+  fprintf( fh, "%s --print-ld.so\n",
+           program_invocation_short_name );
+  fprintf( fh, "\tPrint the ld.so filename for this architecture and exit.\n" );
+  fprintf( fh, "%s --resolve-ld.so=TREE\n",
+           program_invocation_short_name );
+  fprintf( fh, "\tPrint the absolute path of the file that implements ld.so\n"
+               "\tin TREE.\n" );
+  fprintf( fh, "\n" );
+  fprintf( fh, "%s --help\n",
+           program_invocation_short_name );
+  fprintf( fh, "\tShow this help.\n" );
+  fprintf( fh, "\n" );
+  fprintf( fh, "Options:\n" );
+  fprintf( fh, "--compare-by=METHOD[,METHOD2...]\n"
+               "\tUse METHOD by default to decide which library is newer.\n"
+               "\tIf unable to decide, use METHOD2, and so on.\n"
+               "\tIf unable to decide by any method, choose PROVIDER.\n"
+               "\tThe default is 'name,provider'.\n"
+               "\t\tname: Use library name: libfoo.so.1.0 < libfoo.so.1.2\n"
+               "\t\tversions: The one with a superset of DT_VERDEF is newer\n"
+               "\t\tsymbols: The one with a superset of symbols is newer\n"
+               "\t\tcontainer: The one in CONTAINER is newer\n"
+               "\t\tprovider: The one in PROVIDER is newer\n" );
+  fprintf( fh, "--container=CONTAINER\n"
+               "\tAssume the container will look like CONTAINER when\n"
+               "\tdeciding which libraries are needed [default: /]\n" );
+  fprintf( fh, "--dest=LIBDIR\n"
+               "\tCreate symlinks in LIBDIR [default: .]\n" );
+  fprintf( fh, "--library-knowledge=FILE\n"
+               "\tLoad information about known libraries from a"
+               "\t.desktop-style file at FILE, overriding --compare-by.\n" );
+  fprintf( fh, "--link-target=PATH\n"
+               "\tAssume PROVIDER will be mounted at PATH when the\n"
+               "\tcontainer is used [default: PROVIDER]\n" );
+  fprintf( fh, "--provider=PROVIDER\n"
+               "\tFind libraries in PROVIDER [default: /]\n" );
+  fprintf( fh, "--remap-link-prefix=FROM=TO\n"
+               "\tWhile in the process of creating symlinks, if their prefix\n"
+               "\twas supposed to be FROM, they will instead be changed with\n"
+               "\tTO\n" );
+  fprintf( fh, "--no-glibc\n"
+               "\tDon't capture libraries that are part of glibc\n" );
+  fprintf( fh, "\n" );
+  fprintf( fh, "Each PATTERN is one of:\n" );
+  fprintf( fh, "\n" );
+  fprintf( fh, "from:FILE\n"
+               "\tRead PATTERNs from FILE, one per line.\n" );
+  fprintf( fh, "soname:SONAME\n"
+               "\tCapture the library in ld.so.cache whose name is\n"
+               "\texactly SONAME\n" );
+  fprintf( fh, "soname-match:GLOB\n"
+               "\tCapture every library in ld.so.cache that matches\n"
+               "\ta shell-style glob (which will usually need to be\n"
+               "\tquoted when using a shell)\n" );
+  fprintf( fh, "only-dependencies:PATTERN\n"
+               "\tCapture the dependencies of each library matched by\n"
+               "\tPATTERN, but not the library matched by PATTERN itself\n"
+               "\t(unless a match for PATTERN depends on another match)\n" );
+  fprintf( fh, "no-dependencies:PATTERN\n"
+               "\tCapture each library matched by PATTERN, but not\n"
+               "\ttheir dependencies\n" );
+  fprintf( fh, "if-exists:PATTERN\n"
+               "\tCapture PATTERN, but don't fail if nothing matches\n" );
+  fprintf( fh, "if-same-abi:PATTERN\n"
+               "\tCapture PATTERN, but don't fail if it points to a\n"
+               "\tlibrary with mismatched word size or architecture\n" );
+  fprintf( fh, "even-if-older:PATTERN\n"
+               "\tCapture PATTERN, even if the version in CONTAINER\n"
+               "\tappears newer\n" );
+  fprintf( fh, "gl:\n"
+               "\tShortcut for even-if-older:if-exists:soname:libGL.so.1,\n"
+               "\teven-if-older:if-exists:soname-match:libGLX_*.so.0, and\n"
+               "\tvarious other GL-related libraries\n" );
+  fprintf( fh, "path:ABS-PATH\n"
+               "\tResolve ABS-PATH as though chrooted into PROVIDER\n"
+               "\tand capture the result\n" );
+  fprintf( fh, "path-match:GLOB\n"
+               "\tResolve GLOB as though chrooted into PROVIDER\n"
+               "\tand capture any results that are of the right ABI\n" );
+  fprintf( fh, "an absolute path with no '?', '*', '['\n"
+               "\tSame as path:PATTERN\n" );
+  fprintf( fh, "a glob pattern starting with '/'\n"
+               "\tSame as path-match:PATTERN\n" );
+  fprintf( fh, "a glob pattern with no '/'\n"
+               "\tSame as soname-match:PATTERN\n" );
+  fprintf( fh, "a bare SONAME with no '/', '?', '*', '['\n"
+               "\tSame as soname:PATTERN\n" );
+  exit( code );
+}
+
+typedef enum
+{
+  CAPTURE_FLAG_NONE = 0,
+  CAPTURE_FLAG_EVEN_IF_OLDER = (1 << 0),
+  CAPTURE_FLAG_IF_EXISTS = (1 << 1),
+  CAPTURE_FLAG_LIBRARY_ITSELF = ( 1 << 2 ),
+  CAPTURE_FLAG_DEPENDENCIES = ( 1 << 3 ),
+  CAPTURE_FLAG_IF_SAME_ABI = ( 1 << 4 ),
+} capture_flags;
+
+typedef struct
+{
+    capture_flags flags;
+    library_cmp_function *comparators;
+    library_knowledge knowledge;
+} capture_options;
+
+static bool
+init_with_target( ld_libs *ldlibs, const char *tree, const char *target,
+                  int *code, char **message )
+{
+    if( !ld_libs_init( ldlibs, NULL, tree, debug_flags, code, message ) )
+    {
+        goto fail;
+    }
+
+    if( !ld_libs_load_cache( ldlibs, code, message ) )
+    {
+        goto fail;
+    }
+
+    if( !ld_libs_set_target( ldlibs, target, code, message ) )
+    {
+        goto fail;
+    }
+
+    return true;
+
+fail:
+    ld_libs_finish( ldlibs );
+    return false;
+}
+
+static bool capture_pattern( const char *pattern,
+                             const capture_options *options,
+                             int *code, char **message );
+
+static bool capture_patterns( const char * const *patterns,
+                              const capture_options *options,
+                              int *code, char **message );
+
+static bool
+capture_one( const char *soname, const capture_options *options,
+             int *code, char **message )
+{
+    unsigned int i;
+    unsigned int j;
+    _capsule_cleanup(ld_libs_finish) ld_libs provider = {};
+    int local_code = 0;
+    _capsule_autofree char *local_message = NULL;
+
+    if( !init_with_target( &provider, option_provider, soname,
+                           &local_code, &local_message ) )
+    {
+        if( ( options->flags & CAPTURE_FLAG_IF_EXISTS ) && local_code == ENOENT )
+        {
+            DEBUG( DEBUG_TOOL, "%s not found, ignoring", soname );
+            _capsule_clear( &local_message );
+            return true;
+        }
+
+        if( ( options->flags & CAPTURE_FLAG_IF_SAME_ABI ) && local_code == ENOEXEC )
+        {
+            DEBUG( DEBUG_TOOL, "%s is a different ABI: %s", soname, local_message );
+            _capsule_clear( &local_message );
+            return true;
+        }
+
+        _capsule_propagate_error( code, message, local_code,
+                                  _capsule_steal_pointer( &local_message ) );
+        return false;
+    }
+
+    if( !ld_libs_find_dependencies( &provider, &local_code, &local_message ) )
+    {
+        if( ( options->flags & CAPTURE_FLAG_IF_EXISTS ) && local_code == ENOENT )
+        {
+            DEBUG( DEBUG_TOOL,
+                   "Some of the dependencies for %s have not been found, ignoring",
+                   soname );
+            _capsule_clear( &local_message );
+            return true;
+        }
+
+        _capsule_propagate_error( code, message, local_code,
+                                  _capsule_steal_pointer( &local_message ) );
+        return false;
+    }
+
+    for( i = 0; i < N_ELEMENTS( provider.needed ); i++ )
+    {
+        _capsule_autofree char *target = NULL;
+        struct stat statbuf;
+        const char *needed_name = provider.needed[i].name;
+        const char *needed_path_in_provider = provider.needed[i].path;
+        const char *needed_basename;
+        bool remapped_prefix = false;
+
+        if( !needed_name )
+        {
+            continue;
+        }
+
+        if( i == 0 && !( options->flags & CAPTURE_FLAG_LIBRARY_ITSELF ) )
+        {
+            DEBUG( DEBUG_TOOL, "Not capturing \"%s\" itself as requested",
+                   needed_name );
+            continue;
+        }
+
+        if( i > 0 && !( options->flags & CAPTURE_FLAG_DEPENDENCIES ) )
+        {
+            DEBUG( DEBUG_TOOL,
+                   "Not capturing dependencies of \"%s\" as requested",
+                   soname );
+            break;
+        }
+
+        needed_basename = _capsule_basename( needed_name );
+
+        if( !option_glibc )
+        {
+            bool capture = true;
+
+            for( j = 0; j < N_ELEMENTS( libc_patterns ); j++ )
+            {
+                if( libc_patterns[j] == NULL )
+                    break;
+
+                assert( strstarts( libc_patterns[j], "soname:" ) );
+
+                if( strcmp( libc_patterns[j] + strlen( "soname:" ),
+                            needed_basename ) == 0 )
+                {
+                    DEBUG( DEBUG_TOOL,
+                           "Not capturing \"%s\" because it is part of glibc",
+                           needed_name );
+                    capture = false;
+                    break;
+                }
+            }
+
+            if( !capture )
+                continue;
+        }
+
+        if( fstatat( dest_fd, needed_basename, &statbuf,
+                     AT_SYMLINK_NOFOLLOW ) == 0 )
+        {
+            /* We already created a symlink for this library. No further
+             * action required (but keep going through its dependencies
+             * in case we need to symlink those into place) */
+            DEBUG( DEBUG_TOOL, "We already have a symlink for %s",
+                   needed_name );
+            continue;
+        }
+
+        /* For the library we were originally looking for, we don't
+         * compare with the container if we have EVEN_IF_OLDER flag.
+         * For its dependencies, we ignore that flag. */
+        if( option_container == NULL )
+        {
+            DEBUG( DEBUG_TOOL,
+                   "Container unknown, cannot compare version with "
+                   "\"%s\": assuming provider version is newer",
+                   needed_path_in_provider );
+        }
+        else if( i == 0 && ( options->flags & CAPTURE_FLAG_EVEN_IF_OLDER ) )
+        {
+            DEBUG( DEBUG_TOOL,
+                   "Explicitly requested %s from %s even if older: \"%s\"",
+                   needed_name, option_provider,
+                   needed_path_in_provider );
+        }
+        else
+        {
+            _capsule_cleanup(ld_libs_finish) ld_libs container = {};
+            if( init_with_target( &container, option_container,
+                                  needed_name,
+                                  &local_code, &local_message ) )
+            {
+                const char *needed_path_in_container = container.needed[0].path;
+                int decision;
+                library_details details = {};
+                const library_details *known = NULL;
+
+                known = library_knowledge_lookup( &options->knowledge,
+                                                  needed_name );
+
+                if( known != NULL )
+                {
+                    DEBUG( DEBUG_TOOL, "Found library-specific details for \"%s\"",
+                           needed_name );
+                    details = *known;
+                }
+
+                /* Not const-correct, but we don't actually modify or free it */
+                details.name = (char *) needed_name;
+
+                if( details.comparators == NULL )
+                    details.comparators = options->comparators;
+
+                decision = library_cmp_list_iterate( &details,
+                                                     needed_path_in_container,
+                                                     option_container,
+                                                     needed_path_in_provider,
+                                                     option_provider );
+
+                if( decision > 0 )
+                {
+                    /* Version in container is strictly newer: don't
+                     * symlink in the one from the provider */
+                    DEBUG( DEBUG_TOOL,
+                           "Choosing %s from container",
+                           needed_name );
+                    continue;
+                }
+                else if( decision < 0 )
+                {
+                    DEBUG( DEBUG_TOOL,
+                           "Choosing %s from provider",
+                           needed_name );
+                }
+                else
+                {
+                    /* If equal, we prefer the provider over the container
+                     * (this is equivalent to having "...,provider" at the
+                     * end of the comparison specification) */
+                    DEBUG( DEBUG_TOOL,
+                           "Falling back to choosing %s from provider",
+                           needed_name );
+                }
+            }
+            else if( local_code == ENOENT )
+            {
+                /* else assume it's absent from the container, which is
+                 * just like it being newer in the provider */
+                DEBUG( DEBUG_TOOL, "%s is not in the container",
+                       needed_name );
+                _capsule_clear( &local_message );
+            }
+            else
+            {
+                _capsule_propagate_error( code, message, local_code,
+                                          _capsule_steal_pointer( &local_message ) );
+                return false;
+            }
+        }
+
+        /* By this point we've decided we want the version from the
+         * provider, not the version from the container */
+
+        if( option_link_target != NULL || remap_prefix != NULL )
+        {
+            char path[PATH_MAX];
+            size_t prefix_len = strlen( option_provider );
+
+            // We need to take the realpath() inside the container,
+            // because if we're using LD_LIBRARY_PATH rather than
+            // libcapsule, we have to follow the chain of
+            // $libdir/libGL.so.1 -> /etc/alternatives/whatever -> ...
+            // within that prefix.
+
+            safe_strncpy( path, needed_path_in_provider, sizeof(path) );
+
+            DEBUG( DEBUG_TOOL, "Link target initially: \"%s\"", path );
+
+            while( resolve_link( option_provider, path ) )
+            {
+                DEBUG( DEBUG_TOOL, "Link target pursued to: \"%s\"", path );
+            }
+
+            if( strcmp( option_provider, "/" ) == 0 )
+            {
+                prefix_len = 0;
+            }
+
+            if( strncmp( path, option_provider, prefix_len ) != 0 ||
+                path[prefix_len] != '/' )
+            {
+                warnx( "warning: \"%s\" is not within prefix \"%s\"",
+                       path, option_provider );
+                continue;
+            }
+
+            target = build_filename_alloc( option_link_target ? option_link_target : "/",
+                                           path + prefix_len,
+                                           NULL );
+
+            for( j = 0; remap_prefix != NULL && remap_prefix[j].from != NULL; j++ )
+            {
+                if( strstarts( target, remap_prefix[j].from ) )
+                {
+                    char *tmp;
+                    assert( remap_prefix[j].to != NULL );
+                    DEBUG( DEBUG_TOOL, "Remapping \"%s\" to \"%s\" in \"%s\"",
+                           remap_prefix[j].from, remap_prefix[j].to, target );
+                    xasprintf( &tmp, "%s%s", remap_prefix[j].to,
+                               target + strlen( remap_prefix[j].from ) );
+                    free( target );
+                    target = tmp;
+                    remapped_prefix = true;
+                }
+            }
+        }
+
+        // If we don't have the link target option and we didn't remap the
+        // prefix, we just set the target to the needed path in provider
+        // without following the eventual link chain
+        if( !remapped_prefix && option_link_target == NULL )
+        {
+            if( target != NULL )
+                free( target );
+            target = xstrdup( needed_path_in_provider );
+        }
+
+        assert( target != NULL );
+
+        DEBUG( DEBUG_TOOL, "Creating symlink %s/%s -> %s",
+               option_dest, needed_basename, target );
+
+        if( symlinkat( target, dest_fd, needed_basename ) < 0 )
+        {
+            warn( "warning: cannot create symlink %s/%s",
+                  option_dest, needed_basename );
+        }
+
+        if( strcmp( needed_basename, "libc.so.6" ) == 0 )
+        {
+            capture_options new_options;
+
+            /* Having captured libc, we need to capture the rest of
+             * the related libraries from the same place */
+            DEBUG( DEBUG_TOOL,
+                   "Capturing the rest of glibc to go with %s",
+                   needed_name );
+
+            new_options = *options;
+            new_options.flags |= CAPTURE_FLAG_IF_EXISTS;
+            new_options.flags |= CAPTURE_FLAG_EVEN_IF_OLDER;
+
+            if( !capture_patterns( libc_patterns, &new_options,
+                                   code, message ) )
+            {
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+typedef struct
+{
+    const char *pattern;
+    const capture_options *options;
+    bool found;
+    ld_cache cache;
+    int *code;
+    char **message;
+} cache_foreach_context;
+
+static intptr_t
+cache_foreach_cb (const char *name, int flag, unsigned int osv,
+                  uint64_t hwcap, const char *path, void *data)
+{
+  cache_foreach_context *ctx = data;
+
+  if( !name || !*name )
+  {
+      warnx( "warning: empty name found in ld.so.cache" );
+      return 0;
+  }
+
+  // We don't really care about whether the library matches our class,
+  // machine, hwcaps etc. - if we can't dlopen a library of this name,
+  // we'll just skip it.
+  if( fnmatch( ctx->pattern, name, 0 ) == 0 )
+  {
+      capture_options new_options;
+
+      DEBUG( DEBUG_TOOL, "%s matches %s", name, ctx->pattern );
+
+      ctx->found = true;
+
+      new_options = *ctx->options;
+      new_options.flags |= CAPTURE_FLAG_IF_EXISTS;
+
+      if( !capture_one( name, &new_options, ctx->code, ctx->message ) )
+          return 1;
+  }
+
+  return 0;   // continue iteration
+}
+
+static bool
+capture_soname_match( const char *pattern, const capture_options *options,
+                      int *code, char **message )
+{
+    cache_foreach_context ctx = {
+        .pattern = pattern,
+        .options = options,
+        .cache = { .is_open = 0 },
+        .found = false,
+        .code = code,
+        .message = message,
+    };
+    bool ret = false;
+    size_t i;
+
+    DEBUG( DEBUG_TOOL, "%s", pattern );
+
+    for( i = 0; ld_cache_filenames[i] != NULL; i++ )
+    {
+        _capsule_autofree char *cache_path = NULL;
+
+        if( message != NULL )
+            _capsule_clear( message );
+
+        cache_path = build_filename_alloc( option_provider,
+                                           ld_cache_filenames[i],
+                                           NULL );
+
+        if( ld_cache_open( &ctx.cache, cache_path, code, message ) )
+            break;
+        else
+            assert( !ctx.cache.is_open );
+    }
+
+    if( !ctx.cache.is_open )
+        goto out;
+
+    if( ld_cache_foreach( &ctx.cache, cache_foreach_cb, &ctx ) != 0 )
+        goto out;
+
+    if( !ctx.found && !( options->flags & CAPTURE_FLAG_IF_EXISTS ) )
+    {
+        _capsule_set_error( code, message, ENOENT,
+                            "no matches found for glob pattern \"%s\" "
+                            "in ld.so.cache",
+                            pattern );
+        goto out;
+    }
+
+    ret = true;
+
+out:
+    if( ctx.cache.is_open )
+        ld_cache_close( &ctx.cache );
+
+    return ret;
+}
+
+static bool
+capture_path_match( const char *pattern, const capture_options *options,
+                    int *code, char **message )
+{
+    char *abs_path = NULL;
+    int res;
+    bool ret = false;
+    glob_t buffer;
+    size_t i;
+
+    DEBUG( DEBUG_TOOL, "%s", pattern );
+
+    abs_path = build_filename_alloc( option_provider, pattern, NULL );
+    res = glob( abs_path, 0, NULL, &buffer );
+
+    switch( res )
+    {
+        case 0:
+            ret = true;
+            for( i = 0; i < buffer.gl_pathc; i++)
+            {
+                const char *path = buffer.gl_pathv[i];
+                capture_options new_options;
+
+                if( option_provider != NULL &&
+                    strcmp( option_provider, "/" ) != 0 &&
+                    ( !strstarts( path, option_provider ) ||
+                      path[strlen( option_provider )] != '/' ) )
+                {
+                  _capsule_set_error( code, message, EXDEV,
+                                      "path pattern \"%s\" matches \"%s\""
+                                      "which is not in \"%s\"",
+                                      pattern, path, option_provider );
+                  globfree( &buffer );
+                  return false;
+                }
+
+                new_options = *options;
+                new_options.flags |= CAPTURE_FLAG_IF_SAME_ABI;
+
+                if( !capture_one( path + strlen( option_provider ),
+                                  &new_options, code, message ) )
+                {
+                    ret = false;
+                    break;
+                }
+            }
+            globfree( &buffer );
+            break;
+
+        case GLOB_NOMATCH:
+            if( options->flags & CAPTURE_FLAG_IF_EXISTS )
+            {
+                ret = true;
+            }
+            else
+            {
+                _capsule_set_error( code, message, ENOENT,
+                                    "no matches found for glob pattern \"%s\" "
+                                    "in \"%s\"",
+                                    pattern, option_provider );
+            }
+            break;
+
+        case GLOB_NOSPACE:
+            _capsule_set_error( code, message, ENOMEM,
+                                "unable to match glob pattern \"%s\" "
+                                "in \"%s\"",
+                                pattern, option_provider );
+            break;
+
+        default:
+        case GLOB_ABORTED:
+            _capsule_set_error( code, message, EIO,
+                                "unable to match glob pattern \"%s\" "
+                                "in \"%s\"",
+                                pattern, option_provider );
+            break;
+    }
+
+    free( abs_path );
+    return ret;
+}
+
+static bool
+capture_lines( const char *filename, const capture_options *options,
+               int *code, char **message )
+{
+  FILE *fp = NULL;
+  bool ret = true;
+  char *line = NULL;
+  size_t len = 0;
+  ssize_t chars;
+
+  if( strcmp( filename, "-" ) != 0 )
+  {
+      fp = fopen( filename, "re" );
+
+      if( fp == NULL )
+      {
+          _capsule_set_error( code, message, errno,
+                              "Unable to open \"%s\": %s",
+                              filename, strerror( errno ) );
+          return false;
+      }
+  }
+
+  while( ( chars = getline( &line, &len, fp ? fp : stdin ) ) > -1 )
+  {
+      /* Ignore blank lines and shell-style comments (which must
+       * currently be at the beginning of the line) */
+      if( chars == 0 || line[0] == '\0' || line[0] == '\n' || line[0] == '#' )
+          continue;
+
+      if( line[chars - 1] == '\n' )
+          line[chars - 1] = '\0';
+
+      if( !capture_pattern( line, options, code, message ) )
+      {
+          ret = false;
+          break;
+      }
+  }
+
+  free( line );
+
+  if( fp != NULL )
+      fclose( fp );
+
+  return ret;
+}
+
+static bool
+capture_pattern( const char *pattern, const capture_options *options,
+                 int *code, char **message )
+{
+    DEBUG( DEBUG_TOOL, "%s", pattern );
+
+    if ( ( options->flags & ( CAPTURE_FLAG_LIBRARY_ITSELF |
+                              CAPTURE_FLAG_DEPENDENCIES ) ) == 0 )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "combining no-dependencies: with "
+                            "only-dependencies: is meaningless, "
+                            "so \"%s\" is invalid",
+                            pattern );
+        return false;
+    }
+
+    if( strstarts( pattern, "path:" ) )
+    {
+        if( !strstarts( pattern, "path:/" ) )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "path: requires an absolute path as "
+                                "argument, not \"%s\"",
+                                pattern );
+            return false;
+        }
+
+        return capture_one( pattern + strlen( "path:" ),
+                            options, code, message );
+    }
+
+    if( strstarts( pattern, "soname:" ) )
+    {
+        return capture_one( pattern + strlen( "soname:" ),
+                            options, code, message );
+    }
+
+    if( strstarts( pattern, "soname-match:" ) )
+    {
+        return capture_soname_match( pattern + strlen( "soname-match:" ),
+                                     options, code, message );
+    }
+
+    if( strstarts( pattern, "path-match:" ) )
+    {
+        return capture_path_match( pattern + strlen( "path-match:" ),
+                                   options, code, message );
+    }
+
+    if( strstarts( pattern, "if-exists:" ) )
+    {
+        capture_options new_options = *options;
+
+        new_options.flags |= CAPTURE_FLAG_IF_EXISTS;
+        return capture_pattern( pattern + strlen( "if-exists:" ),
+                                &new_options, code, message );
+    }
+
+    if( strstarts( pattern, "if-same-abi:" ) )
+    {
+        capture_options new_options = *options;
+
+        new_options.flags |= CAPTURE_FLAG_IF_SAME_ABI;
+        return capture_pattern( pattern + strlen( "if-same-abi:" ),
+                                &new_options, code, message );
+    }
+
+    if( strstarts( pattern, "even-if-older:" ) )
+    {
+        capture_options new_options = *options;
+
+        new_options.flags |= CAPTURE_FLAG_EVEN_IF_OLDER;
+        return capture_pattern( pattern + strlen( "even-if-older:" ),
+                                &new_options, code, message );
+    }
+
+    if( strstarts( pattern, "only-dependencies:" ) )
+    {
+        capture_options new_options = *options;
+
+        new_options.flags &= ~CAPTURE_FLAG_LIBRARY_ITSELF;
+        return capture_pattern( pattern + strlen( "only-dependencies:" ),
+                                &new_options, code, message );
+    }
+
+    if( strstarts( pattern, "no-dependencies:" ) )
+    {
+        capture_options new_options = *options;
+
+        new_options.flags &= ~CAPTURE_FLAG_DEPENDENCIES;
+        return capture_pattern( pattern + strlen( "no-dependencies:" ),
+                                &new_options, code, message );
+    }
+
+    if( strcmp( pattern, "gl:" ) == 0 )
+    {
+        // Useful information:
+        // https://devtalk.nvidia.com/default/topic/915640/multiple-glx-client-libraries-in-the-nvidia-linux-driver-installer-package/
+        static const char * const gl_patterns[] = {
+            "soname:libEGL.so.1",
+            // Vendor ICDs for libEGL.so.1
+            // (Registered via JSON in /usr/share/glvnd/egl_vendor.d)
+            "soname-match:libEGL_*.so.*",
+
+            "soname:libGL.so.1",
+
+            "soname:libGLESv1_CM.so.1",
+            // Vendor ICDs for libGLESv1_CM.so.1
+            "soname-match:libGLESv1_CM_*.so.*",
+
+            "soname:libGLESv2.so.2",
+            // Vendor ICDs for libGLESv2.so.2
+            "soname:libGLESv2_*.so.*",
+
+            "soname:libGLX.so.0",
+            // Vendor ICDs for libGL.so.1 and/or libGLX.so.0
+            "soname-match:libGLX_*.so.*",
+            // This one looks redundant, but because it's usually a
+            // symlink to someone else's implementation, we can't find
+            // it in the ld.so cache under its own name: its SONAME is
+            // libGLX_mesa.so.0 or libGLX_nvidia.so.0. So we can't find
+            // it by wildcard-matching and have to look it up explicitly
+            // instead.
+            "soname:libGLX_indirect.so.0",
+
+            // This is an implementation detail of GLVND, but it had
+            // better match the GLVND dispatchers or bad things will
+            // happen
+            "soname-match:libGLdispatch.so.*",
+
+            "soname:libOpenGL.so.0",
+
+            // Mostly used by Mesa, but apps/games are also allowed to
+            // use it directly
+            "soname:libgbm.so.1",
+
+            // Mesa libraries should have DT_NEEDED for this, but some
+            // historical versions didn't, so it wouldn't be picked up
+            // by recursive dependency resolution
+            "soname:libglapi.so.0",
+
+            // Some libraries are not explicitly mentioned here:
+            // For NVIDIA, we also need libnvidia-glcore.so.$VERSION,
+            // but it will be pulled in by dependencies, so we don't
+            // need to list it explicitly.
+            // For NVIDIA, we also need libnvidia-tls.so.$VERSION,
+            // either the TLS or non-TLS version as appropriate; but
+            // again it will be pulled in via dependencies.
+            NULL
+        };
+        capture_options new_options = *options;
+
+        new_options.flags |= CAPTURE_FLAG_IF_EXISTS;
+        /* We usually want to capture the host GL stack even if it
+         * appears older than what's in the container. */
+        new_options.flags |= CAPTURE_FLAG_EVEN_IF_OLDER;
+
+        return capture_patterns( gl_patterns, &new_options, code, message );
+    }
+
+    if( strcmp( pattern, "nvidia:" ) == 0 )
+    {
+        static const char * const gl_patterns[] = {
+            "soname:libEGL.so.1",
+            "soname-match:libEGL_nvidia.so.*",
+
+            "soname:libGL.so.1",
+
+            "soname:libGLESv1_CM.so.1",
+            "soname-match:libGLESv1_CM_nvidia.so.*",
+
+            "soname:libGLESv2.so.2",
+            "soname-match:libGLESv2_nvidia.so.*",
+
+            "soname:libGLX.so.0",
+            "soname-match:libGLX_nvidia.so.*",
+            "soname:libGLX_indirect.so.0",
+
+            "soname-match:libGLdispatch.so.*",
+
+            "soname:libOpenGL.so.0",
+
+            "soname-match:libcuda.so.*",
+            "soname-match:libglx.so.*",
+            "soname-match:libnvcuvid.so.*",
+            "soname-match:libnvidia-*.so.*",
+            "soname-match:libOpenCL.so.*",
+            "soname-match:libvdpau_nvidia.so.*",
+            NULL
+        };
+        capture_options new_options = *options;
+
+        new_options.flags |= CAPTURE_FLAG_IF_EXISTS;
+        /* We certainly want to capture the host GL stack even if it
+         * appears older than what's in the container: the NVIDIA
+         * proprietary drivers have to be in lockstep with the kernel. */
+        new_options.flags |= CAPTURE_FLAG_EVEN_IF_OLDER;
+
+        return capture_patterns( gl_patterns, &new_options, code, message );
+    }
+
+    if( strstarts( pattern, "from:" ) )
+    {
+        return capture_lines( pattern + strlen( "from:" ),
+                              options, code, message );
+    }
+
+    if( strchr( pattern, ':' ) != NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "patterns containing ':' must match a known "
+                            "mode, not \"%s\" (use soname: or path: to "
+                            "take patterns containing ':' literally, if "
+                            "necessary)",
+                            pattern );
+        return false;
+    }
+
+    if( pattern[0] == '/' )
+    {
+        if( strchr( pattern, '*' ) != NULL ||
+            strchr( pattern, '?' ) != NULL ||
+            strchr( pattern, '[' ) != NULL )
+        {
+            // Interpret as if path-match:
+            return capture_path_match( pattern, options, code, message );
+        }
+        else
+        {
+            return capture_one( pattern, options, code, message );
+        }
+    }
+
+    if( strchr( pattern, '/' ) != NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "path arguments must be absolute, not \"%s\"",
+                            pattern );
+        return false;
+    }
+
+    if( strchr( pattern, '*' ) != NULL ||
+        strchr( pattern, '?' ) != NULL ||
+        strchr( pattern, '[' ) != NULL )
+    {
+        // Interpret as if soname-match:
+        return capture_soname_match( pattern, options, code, message );
+    }
+
+    // Default: interpret as if soname:
+    return capture_one( pattern, options, code, message );
+}
+
+static bool
+capture_patterns( const char * const *patterns,
+                  const capture_options *options,
+                  int *code, char **message )
+{
+    unsigned int i;
+
+    for( i = 0; patterns[i] != NULL; i++ )
+    {
+        if( !capture_pattern( patterns[i], options, code, message ) )
+            return false;
+    }
+
+    return true;
+}
+
+int
+main (int argc, char **argv)
+{
+    capture_options options = {
+        .comparators = NULL,
+        .flags = (CAPTURE_FLAG_LIBRARY_ITSELF |
+                  CAPTURE_FLAG_DEPENDENCIES ),
+        .knowledge = LIBRARY_KNOWLEDGE_INIT,
+    };
+    const char *option_compare_by = "name,provider";
+    const char *option_library_knowledge = NULL;
+    int code = 0;
+    char *message = NULL;
+    // Arbitrary initialization size
+    ptr_list *remap_list = ptr_list_alloc( 4 );
+
+    set_debug_flags( getenv("CAPSULE_DEBUG") );
+
+    while( 1 )
+    {
+        int opt = getopt_long( argc, argv, "h", long_options, NULL );
+
+        if( opt == -1 )
+        {
+            break;
+        }
+
+        switch( opt )
+        {
+            case '?':
+            default:
+                usage( 2 );
+                break;  // not reached
+
+            case 'h':
+                usage( 0 );
+                break;  // not reached
+
+            case OPTION_COMPARE_BY:
+                option_compare_by = optarg;
+                break;
+
+            case OPTION_CONTAINER:
+                option_container = optarg;
+                break;
+
+            case OPTION_DEST:
+                option_dest = optarg;
+                break;
+
+            case OPTION_LIBRARY_KNOWLEDGE:
+                if( option_library_knowledge != NULL )
+                    errx( 1, "--library-knowledge can only be used once" );
+
+                option_library_knowledge = optarg;
+                break;
+
+            case OPTION_LINK_TARGET:
+                option_link_target = optarg;
+                break;
+
+            case OPTION_PRINT_LD_SO:
+                puts( LD_SO );
+                goto out;
+
+            case OPTION_PROVIDER:
+                option_provider = optarg;
+                break;
+
+            case OPTION_NO_GLIBC:
+                option_glibc = false;
+                break;
+
+            case OPTION_VERSION:
+                _capsule_tools_print_version( "capsule-capture-libs" );
+                goto out;
+
+            case OPTION_REMAP_LINK_PREFIX:
+                if( strchr( optarg, '=' ) == NULL )
+                    errx( 1, "--remap-link-prefix value must follow the FROM=TO pattern" );
+
+                ptr_list_push_ptr( remap_list, xstrdup( optarg ) );
+                break;
+
+            case OPTION_RESOLVE_LD_SO:
+                {
+                    char path[PATH_MAX] = { 0 };
+                    const char *within_prefix = NULL;
+
+                    if( !resolve_ld_so( optarg, path, &within_prefix,
+                                        &code, &message ) )
+                    {
+                        errx( 1, "code %d: %s", code, message );
+                    }
+
+                    puts( within_prefix );
+                    goto out;
+                }
+                break;
+        }
+    }
+
+    if( optind >= argc )
+    {
+        warnx( "One or more patterns must be provided" );
+        usage( 2 );
+    }
+
+    if (remap_list->next > 0)
+    {
+        size_t n = 0;
+        char **remap_array = (char **) ptr_list_free_to_array( _capsule_steal_pointer( &remap_list ), &n );
+        remap_prefix = xcalloc( n + 1, sizeof( remap_tuple ) );
+        for( size_t i = 0; remap_array[i] != NULL; i++ )
+        {
+            char *buf = xstrdup( remap_array[i] );
+            char *equals = strchr( buf, '=' );
+            if( equals == NULL || equals == buf || *(equals + 1) == '\0' )
+            {
+                free( buf );
+                free_strv_full( remap_array );
+                errx( 1, "--remap-link-prefix value must follow the FROM=TO pattern" );
+            }
+            *equals = '\0';
+            remap_prefix[i].from = buf;
+            remap_prefix[i].to = equals + 1;
+        }
+        free_strv_full( remap_array );
+    }
+
+    arg_patterns = (const char * const *) argv + optind;
+    assert( arg_patterns[argc - optind] == NULL );
+
+    if( strcmp( option_dest, "." ) != 0 &&
+        mkdir( option_dest, 0755 ) < 0 &&
+        errno != EEXIST )
+    {
+        err( 1, "creating \"%s\"", option_dest );
+    }
+
+    options.comparators = library_cmp_list_from_string( option_compare_by, ",",
+                                                        &code, &message );
+
+    if( options.comparators == NULL )
+    {
+        errx( 1, "code %d: %s", code, message );
+    }
+
+    if( option_library_knowledge != NULL )
+    {
+        FILE *fh = fopen( option_library_knowledge, "re" );
+
+        if( fh == NULL)
+        {
+            err( 1, "opening \"%s\"", option_library_knowledge );
+        }
+
+        if( !library_knowledge_load_from_stream( &options.knowledge,
+                                                 fh, option_library_knowledge,
+                                                 &code, &message ) )
+        {
+            errx( 1, "code %d: %s", code, message );
+        }
+
+        fclose( fh );
+    }
+
+    dest_fd = open( option_dest, O_RDWR|O_DIRECTORY|O_CLOEXEC|O_PATH );
+
+    if( dest_fd < 0 )
+    {
+        err( 1, "opening \"%s\"", option_dest );
+    }
+
+    if( !capture_patterns( arg_patterns, &options, &code, &message ) )
+    {
+        errx( 1, "code %d: %s", code, message );
+    }
+
+    close( dest_fd );
+    free( options.comparators );
+    library_knowledge_clear( &options.knowledge );
+
+    for( size_t i = 0; remap_prefix != NULL && remap_prefix[i].from != NULL; i++ )
+    {
+        free( remap_prefix[i].from );
+        remap_prefix[i].from = NULL;
+        remap_prefix[i].to = NULL;
+    }
+
+out:
+    if( remap_list != NULL )
+    {
+        char **tmp_to_free = (char **) ptr_list_free_to_array( _capsule_steal_pointer( &remap_list ), NULL );
+        free_strv_full( tmp_to_free );
+    }
+
+    return 0;
+}
diff --git a/subprojects/libcapsule/utils/debug.h b/subprojects/libcapsule/utils/debug.h
new file mode 100644
index 0000000000000000000000000000000000000000..2eed3df89e18bedf560159d6bd8777b9ca62aa1c
--- /dev/null
+++ b/subprojects/libcapsule/utils/debug.h
@@ -0,0 +1,45 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+enum
+{
+    DEBUG_NONE       = 0,
+    DEBUG_PATH       = 0x1,
+    DEBUG_SEARCH     = 0x1 << 1,
+    DEBUG_LDCACHE    = 0x1 << 2,
+    DEBUG_CAPSULE    = 0x1 << 3,
+    DEBUG_MPROTECT   = 0x1 << 4,
+    DEBUG_WRAPPERS   = 0x1 << 5,
+    DEBUG_RELOCS     = 0x1 << 6,
+    DEBUG_ELF        = 0x1 << 7,
+    DEBUG_DLFUNC     = 0x1 << 8,
+    DEBUG_TOOL       = 0x1 << 9,
+    DEBUG_ALL        = 0xffff,
+};
+
+#define LDLIB_DEBUG(ldl, flags, fmt, args...)  \
+    if( ldl->debug && (ldl->debug & (flags)) ) \
+        fprintf( stderr, "%s:" fmt "\n", __PRETTY_FUNCTION__, ##args )
+
+#define DEBUG(flags, fmt, args...)               \
+    if( debug_flags && (debug_flags & (flags)) ) \
+        fprintf( stderr, "%s:" fmt "\n", __PRETTY_FUNCTION__, ##args )
+
+extern unsigned long debug_flags;
+void  set_debug_flags (const char *control);
diff --git a/subprojects/libcapsule/utils/dump.c b/subprojects/libcapsule/utils/dump.c
new file mode 100644
index 0000000000000000000000000000000000000000..12f6aea53f28e8b55a6eb1d75339ec84c8d801ea
--- /dev/null
+++ b/subprojects/libcapsule/utils/dump.c
@@ -0,0 +1,1140 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <link.h>
+#include "dump.h"
+#include "utils.h"
+
+// this file is a home for the various ways of parsing the ELF layout
+// of a program or library from within the executable itself, ie
+// it allows the different parts of the in-memory ELF layout to be
+// extracted in some useful way. not all these sections are relevant
+// or useful for our eventual goals, but as this knowledge is hard
+// to come by it is [literally] codified here
+
+// these macros are secretly the same for elf32 & elf64:
+#define ELFW_ST_TYPE(a)       ELF32_ST_TYPE(a)
+#define ELFW_ST_BIND(a)       ELF32_ST_BIND(a)
+#define ELFW_ST_VISIBILITY(a) ELF32_ST_VISIBILITY(a)
+
+typedef enum
+{
+    TTYPE_VAL,
+    TTYPE_PTR,
+    TTYPE_STR,
+} ttype;
+
+static const char *
+pt_flags (int x)
+{
+    static char flags[80];
+
+    flags[sizeof(flags) - 1] = '\0';
+    snprintf( flags, sizeof(flags) - 1,
+              "%c%c%c [%x %x]",
+              (x & PF_R) ? 'R' : '-',
+              (x & PF_W) ? 'W' : '-',
+              (x & PF_X) ? 'X' : '-',
+              x & PF_MASKOS   ,
+              x & PF_MASKPROC );
+
+    return flags;
+}
+
+static const char *
+pt_type (int x)
+{
+    switch (x)
+    {
+      case PT_NULL:         return "NULL";
+      case PT_LOAD:         return "LOAD";
+      case PT_DYNAMIC:      return "DYNAMIC";
+      case PT_INTERP:       return "INTERP";
+      case PT_NOTE:         return "NOTE";
+      case PT_SHLIB:        return "SHLIB";
+      case PT_PHDR:         return "PHDR";
+      case PT_TLS:          return "TLS";
+      case PT_NUM:          return "NUM";
+      case PT_LOOS:         return "LOOS";
+      case PT_GNU_EH_FRAME: return "GNU_EH_FRAME";
+      case PT_GNU_STACK:    return "GNU_STACK";
+      case PT_GNU_RELRO:    return "GNU_RELRO";
+      case PT_SUNWBSS:      return "SUNWBSS";
+      case PT_SUNWSTACK:    return "SUNWSTACK";
+      case PT_HISUNW:       return "HISUNW";
+      case PT_LOPROC:       return "LOPROC";
+      case PT_HIPROC:       return "HIPROC";
+      default:              return "-unknown-";
+    };
+}
+
+static const char *
+d_tag (unsigned long tag, int *tag_type)
+{
+    static char label[80];
+    const char *rval = label;
+
+    label[0] = '\0';
+    *tag_type = TTYPE_VAL;
+
+    switch( tag )
+    {
+      case DT_NULL: // Marks end of dynamic section
+        rval = "NULL";
+        break;
+
+      case DT_NEEDED: // Name of needed library
+        *tag_type = TTYPE_STR;
+        rval = "NEEDED";
+        break;
+
+      case DT_PLTRELSZ:		// Size in bytes of PLT relocs
+        rval = "PLTRELSZ";
+        break;
+
+      case DT_PLTGOT:   // Processor defined value
+        *tag_type = TTYPE_PTR;
+        rval = "PLTGOT";
+        break;
+
+      case DT_HASH: // Address of symbol hash table
+        *tag_type = TTYPE_PTR;
+        rval = "HASH";
+        break;
+
+      case DT_STRTAB: // Address of string table
+        *tag_type = TTYPE_PTR;
+        rval = "STRTAB";
+        break;
+
+      case DT_SYMTAB: // Address of symbol table
+        *tag_type = TTYPE_PTR;
+        rval = "SYMTAB";
+        break;
+
+      case DT_RELA: // Address of Rela relocs
+        *tag_type = TTYPE_PTR;
+        rval = "RELA";
+        break;
+
+      case DT_RELASZ:		// Total size of Rela relocs
+        rval = "RELASZ";
+        break;
+
+      case DT_RELAENT: // Size of one Rela reloc
+        rval = "RELAENT";
+        break;
+
+      case DT_STRSZ: // Size of string table
+        rval = "STRSZ";
+        break;
+
+      case DT_SYMENT: // Size of one symbol table entry
+        rval = "SYMENT";
+        break;
+
+      case DT_INIT: // Address of init function
+        *tag_type = TTYPE_PTR;
+        rval = "INIT";
+        break;
+
+      case DT_FINI: // Address of termination function
+        *tag_type = TTYPE_PTR;
+        rval = "FINI";
+        break;
+
+      case DT_SONAME: // Name of shared object
+        *tag_type = TTYPE_STR;
+        rval = "SONAME";
+        break;
+
+      case DT_RPATH: // Library search path (deprecated)
+        *tag_type = TTYPE_STR;
+        rval = "RPATH (deprecated)";
+        break;
+
+      case DT_SYMBOLIC: // Start symbol search here
+        *tag_type = TTYPE_PTR;
+        rval = "SYMBOLIC";
+        break;
+
+      case DT_REL: // Address of Rel relocs
+        *tag_type = TTYPE_PTR;
+        rval = "REL";
+        break;
+
+      case DT_RELSZ: // Total size of Rel relocs
+        rval = "RELSZ";
+        break;
+
+      case DT_RELENT: // Size of one Rel reloc
+        rval = "RELENT";
+        break;
+
+      case DT_PLTREL: // Type of reloc in PLT
+        rval = "PLTREL";
+        break;
+
+      case DT_DEBUG: // For debugging; unspecified
+        rval = "DEBUG";
+        break;
+
+      case DT_TEXTREL: // Reloc might modify .text
+        rval = "TEXTREL";
+        break;
+
+      case DT_JMPREL: // Address of PLT relocs
+        *tag_type = TTYPE_PTR;
+        rval = "JMPREL";
+        break;
+
+      case DT_BIND_NOW: // Process relocations of object
+        rval = "BIND_NOW";
+        break;
+
+      case DT_INIT_ARRAY:			// Array with addresses of init fct
+        *tag_type = TTYPE_PTR;
+        rval = "INIT_ARRAY";
+        break;
+
+      case DT_FINI_ARRAY: // Array with addresses of fini fct
+        *tag_type = TTYPE_PTR;
+        rval = "FINI_ARRAY";
+        break;
+
+      case DT_INIT_ARRAYSZ: // Size in bytes of DT_INIT_ARRAY
+        rval = "INIT_ARRAYSZ";
+        break;
+
+      case DT_FINI_ARRAYSZ: // Size in bytes of DT_FINI_ARRAY
+        rval = "FINI_ARRAYSZ";
+        break;
+
+      case DT_RUNPATH: // Library search path
+        *tag_type = TTYPE_PTR;
+        rval = "RUNPATH";
+        break;
+
+      case DT_FLAGS: // Flags for the object being loaded
+        rval = "FLAGS";
+        break;
+
+      case DT_PREINIT_ARRAY:		// Array with addresses of preinit fct
+        *tag_type = TTYPE_PTR;
+        rval = "PREINIT_ARRAY";
+        break;
+
+      case DT_PREINIT_ARRAYSZ: // size in bytes of DT_PREINIT_ARRAY
+        rval = "PREINIT_ARRAYSZ";
+        break;
+
+      case DT_NUM:          // Number used
+        rval = "NUM";
+        break;
+
+      case DT_GNU_PRELINKED:
+        rval = "GNU_PRELINKED"; // Prelinking timestamp
+        break;
+
+      case DT_GNU_CONFLICTSZ:
+        rval = "GNU_CONFLICTSZ"; // Size of conflict section
+        break;
+
+      case DT_GNU_LIBLISTSZ:
+        rval = "GNU_LIBLISTSZ"; // Size of library list
+        break;
+
+      case DT_CHECKSUM:
+        rval = "CHECKSUM"; // Feature selection (DTF_*).
+        break;
+
+      case DT_POSFLAG_1:
+        rval = "POSFLAG_1"; // Flags for DT_* entries, effecting the following DT_* entry.
+        break;
+
+      case DT_SYMINSZ:
+        rval = "SYMINSZ"; // Size of syminfo table (in bytes)
+        break;
+
+      case DT_SYMINENT:
+        rval = "SYMINENT"; // Entry size of syminfo
+        break;
+
+      case DT_GNU_HASH:
+        rval = "GNU_HASH"; // GNU-style hash table.
+        break;
+
+      case DT_TLSDESC_PLT:
+        rval = "TLSDESC_PLT"; // Start of conflict section
+        break;
+
+      case DT_GNU_LIBLIST:
+        rval = "GNU_LIBLIST"; // Library list
+        break;
+
+      case DT_CONFIG:
+        rval = "CONFIG"; // Configuration information.
+        break;
+
+      case DT_DEPAUDIT:
+        rval = "DEPAUDIT"; // Dependency auditing.
+        break;
+
+      case DT_AUDIT:
+        rval = "AUDIT"; // Object auditing.
+        break;
+
+      case DT_PLTPAD:
+        rval = "PLTPAD"; // PLT padding.
+        break;
+
+      case DT_MOVETAB:
+        rval = "MOVETAB"; // Move table.
+        break;
+
+      case DT_SYMINFO:
+        rval = "SYMINFO";
+        break;
+
+      case DT_VERSYM:
+        rval = "DT_VERSYM";
+        *tag_type = TTYPE_PTR;
+        break;
+
+      case DT_RELACOUNT:
+          rval = "RELACOUNT";
+        break;
+
+      case DT_RELCOUNT:
+          rval = "DT_RELCOUNT";
+        break;
+
+      case DT_FLAGS_1:  /* State flags, see DF_1_* below.  */
+        rval = "DT_FLAGS_1";
+        break;
+
+      case DT_VERDEF:  /* Address of version definition table */
+        rval = "DT_VERDEF";
+        break;
+
+      case DT_VERDEFNUM:  /* Number of version definitions */
+        rval = "VERDEFNUM";
+        break;
+
+      case DT_VERNEED: /* Address of table with needed versions */
+        *tag_type = TTYPE_PTR;
+        rval = "DT_VERNEED";
+        break;
+
+      case DT_VERNEEDNUM:
+        rval = "DT_VERNEEDNUM";
+        break;
+
+      default:
+        label[sizeof(label) - 1] = '\0';
+        if( tag >= DT_LOOS && tag <= DT_HIOS )
+            snprintf( label, sizeof(label) - 1, "OS_SPECIFIC:%lx", tag );
+        else if ( tag >= DT_LOPROC && tag <= DT_HIPROC )
+            snprintf( label, sizeof(label) - 1, "PROC_SPECIFIC:%lx", tag );
+        else
+            snprintf( label, sizeof(label) - 1, "-unknown-:%lx", tag );
+        rval = &label[0];
+    }
+
+    if ( tag >= DT_ADDRRNGLO && tag <= DT_ADDRRNGHI )
+        *tag_type = TTYPE_PTR;
+
+    return rval;
+}
+
+static const char *
+st_bind(int bind)
+{
+    switch( bind )
+    {
+      case STB_LOCAL:      return "LOCAL"          ; // Local symbol
+      case STB_GLOBAL:     return "GLOBAL"         ; // Global symbol
+      case STB_WEAK:       return "WEAK"           ; // Weak symbol
+      case STB_NUM:        return "NUM"            ; // Number of defined types.
+      case STB_LOOS:       return "LOOS/GNU_UNIQUE"; // Start of OS-specific
+      case STB_HIOS:       return "HIOS"           ; // End of OS-specific
+      case STB_LOPROC:     return "LOPROC"         ; // Start of processor-specific
+      case STB_HIPROC:     return "HIPROC"         ; // End of processor-specific
+      default:             return "???";
+    }
+}
+
+static const char *
+st_type(int type)
+{
+    switch( type )
+    {
+      case STT_NOTYPE:    return "NOTYPE"   ; // Symbol type is unspecified
+      case STT_OBJECT:    return "OBJECT"   ; // Symbol is a data object
+      case STT_FUNC:      return "FUNC"     ; // Symbol is a code object
+      case STT_SECTION:   return "SECTION"  ; // associated with a section
+      case STT_FILE:      return "FILE"     ; // Symbol's name is file name
+      case STT_COMMON:    return "COMMON"   ; // common data object
+      case STT_TLS:       return "TLS"      ; // thread-local data object
+      case STT_NUM:       return "NUM"      ; // Number of defined types.
+      case STT_LOOS:      return "LOOS/GNU_IFUNC"; // Start of OS-specific
+      case STT_HIOS:      return "HIOS"     ; // End of OS-specific
+      case STT_LOPROC:    return "LOPROC"   ; // Start of processor-specific
+      case STT_HIPROC:    return "HIPROC"   ; // End of processor-specific
+      default:            return "???";
+   }
+}
+
+static void
+symbol_version ( const ElfW(Sym) *symbol,
+                 int i,
+                 const char *strtab,
+                 const ElfW(Versym) *versym,
+                 const void *verdef,
+                 const int verdefnum,
+                 int *default_version,
+                 const char **version,
+                 ElfW(Versym) *vs)
+{
+    *default_version = 0;
+    *version = NULL;
+
+    if( versym == NULL )
+        return;
+
+    switch( symbol->st_shndx )
+    {
+      case SHN_UNDEF:
+      case SHN_ABS:
+      case SHN_COMMON:
+      case SHN_BEFORE:
+      case SHN_AFTER:
+      case SHN_XINDEX:
+        // none of these are handled (and we're very unlikely to need to)
+        break;
+
+      default:
+        if( symbol->st_shndx < SHN_LORESERVE )
+        {
+            const char  *vd = verdef;
+            *vs = *(versym + i);
+
+            for( int x = 0; x < verdefnum; x++ )
+            {
+                ElfW(Verdef) *entry = (ElfW(Verdef) *) vd;
+
+                if( entry->vd_ndx == (*vs & 0x7fff) )
+                {
+                    const char *au;
+
+                    if( entry->vd_flags & VER_FLG_BASE )
+                        break;
+
+                    au = vd + entry->vd_aux;
+                    ElfW(Verdaux) *aux = (ElfW(Verdaux) *) au;
+                    *version = strtab + aux->vda_name;
+                    *default_version = (*vs & 0x8000) ? 0 : 1;
+                }
+
+                vd = vd + entry->vd_next;
+            }
+        }
+    }
+}
+
+static void
+dump_symtab (const char *indent,
+             const ElfW(Sym) *start,
+             size_t symsz,
+             const char *strtab,
+             size_t strsz,
+             const ElfW(Versym) *versym,
+             const void *verdef,
+             const int verdefnum)
+{
+    int x = 0;
+    const ElfW(Sym) *entry;
+
+    for( entry = start;
+         (entry - start + 1) * sizeof(*entry) <= symsz;
+         entry++ )
+    {
+        int defversym = 0;
+        const char *version = NULL;
+        ElfW(Versym) vs = 0;
+
+        symbol_version( entry, x, strtab, versym, verdef, verdefnum,
+                        &defversym, &version, &vs );
+
+        fprintf(stderr, "%s    // %02d [%-8s %-8s] size:%2" FMT_WORD " (%d)%s%s%s%s %04x %p\n",
+                indent,
+                x,
+                st_type( ELFW_ST_TYPE(entry->st_info) ),
+                st_bind( ELFW_ST_BIND(entry->st_info) ),
+                // ELFW_ST_VISIBILITY(entry->st_other),
+                entry->st_size  ,
+                entry->st_shndx ,
+                strtab + entry->st_name ,
+                version   ? "@"     : "",
+                defversym ? "@"     : "",
+                version   ? version : "",
+                vs,
+                (void *)entry->st_value );
+        x++;
+    }
+}
+
+static void
+dump_interp (const char *indent, void *start, size_t size, ElfW(Addr) base)
+{
+    char interpreter[PATH_MAX + 1] = "";
+    interpreter[PATH_MAX] = '\0';
+
+    strncpy( &interpreter[0], start + base, PATH_MAX );
+
+    fprintf( stderr, "%s// %p %"FMT_SIZE" %p %s", indent, start, size, (void *)base,
+             interpreter );
+    return;
+}
+
+static void *
+addr (ElfW(Addr) base, ElfW(Addr) ptr)
+{
+    if( ptr > base )
+        return (void *)ptr;
+    else
+        return (void *)(base + ptr);
+}
+
+static const char *
+reloc_type (int rtype)
+{
+    switch( __ELF_NATIVE_CLASS )
+    {
+      case 64:
+        switch( rtype )
+        {
+          case R_X86_64_NONE:      return "NONE";      // No reloc
+          case R_X86_64_64:        return "64";        // Direct 64 bit
+          case R_X86_64_PC32:      return "PC32";      // PC rel 32 bit signed
+          case R_X86_64_GOT32:     return "GOT32";     // 32 bit GOT entry
+          case R_X86_64_PLT32:     return "PLT32";     // 32 bit PLT address
+          case R_X86_64_COPY:      return "COPY";      // Copy symbol at runtime
+          case R_X86_64_GLOB_DAT:  return "GLOB_DAT";  // Create GOT entry
+          case R_X86_64_JUMP_SLOT: return "JUMP_SLOT"; // Create PLT entry
+          case R_X86_64_RELATIVE:  return "RELATIVE";  // Adjust by program base
+          case R_X86_64_GOTPCREL:  return "GOTPCREL";  // 32 bit GOT PC ofset
+          case R_X86_64_32:        return "32";        // Direct 32 bit zero
+          case R_X86_64_32S:       return "32S";       // Direct 32 bit sign
+          case R_X86_64_16:        return "16";        // Direct 16 bit zero
+          case R_X86_64_PC16:      return "PC16";      // 16 bit extended pc
+          case R_X86_64_8:         return "8";         // Direct 8 bit extended
+          case R_X86_64_PC8:       return "PC8";       // 8 bit sign extended pc relative
+          case R_X86_64_DTPMOD64:  return "DTPMOD64";  // ID of containing symbol module
+          case R_X86_64_DTPOFF64:  return "DTPOFF64";  // Offset in module's TLS
+          case R_X86_64_TPOFF64:   return "TPOFF64";   // Offset in initial TLS
+          case R_X86_64_TLSGD:     return "TLSGD";     // 32 bit PC relative offset to two GOT entries for GD symbol
+          case R_X86_64_TLSLD:     return "TLSLD";     // 32 bit PC relative offset to two GOT entries for LD symbol
+          case R_X86_64_DTPOFF32:  return "DTPOFF32";  // Offset in TLS block
+          case R_X86_64_GOTTPOFF:  return "GOTTPOFF";  // 32 bit PC relative offset to GOT entry for IE symbol
+          case R_X86_64_TPOFF32:   return "TPOFF32";   // Offset in initial TLS
+          default:                 break;
+        }
+        break;
+      case 32:
+        switch( rtype )
+        {
+          case R_386_NONE:         return "NONE";         // No reloc
+          case R_386_32:           return "32";           // Direct 32 bit
+          case R_386_PC32:         return "PC32";         // PC relative 32 bit
+          case R_386_GOT32:        return "GOT32";        // 32 bit GOT entry
+          case R_386_PLT32:        return "PLT32";        // 32 bit PLT address
+          case R_386_COPY:         return "COPY";         // Copy symbol at runtime
+          case R_386_GLOB_DAT:     return "GLOB_DAT";     // Create GOT entry
+          case R_386_JMP_SLOT:     return "JMP_SLOT";     // Create PLT entry
+          case R_386_RELATIVE:     return "RELATIVE";     // Adjust by program base
+          case R_386_GOTOFF:       return "GOTOFF";       // 32 bit offset to GOT
+          case R_386_GOTPC:        return "GOTPC";        // 32 bit PC relative offset to GOT
+          case R_386_32PLT:        return "32PLT";
+          case R_386_TLS_TPOFF:    return "TLS_TPOFF";    // Offset in static TLS block
+          case R_386_TLS_IE:       return "TLS_IE";       // Address of GOT entry for static TLS block offset
+          case R_386_TLS_GOTIE:    return "TLS_GOTIE";    // GOT entry for static TLS block offset
+          case R_386_TLS_LE:       return "TLS_LE";       // Offset relative to static TLS block
+          case R_386_TLS_GD:       return "TLS_GD";       // Direct 32 bit for GNU version of general dynamic thread local data
+          case R_386_TLS_LDM:      return "TLS_LDM";      // Direct 32 bit for GNU version of local dynamic thread local data in LE code
+          case R_386_16:           return "16";
+          case R_386_PC16:         return "PC16";
+          case R_386_8:            return "8";
+          case R_386_PC8:          return "PC8";
+          case R_386_TLS_GD_32:    return "TLS_GD_32";    // Direct 32 bit for general dynamic thread local data
+          case R_386_TLS_GD_PUSH:  return "TLS_GD_PUSH";  // Tag for pushl in GD TLS code
+          case R_386_TLS_GD_CALL:  return "TLS_GD_CALL";  // Relocation for call to __tls_get_addr()
+          case R_386_TLS_GD_POP:   return "TLS_GD_POP";   // Tag for popl in GD TLS code
+          case R_386_TLS_LDM_32:   return "TLS_LDM_32";   // Direct 32 bit for local dynamic thread local data in LE code
+          case R_386_TLS_LDM_PUSH: return "TLS_LDM_PUSH"; // Tag for pushl in LDM TLS code
+          case R_386_TLS_LDM_CALL: return "TLS_LDM_CALL"; // Relocation for call to __tls_get_addr() in LDM code
+          case R_386_TLS_LDM_POP:  return "TLS_LDM_POP";  // Tag for popl in LDM TLS code
+          case R_386_TLS_LDO_32:   return "TLS_LDO_32";   // Offset relative to TLS block
+          case R_386_TLS_IE_32:    return "TLS_IE_32";    // GOT entry for negated static TLS block offset
+          case R_386_TLS_LE_32:    return "TLS_LE_32";    // Negated offset relative to static TLS block
+          case R_386_TLS_DTPMOD32: return "TLS_DTPMOD32"; // ID of module containing symbol
+          case R_386_TLS_DTPOFF32: return "TLS_DTPOFF32"; // Offset in TLS block
+          case R_386_TLS_TPOFF32:  return "TLS_TPOFF32";  // Negated offset in static TLS block
+          default:                 break;
+        }
+        break;
+      default:
+        fprintf( stderr, "Unknown __ELF_NATIVE_CLASS\n" );
+        abort();
+        break;
+    }
+
+    return "???";
+}
+
+static void
+dump_rel (const char *indent,
+          const void *start,
+          size_t relsz,
+          const char *strtab,
+          size_t strsz,
+          const void *symtab,
+          size_t symsz)
+{
+    int x = 0;
+    ElfW(Rel) *entry;
+
+    for( entry = (ElfW(Rel) *)start;
+         entry < (ElfW(Rel) *)(start + relsz);
+         entry++ )
+    {
+        int sym;
+        int chr;
+        const char *name = NULL;
+
+        const ElfW(Sym) *symbol;
+
+#if __ELF_NATIVE_CLASS == 32
+        sym = ELF32_R_SYM (entry->r_info);
+        chr = ELF32_R_TYPE(entry->r_info);
+#elif __ELF_NATIVE_CLASS == 64
+        sym = ELF64_R_SYM (entry->r_info);
+        chr = ELF64_R_TYPE(entry->r_info);
+#else
+        fprintf( stderr,
+                 "__ELF_NATIVE_CLASS is NOT 32 or 64:"
+                 " What the actual hell\n" );
+        exit( 22 );
+#endif
+
+        symbol = find_symbol( sym, symtab, symsz, strtab, strsz, &name );
+        if( symbol )
+            fprintf( stderr, "%s    // [%03d] %16s off: %"FMT_XADDR"; %s;\n",
+                     indent,
+                     x++,
+                     reloc_type(chr),
+                     entry->r_offset,
+                     name );
+        else
+            fprintf( stderr, "%s    // [%03d] %16s off: %"FMT_XADDR"; sym[%d];\n",
+                     indent,
+                     x++,
+                     reloc_type(chr),
+                     entry->r_offset,
+                     sym );
+    }
+}
+
+static void
+dump_rela (const char *indent,
+           const void *start,
+           size_t relasz,
+           const char *strtab,
+           size_t strsz,
+           const void *symtab,
+           size_t symsz,
+           ElfW(Addr)  base)
+{
+    int x = 0;
+    ElfW(Rela) *entry;
+
+    for( entry = (ElfW(Rela) *)start;
+         entry < (ElfW(Rela) *)(start + relasz);
+         entry++ )
+    {
+        int sym;
+        int chr;
+        const char *name = NULL;
+
+        const ElfW(Sym) *symbol;
+
+#if __ELF_NATIVE_CLASS == 32
+        sym = ELF32_R_SYM (entry->r_info);
+        chr = ELF32_R_TYPE(entry->r_info);
+#elif __ELF_NATIVE_CLASS == 64
+        sym = ELF64_R_SYM (entry->r_info);
+        chr = ELF64_R_TYPE(entry->r_info);
+#else
+        fprintf( stderr,
+                 "__ELF_NATIVE_CLASS is NOT 32 or 64:"
+                 " What the actual hell\n" );
+        exit( 22 );
+#endif
+
+        symbol = find_symbol( sym, symtab, symsz, strtab, strsz, &name );
+        if( symbol )
+            fprintf( stderr, "%s    // [%03d] %16s off: %"FMT_XADDR"; %s; add: 0x%"FMT_XADDR"\n",
+                     indent,
+                     x++,
+                     reloc_type(chr),
+                     entry->r_offset,
+                     name,
+                     entry->r_addend );
+        else
+            fprintf( stderr, "%s    // [%03d] %16s off: %"FMT_XADDR"; sym: %d; add: %"FMT_XADDR"\n",
+                     indent,
+                     x++,
+                     reloc_type(chr),
+                     entry->r_offset,
+                     sym,
+                     entry->r_addend );
+
+        switch( chr )
+        {
+            ElfW(Addr) *reloc_addr;
+          case R_X86_64_JUMP_SLOT:
+            reloc_addr = addr( base, entry->r_offset );
+            fprintf( stderr,
+                     "%s    // JUMP_SLOT relocation for %s is at %p "
+                     "and contains %p\n",
+                     indent,
+                     (symbol && name) ? name : "*unnamed*",
+                     (void *)reloc_addr,
+                     (void *)*((ElfW(Addr) *) reloc_addr) );
+            break;
+          default:
+            break;
+        }
+    }
+}
+
+static void
+dump_verneed(const char *indent,
+             const void *start,
+             int entries,
+             const char *strtab,
+             ElfW(Addr)  base)
+{
+    const char *vn = start;
+
+    for( int x = 0; x < entries; x++ )
+    {
+        ElfW(Verneed) *entry = (ElfW(Verneed) *) vn;
+
+        fprintf( stderr, "%s    // %02d %-15s : %d entries (aux %d)\n",
+                 indent, x,
+                 strtab + entry->vn_file,
+                 entries,
+                 // entry->vn_cnt,
+                 entry->vn_aux );
+
+        const char *au = vn + entry->vn_aux;
+
+        for( int y = 0; y < entry->vn_cnt; y++ )
+        {
+            ElfW(Vernaux) *aux = (ElfW(Vernaux) *) au;
+            fprintf( stderr, "%s    //    %s %s\n",
+                     indent,
+                     strtab + aux->vna_name,
+                     (aux->vna_flags & VER_FLG_WEAK) ? "(weak symbol)" : "" );
+            au = au + aux->vna_next;
+        }
+
+        vn = vn + entry->vn_next;
+    }
+}
+
+static void
+dump_verdef(const char *indent,
+            const void *start,
+            int entries,
+            const char *strtab,
+            ElfW(Addr)  base)
+{
+    const char *vd = start;
+
+    for( int x = 0; x < entries; x++ )
+    {
+        ElfW(Verdef) *entry = (ElfW(Verdef) *) vd;
+
+        fprintf( stderr, "%s    // %02d/%02d [%d]; ndx: %0x; flags: %0x\n",
+                 indent, x, entries,
+                 entry->vd_cnt  ,
+                 entry->vd_ndx  ,
+                 entry->vd_flags);
+
+        const char *au = vd + entry->vd_aux;
+
+        for( int y = 0; y < entry->vd_cnt; y++ )
+        {
+            ElfW(Verdaux) *aux = (ElfW(Verdaux) *) au;
+            fprintf( stderr, "%s    //    %s\n",
+                     indent,
+                     strtab + aux->vda_name );
+            au = au + aux->vda_next;
+        }
+
+        vd = vd + entry->vd_next;
+    }
+}
+
+
+static void
+dump_dynamic (const char *indent, void *start, size_t size, ElfW(Addr) base)
+{
+    int x = 0;
+    const char *tag;
+    ElfW(Dyn) *entry;
+
+    size_t strsiz  = -1;
+    size_t relasz  = -1;
+    size_t relsz   = -1;
+    size_t jmprelsz = -1;
+    int verneednum = -1;
+    int verdefnum  = -1;
+    int jmpreltype = DT_NULL;
+    size_t symsz;
+
+    const void *symtab = NULL;
+    const void *versym = NULL;
+    const void *verdef = NULL;
+    const char *strtab = dynamic_section_find_strtab( start + base, (const void *) base, &strsiz );
+    int tag_type = TTYPE_VAL;
+
+    fprintf( stderr, "%s{\n", indent );
+
+    for( entry = start + base;
+         (entry->d_tag != DT_NULL) && ((void *)entry < (start + base + size));
+         entry++ )
+    {
+        if( entry->d_tag == DT_SYMTAB )
+        {
+            symtab = fix_addr( (void *) base, entry->d_un.d_ptr );
+        }
+    }
+
+    if( strtab )
+    {
+        fprintf( stderr, "%s    string table at %p, %zu bytes\n",
+                 indent, strtab, strsiz );
+
+        /* XXX Apparently the only way to find out the size of the dynamic
+           symbol section is to assume that the string table follows right
+           afterwards... —glibc elf/dl-fptr.c */
+        assert( strtab >= (const char *) symtab );
+        symsz = strtab - (const char *) symtab;
+
+        fprintf( stderr, "%s    symbol table at %p, %zu bytes\n",
+                 indent, symtab, symsz );
+    }
+    else
+    {
+        fprintf( stderr, "%s    no string table?!\n", indent );
+        symsz = 0;
+    }
+
+    for( entry = start + base;
+         (entry->d_tag != DT_NULL) && ((void *)entry < (start + base + size));
+         entry++ )
+    {
+        tag = d_tag( entry->d_tag, &tag_type );
+
+        switch( tag_type )
+        {
+          case TTYPE_VAL:
+            fprintf( stderr, "%s    { #%03d %20s(%"FMT_SWORD") = val:%"FMT_WORD" }\n",
+                     indent,
+                     x++,
+                     tag,
+                     entry->d_tag,
+                     entry->d_un.d_val );
+            break;
+          case TTYPE_PTR:
+            fprintf( stderr, "%s    { #%03d %20s(%"FMT_SWORD") = ptr:%p }\n",
+                     indent, x++,
+                     tag,
+                     entry->d_tag,
+                     (const void *) entry->d_un.d_ptr );
+            break;
+          case TTYPE_STR:
+            fprintf( stderr, "%s    { #%03d %20s(%"FMT_SWORD") = str:%s }\n",
+                     indent,
+                     x++,
+                     tag,
+                     entry->d_tag,
+                     strtab + entry->d_un.d_ptr );
+            break;
+          default:
+            fprintf( stderr, "%s    { #%03d %20s(%"FMT_SWORD") = ???:%p }\n",
+                     indent,
+                     x++,
+                     tag,
+                     entry->d_tag,
+                     (void *)entry->d_un.d_ptr );
+            break;
+        }
+
+        switch( entry->d_tag )
+        {
+          case DT_PLTRELSZ:
+            jmprelsz = entry->d_un.d_val;
+            break;
+
+          case DT_SYMTAB:
+            if( versym == NULL )
+                versym = (const void *) find_ptr( base, start, DT_VERSYM );
+            if( verdef == NULL )
+                verdef = (const void *) find_ptr( base, start, DT_VERDEF );
+            if( verdefnum == -1 )
+                verdefnum = find_value( base, start, DT_VERDEFNUM );
+            symtab = fix_addr( (void *) base, entry->d_un.d_ptr );
+            dump_symtab( indent, symtab, symsz, strtab, strsiz, versym, verdef, verdefnum );
+            break;
+
+          case DT_RELA:
+            if( relasz == (size_t) -1 )
+                relasz = find_value( base, start, DT_RELASZ );
+            dump_rela( indent, (const void *) entry->d_un.d_ptr, relasz,
+                       strtab, strsiz, symtab, symsz, base );
+            break;
+
+          case DT_RELASZ:
+            relasz = entry->d_un.d_val;
+            break;
+
+          case DT_RELSZ:
+            relsz = entry->d_un.d_val;
+            break;
+
+          case DT_REL:
+            if( relsz == (size_t) -1 )
+                relsz = find_value( base, start, DT_RELSZ );
+            dump_rel( indent, (const void *) entry->d_un.d_ptr, relsz,
+                      strtab, strsiz, symtab, symsz );
+            break;
+
+          case DT_PLTREL:
+            jmpreltype = entry->d_un.d_val;
+            break;
+
+          case DT_JMPREL:
+            if( jmprelsz == (size_t) -1 )
+                jmprelsz = find_value( base, start, DT_PLTRELSZ );
+            if( jmpreltype == DT_NULL )
+                jmpreltype = find_value( base, start, DT_PLTREL );
+
+            switch( jmpreltype )
+            {
+                int unused;
+
+              case DT_REL:
+                dump_rel( indent, (const void *) entry->d_un.d_ptr, jmprelsz,
+                          strtab, strsiz, symtab, symsz );
+                break;
+
+              case DT_RELA:
+                dump_rela( indent, (const void *) entry->d_un.d_ptr, jmprelsz,
+                           strtab, strsiz, symtab, symsz, base );
+                break;
+
+              default:
+                fprintf( stderr, "%s    // unknown DT_PLTREL value: %d (%s)\n",
+                         indent, jmpreltype, d_tag( jmpreltype, &unused ) );
+                fprintf( stderr, "%s    // expected DT_REL or DT_RELA\n", indent );
+                break;
+            }
+            break;
+
+          case DT_VERNEEDNUM:
+            verneednum = entry->d_un.d_val;
+            break;
+
+          case DT_VERNEED:
+            if( verneednum == -1 )
+                verneednum = find_value( base, start, DT_VERNEEDNUM );
+            dump_verneed( indent,
+                          fix_addr( (const void *) base, entry->d_un.d_ptr ),
+                          verneednum,
+                          strtab, base );
+            break;
+
+          case DT_VERDEFNUM:
+            verdefnum = entry->d_un.d_val;
+            break;
+
+          case DT_VERDEF:
+            if( verdefnum == -1 )
+                verdefnum = find_value( base, start, DT_VERDEFNUM );
+            verdef = fix_addr( (const void *) base, entry->d_un.d_ptr );
+            dump_verdef( indent, verdef, verdefnum, strtab, base );
+            break;
+
+          case DT_VERSYM:
+            if( versym == NULL )
+                versym = (const void *) entry->d_un.d_ptr;
+            break;
+
+          default:
+            break;
+        }
+    }
+    fprintf(stderr, "%s}\n", indent);
+    return;
+}
+
+static int
+phdr_cb (struct dl_phdr_info *info, size_t size, void *data)
+{
+    int j;
+
+    if( data != NULL && strstr( info->dlpi_name, (const char *)data ) == NULL )
+        return 0;
+
+    fprintf(stderr, "\n\
+===============================================================================\n\
+struct dl_phdr_info                  \n\
+{                                    \n\
+    ElfW(Addr)        dlpi_addr;  %p \n\
+    const char       *dlpi_name; \"%s\" \n\
+    ElfW(Half)        dlpi_phnum; %d \n\
+    const ElfW(Phdr) *dlpi_phdr;  %p \n",
+            (void *)info->dlpi_addr  ,
+            info->dlpi_name  ,
+            info->dlpi_phnum ,
+            (void *)info->dlpi_phdr );
+
+    for( j = 0; j < info->dlpi_phnum; j++ )
+    {
+        fprintf(stderr, "    { // #%03d @ ( %p + %p ) %p\n\
+        ElfW(Word)  p_type;    %d %s   \n\
+        ElfW(Off)   p_offset;  %"FMT_OFF"\n\
+        ElfW(Addr)  p_vaddr;   %p      \n\
+        ElfW(Addr)  p_paddr;   %p      \n\
+        ElfW(Word)  p_filesz;  %"FMT_WORD" \n\
+        ElfW(Word)  p_memsz;   %"FMT_WORD" \n\
+        ElfW(Word)  p_flags;   0x%x %s \n\
+        ElfW(Word)  p_align;   %"FMT_WORD" \n",
+                j,
+                (void *)info->dlpi_addr             ,
+                (void *)info->dlpi_phdr[j].p_vaddr  ,
+                (void *) ( info->dlpi_addr + info->dlpi_phdr[j].p_vaddr ),
+                info->dlpi_phdr[j].p_type   ,
+                pt_type( info->dlpi_phdr[j].p_type ),
+                info->dlpi_phdr[j].p_offset ,
+                (void *)info->dlpi_phdr[j].p_vaddr  ,
+                (void *)info->dlpi_phdr[j].p_paddr  ,
+                info->dlpi_phdr[j].p_filesz ,
+                info->dlpi_phdr[j].p_memsz  ,
+                info->dlpi_phdr[j].p_flags  ,
+                pt_flags( info->dlpi_phdr[j].p_flags ),
+                info->dlpi_phdr[j].p_align    );
+        switch(info->dlpi_phdr[j].p_type)
+        {
+          case PT_DYNAMIC:
+            dump_dynamic("        ",
+                         (void *) info->dlpi_phdr[j].p_vaddr,
+                         info->dlpi_phdr[j].p_memsz,
+                         info->dlpi_addr);
+            break;
+          case PT_INTERP:
+            dump_interp("        ",
+                        (void *) info->dlpi_phdr[j].p_vaddr,
+                        info->dlpi_phdr[j].p_memsz,
+                        info->dlpi_addr);
+          default:
+            break;
+        }
+        fprintf(stderr, "\n    }\n");
+    }
+
+    fprintf(stderr, "};\n");
+    return 0;
+}
+
+void
+dump_elf_data (const char *soname)
+{
+    dl_iterate_phdr( phdr_cb, (void *) soname );
+}
+
+static int
+dlhdr_cb (struct dl_phdr_info *info, size_t size, void *data)
+{
+    ElfW(Dyn) *dyn = NULL;
+
+    for( int j = 0; j < info->dlpi_phnum; j++ )
+        if( info->dlpi_phdr[j].p_type == PT_DYNAMIC )
+            dyn = (ElfW(Dyn) *)info->dlpi_phdr[j].p_vaddr;
+
+    if( dyn )
+        fprintf(stderr, "+ dl_phdr: %p | %50s | addr: %p | dyn: %p |\n",
+                info,
+                info->dlpi_name,
+                (void *)info->dlpi_addr,
+                dyn + info->dlpi_addr );
+    else
+        fprintf(stderr, "+ dl_phdr: %p | %50s | addr: %p | dyn: NOT-FOUND |\n",
+                info,
+                info->dlpi_name,
+                (void *)info->dlpi_addr );
+
+    return 0;
+}
+
+void debug_link_maps (void)
+{
+    struct link_map *map;
+    void *handle;
+
+    handle = dlopen( NULL, RTLD_LAZY );
+    dlinfo( handle, RTLD_DI_LINKMAP, &map );
+
+    if (map->l_prev)
+        for( struct link_map *m = map; m; m = m->l_prev )
+            fprintf(stderr, "-link_map: %p | %50s | addr: %p | dyn: %p \n",
+                    m, m->l_name, (void *)m->l_addr, m->l_ld);
+
+    if (map->l_next)
+        for( struct link_map *m = map; m; m = m->l_next )
+            fprintf(stderr, "+link_map: %p | %50s | addr: %p | dyn: %p |\n",
+                    m, m->l_name, (void *)m->l_addr, m->l_ld);
+
+    dl_iterate_phdr( dlhdr_cb, NULL );
+}
diff --git a/subprojects/libcapsule/utils/dump.h b/subprojects/libcapsule/utils/dump.h
new file mode 100644
index 0000000000000000000000000000000000000000..f3fb3046c543f23d450ecbdce280686b4b96149a
--- /dev/null
+++ b/subprojects/libcapsule/utils/dump.h
@@ -0,0 +1,22 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+void dump_elf_data (const char *soname);
+
+void debug_link_maps (void);
diff --git a/subprojects/libcapsule/utils/elf-dump.c b/subprojects/libcapsule/utils/elf-dump.c
new file mode 100644
index 0000000000000000000000000000000000000000..9acf2cdc755beeba3fb8cef84bf85746151dcf10
--- /dev/null
+++ b/subprojects/libcapsule/utils/elf-dump.c
@@ -0,0 +1,117 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <dlfcn.h>
+#include <errno.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "debug.h"
+#include "dump.h"
+#include "tools.h"
+
+static void usage (int code) __attribute__((noreturn));
+static void usage (int code)
+{
+  FILE *fh;
+
+  if( code == 0 )
+  {
+      fh = stdout;
+  }
+  else
+  {
+      fh = stderr;
+  }
+
+  fprintf( fh, "Usage: %s SONAME\n",
+           program_invocation_short_name );
+  fprintf( fh, "SONAME is the machine-readable name of a shared library,\n"
+               "for example 'libz.so.1'.\n" );
+  exit( code );
+}
+
+enum
+{
+  OPTION_HELP,
+  OPTION_VERSION,
+};
+
+static struct option long_options[] =
+{
+    { "help", no_argument, NULL, OPTION_HELP },
+    { "version", no_argument, NULL, OPTION_VERSION },
+    { NULL }
+};
+
+int main (int argc, char **argv)
+{
+    void *handle;
+    const char *libname;
+
+    set_debug_flags( getenv("CAPSULE_DEBUG") );
+
+    while( 1 )
+    {
+        int opt = getopt_long( argc, argv, "", long_options, NULL );
+
+        if( opt == -1 )
+        {
+            break;
+        }
+
+        switch( opt )
+        {
+            case '?':
+            default:
+                usage( 2 );
+                break;  // not reached
+
+            case OPTION_HELP:
+                usage( 0 );
+                break;  // not reached
+
+            case OPTION_VERSION:
+                _capsule_tools_print_version( "capsule-elf-dump" );
+                return 0;
+        }
+    }
+
+    if( argc != optind + 1 )
+        usage( 1 );
+
+    handle = dlopen( argv[optind], RTLD_LAZY );
+
+    if( !handle )
+    {
+        int e = errno;
+        const char *err = dlerror();
+        fprintf( stderr, "%s: dlopen failed (%s)\n", program_invocation_short_name, err );
+        return e ? e : ENOENT;
+    }
+
+    if( (libname = strrchr( argv[optind], '/' )) )
+        libname = libname + 1;
+    else
+        libname = argv[optind];
+
+    dump_elf_data( libname );
+
+    return 0;
+}
diff --git a/subprojects/libcapsule/utils/ld-cache.c b/subprojects/libcapsule/utils/ld-cache.c
new file mode 100644
index 0000000000000000000000000000000000000000..600f2d3e69f6735ac38928f51864e62c63609e2e
--- /dev/null
+++ b/subprojects/libcapsule/utils/ld-cache.c
@@ -0,0 +1,370 @@
+// Copyright © 1999-2017 Free Software Foundation, Inc.
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <errno.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+
+#include "ld-cache.h"
+#include "utils.h"
+
+// ==========================================================================
+// this is stolen from the ld.so config (dl-cache.h) since we need
+// to use a slightly re-brained version of the linker to do our
+// filthy, filthy business (and these details are not in a shipped header)
+
+#define FLAG_ANY                -1
+#define FLAG_TYPE_MASK          0x00ff
+#define FLAG_LIBC4              0x0000
+#define FLAG_ELF                0x0001
+#define FLAG_ELF_LIBC5          0x0002
+#define FLAG_ELF_LIBC6          0x0003
+#define FLAG_REQUIRED_MASK      0xff00
+#define FLAG_SPARC_LIB64        0x0100
+#define FLAG_IA64_LIB64         0x0200
+#define FLAG_X8664_LIB64        0x0300
+#define FLAG_S390_LIB64         0x0400
+#define FLAG_POWERPC_LIB64      0x0500
+#define FLAG_MIPS64_LIBN32      0x0600
+#define FLAG_MIPS64_LIBN64      0x0700
+#define FLAG_X8664_LIBX32       0x0800
+#define FLAG_ARM_LIBHF          0x0900
+#define FLAG_AARCH64_LIB64      0x0a00
+#define FLAG_ARM_LIBSF          0x0b00
+#define FLAG_MIPS_LIB32_NAN2008     0x0c00
+#define FLAG_MIPS64_LIBN32_NAN2008  0x0d00
+#define FLAG_MIPS64_LIBN64_NAN2008  0x0e00
+
+static const char *flag_descr[] = { "libc4", "ELF", "libc5", "libc6"};
+
+static const char *const abi_tag_os[] =
+{
+    [0] = "Linux",
+    [1] = "Hurd",
+    [2] = "Solaris",
+    [3] = "FreeBSD",
+    [4] = "kNetBSD",
+    [5] = "Syllable",
+    [6] = "Unknown OS"
+};
+#define MAXTAG (N_ELEMENTS( abi_tag_os ) - 1)
+
+/* Used to align cache_file_new.  */
+#define ALIGN_CACHE(addr)                               \
+    (((addr) + __alignof__ (struct cache_file_new) -1)	\
+     & (~(__alignof__ (struct cache_file_new) - 1)))
+
+// end of stolen header structures
+////////////////////////////////////////////////////////////////////////////
+
+const char * const ld_cache_filenames[] =
+{
+#ifdef __i386__
+    "/etc/ld-i686-pc-linux-gnu.cache",    /* Exherbo */
+#endif
+#ifdef __x86_64__
+    "/etc/ld-x86_64-pc-linux-gnu.cache",  /* Exherbo */
+#endif
+    "/etc/ld.so.cache",                   /* interoperable */
+    "/var/cache/ldconfig/ld.so.cache",    /* Clear Linux */
+    NULL
+};
+
+static void
+ld_cache_reset (ld_cache *cache)
+{
+    cache->fd       = -1;
+    cache->map_size = 0;
+    cache->data     = NULL;
+    cache->file.old = NULL;
+    cache->type     = CACHE_NONE;
+    cache->mmap     = MAP_FAILED;
+    cache->is_open  = 0;
+}
+
+void
+ld_cache_close (ld_cache *cache)
+{
+    // 0 is a valid fd, but is also the default value of the unset
+    // struct member, so we have to check if it's _really_ open:
+    if( cache->is_open )
+    {
+        if( cache->fd >= 0 )
+            close( cache->fd );
+    }
+
+    if( cache->mmap && cache->map_size )
+        munmap( cache->mmap, cache->map_size );
+
+    ld_cache_reset( cache );
+}
+
+int
+ld_cache_open (ld_cache *cache, const char *path, int *code, char **message)
+{
+    struct stat ldcache = {};
+
+    ld_cache_close( cache );
+
+    cache->fd = open( path, O_RDONLY );
+
+    if( cache->fd < 0 )
+    {
+        int saved_errno = errno;
+
+        _capsule_set_error( code, message, saved_errno, "open \"%s\": %s",
+                            path, strerror( saved_errno ) );
+        goto cleanup;
+    }
+
+    // now we have a real file descriptor tag the
+    // cache as successfully opened:
+    cache->is_open = 1;
+
+    fstat( cache->fd, &ldcache );
+
+    // cache file must be at least this big or it's invalid:
+    if( ldcache.st_size < (off_t) sizeof( struct cache_file ) )
+    {
+        _capsule_set_error( code, message, ENODATA,
+                            "\"%s\" too small (%jd < %zu bytes)",
+                            path, (intmax_t) ldcache.st_size,
+                            sizeof( struct cache_file ) );
+        goto cleanup;
+    }
+
+    cache->mmap =
+      mmap( NULL, ldcache.st_size, PROT_READ, MAP_PRIVATE, cache->fd, 0 );
+
+    if( cache->mmap == MAP_FAILED )
+    {
+        int saved_errno = errno;
+
+        _capsule_set_error( code, message, saved_errno, "mmap \"%s\": %s",
+                            path, strerror( saved_errno ) );
+        goto cleanup;
+    }
+
+    cache->map_size = ldcache.st_size;
+
+    // plain modern (circa 2016) cache map:
+    if( memcmp( cache->mmap->magic, CACHEMAGIC, sizeof(CACHEMAGIC) -1 ) )
+    {
+        DEBUG( DEBUG_LDCACHE, "New format ld cache" );
+        cache->file.new = (struct cache_file_new *)cache->mmap;
+
+        // if the magic strings don't reside at the expected offsets, bail out:
+        if( memcmp( cache->file.new->magic,
+                    CACHEMAGIC_NEW, sizeof(CACHEMAGIC_NEW) - 1 ) ||
+            memcmp( cache->file.new->version,
+                    CACHE_VERSION, sizeof(CACHE_VERSION) - 1 ) )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "invalid cache, expected %s: %s",
+                                CACHEMAGIC_NEW, CACHE_VERSION );
+            goto cleanup;
+        }
+
+        cache->data = (char *)cache->file.new;
+        cache->type = CACHE_NEW;
+    }
+    else
+    {
+        size_t header = sizeof( struct cache_file );
+        size_t entry  = sizeof( struct file_entry );
+        size_t block  = header + (cache->mmap->nlibs * entry);
+        size_t offset = ALIGN_CACHE( block );
+        int nlibs = cache->mmap->nlibs;
+
+        DEBUG( DEBUG_LDCACHE, "Old format ld cache" );
+
+        // it's an old-style cache, unless we successfully probe for a
+        // nested new cache inside it:
+        cache->type = CACHE_OLD;
+
+        /* This is where the strings start in an old style cache  */
+        cache->data = (const char *) &cache->mmap->libs[ nlibs ];
+
+        if( cache->map_size > (offset + sizeof( struct cache_file_new )) )
+        {
+            cache->file.new = (void *)cache->mmap + offset;
+
+            // this is the probe: as in the pervious if block, except
+            // that if we don't find a new cache it's not an error,
+            // it just means we're in an old style cache:
+            if( memcmp( cache->file.new->magic, CACHEMAGIC_NEW,
+                        sizeof(CACHEMAGIC_NEW) - 1 ) ||
+                memcmp( cache->file.new->version, CACHE_VERSION,
+                        sizeof(CACHE_VERSION) - 1) )
+            {
+                // nope, no encapsulated new cache:
+                cache->file.old = cache->mmap;
+            }
+            else
+            {
+                DEBUG( DEBUG_LDCACHE, "... with a new style cache inside" );
+                cache->type = CACHE_NEW;
+                cache->data = (char *)cache->file.new;
+            }
+        }
+    }
+
+    DEBUG( DEBUG_LDCACHE, "Opened ld.cache at %s", path );
+    return 1;
+
+cleanup:
+    DEBUG( DEBUG_LDCACHE, "Failed to open ld.cache at %s", path );
+    ld_cache_close( cache );
+    return 0;
+}
+
+// iterate over the entries in the ld cache, invoking callback cb on each one
+// until eithe the callback returns true or we run out of entries.
+// if the callback terminates iteration by returning true, return that value,
+// otherwise return false:
+intptr_t
+ld_cache_foreach (ld_cache *cache, ld_cache_entry_cb cb, void *data)
+{
+    int rval = 0;
+    const char *base = cache->data;
+
+    switch( cache->type )
+    {
+      case CACHE_OLD:
+        for (unsigned i = 0; !rval && (i < cache->file.old->nlibs); i++)
+        {
+            struct file_entry *f = &cache->file.old->libs[i];
+            rval = cb( base + f->key, f->flags, 0, 0, base + f->value, data );
+        }
+        break;
+
+      case CACHE_NEW:
+        for (unsigned i = 0; !rval && (i < cache->file.new->nlibs); i++)
+        {
+            struct file_entry_new *f = &cache->file.new->libs[i];
+            rval = cb( base + f->key, f->flags,
+                       f->osversion, f->hwcap, base + f->value, data );
+        }
+        break;
+
+      case CACHE_NONE:
+      default:
+        fprintf( stderr, "Invalid ld cache type %d", cache->type );
+        break;
+    }
+
+    return rval;
+}
+
+intptr_t
+ld_entry_dump (const char *name,
+               int flag,
+               unsigned int osv,
+               uint64_t hwcap,
+               const char *path,
+               void *data)
+{
+    FILE *stream = (FILE *)data;
+    fprintf( stream, "%s → %s\n", name, path );
+
+    fputs( "  type: ", stream );
+    switch (flag & FLAG_TYPE_MASK)
+    {
+      case FLAG_LIBC4:
+      case FLAG_ELF:
+      case FLAG_ELF_LIBC5:
+      case FLAG_ELF_LIBC6:
+        fputs( flag_descr[flag & FLAG_TYPE_MASK], stream );
+        break;
+      default:
+        fputs( "???", stdout );
+        break;
+    }
+    fputs( "\n", stream );
+
+    fputs( "  requires: ", stream );
+    switch (flag & FLAG_REQUIRED_MASK)
+    {
+      case FLAG_SPARC_LIB64:
+        fputs( "Sparc 64bit", stream );
+        break;
+      case FLAG_IA64_LIB64:
+        fputs( "IA-64", stream );
+        break;
+      case FLAG_X8664_LIB64:
+        fputs( "x86-64", stream );
+        break;
+      case FLAG_S390_LIB64:
+        fputs( "s390 64bit", stream );
+        break;
+      case FLAG_POWERPC_LIB64:
+        fputs( "PPC 64bit", stream );
+        break;
+      case FLAG_MIPS64_LIBN32:
+        fputs( "MIPS N32", stream );
+        break;
+      case FLAG_MIPS64_LIBN64:
+        fputs( "MIPS 64bit", stream );
+        break;
+      case FLAG_X8664_LIBX32:
+        fputs( "x32", stream );
+        break;
+      case FLAG_ARM_LIBHF:
+        fputs( "Arm hard-float", stream );
+        break;
+      case FLAG_AARCH64_LIB64:
+        fputs( "AArch64", stream );
+        break;
+        /* Uses the ARM soft-float ABI.  */
+      case FLAG_ARM_LIBSF:
+        fputs( "Arm soft-float", stream );
+        break;
+      case FLAG_MIPS_LIB32_NAN2008:
+        fputs( "MIPS nan2008", stream );
+      break;
+      case FLAG_MIPS64_LIBN32_NAN2008:
+        fputs( "MIPS N32 nan2008", stream );
+        break;
+      case FLAG_MIPS64_LIBN64_NAN2008:
+        fputs( "IPS 64bit nan2008", stream );
+        break;
+      case 0:
+        break;
+      default:
+        fprintf (stream, "%0x", flag & FLAG_REQUIRED_MASK);
+        break;
+    }
+    fputs( "\n", stream );
+
+    unsigned int os = osv >> 24;
+
+    fprintf( stream, "  OS ABI: %s %d.%d.%d (%0x)\n",
+             abi_tag_os[ os > MAXTAG ? MAXTAG : os ],
+             (osv >> 16) & 0xff,
+             (osv >> 8 ) & 0xff,
+             osv & 0xff,
+             osv );
+
+    fprintf( stream, "  hwcaps: %0"FMT_XU64"\n", hwcap );
+    return 0;
+}
diff --git a/subprojects/libcapsule/utils/ld-cache.h b/subprojects/libcapsule/utils/ld-cache.h
new file mode 100644
index 0000000000000000000000000000000000000000..869458af21347dacee68df602855bd125cac5db3
--- /dev/null
+++ b/subprojects/libcapsule/utils/ld-cache.h
@@ -0,0 +1,103 @@
+// Copyright © 1999-2017 Free Software Foundation, Inc.
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#include <sys/types.h>
+#include <stdint.h>
+
+typedef enum
+{
+    CACHE_NONE,
+    CACHE_NEW ,
+    CACHE_OLD ,
+} cache_type;
+
+
+// ==========================================================================
+// this is stolen from the ld.so config (dl-cache.h) since we need
+// to use a slightly re-brained version of the linker to do our
+// filthy, filthy business (and these details are not in a shipped header)
+#define CACHEMAGIC "ld.so-1.7.0"
+
+#define CACHEMAGIC_NEW "glibc-ld.so.cache"
+#define CACHE_VERSION "1.1"
+#define CACHEMAGIC_VERSION_NEW CACHEMAGIC_NEW CACHE_VERSION
+
+struct file_entry_new
+{
+    int32_t flags;        /* This is 1 for an ELF library.  */
+    uint32_t key, value;  /* String table indices.  */
+    uint32_t osversion;   /* Required OS version.  */
+    uint64_t hwcap;       /* Hwcap entry.  */
+};
+
+struct cache_file_new
+{
+    char magic[sizeof CACHEMAGIC_NEW - 1];
+    char version[sizeof CACHE_VERSION - 1];
+    uint32_t nlibs;                /* Number of entries.  */
+    uint32_t len_strings;          /* Size of string table. */
+    uint32_t unused[5];            /* Leave space for future extensions
+                                      and align to 8 byte boundary.  */
+    struct file_entry_new libs[0]; /* Entries describing libraries.  */
+    /* After this the string table of size len_strings is found.  */
+};
+
+struct file_entry
+{
+    int flags;               /* This is 1 for an ELF library.  */
+    unsigned int key, value; /* String table indices.  */
+};
+
+struct cache_file
+{
+    char magic[sizeof CACHEMAGIC - 1];
+    unsigned int nlibs;
+    struct file_entry libs[0];
+};
+
+typedef struct
+{
+    int fd;
+    size_t map_size;
+    struct cache_file *mmap;
+    const char *data;
+    union { struct cache_file *old; struct cache_file_new *new; } file;
+    cache_type type;
+    int is_open;
+} ld_cache;
+
+// ==========================================================================
+
+extern const char * const ld_cache_filenames[];
+
+typedef intptr_t (*ld_cache_entry_cb) (const char *name,
+                                       int flag,
+                                       unsigned int osv,
+                                       uint64_t hwcap,
+                                       const char *path,
+                                       void *data);
+
+int      ld_cache_open    (ld_cache *cache, const char *path, int *code,
+                           char **message);
+void     ld_cache_close   (ld_cache *cache);
+intptr_t ld_cache_foreach (ld_cache *cache, ld_cache_entry_cb cb, void *data);
+
+intptr_t ld_entry_dump (const char *name, int flag, unsigned int osv,
+                        uint64_t hwcap, const char *path, void *data);
diff --git a/subprojects/libcapsule/utils/ld-libs.c b/subprojects/libcapsule/utils/ld-libs.c
new file mode 100644
index 0000000000000000000000000000000000000000..2a0b9c87395db9838facf7b538d32f3aab06da62
--- /dev/null
+++ b/subprojects/libcapsule/utils/ld-libs.c
@@ -0,0 +1,1156 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <assert.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <link.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <execinfo.h>
+
+#include "utils.h"
+#include "ld-libs.h"
+
+
+struct dso_cache_search
+{
+    int idx;
+    const char *name;
+    ld_libs *ldlibs;
+};
+
+static const struct stat *stat_caller (void);
+
+// Note that this is not re-entrant. In this context we don't care.
+static const char *
+_rtldstr(int flag)
+{
+    static char flags[160] = { 0 };
+    char *f = &flags[0];
+
+    if( !flag)
+        return "LOCAL";
+
+#define RTLDFLAGSTR(x) \
+    if( x & flag ) f += snprintf(f, &flags[80] - f, " %s", & #x [5])
+
+    RTLDFLAGSTR(RTLD_LAZY);
+    RTLDFLAGSTR(RTLD_NOW);
+    RTLDFLAGSTR(RTLD_NOLOAD);
+    RTLDFLAGSTR(RTLD_DEEPBIND);
+    RTLDFLAGSTR(RTLD_GLOBAL);
+    RTLDFLAGSTR(RTLD_NODELETE);
+
+    return ( flags[0] == ' ' ) ? &flags[1] : &flags[0];
+}
+
+// as we are pulling in files from a non '/' prefix ('/host' by default)
+// we need to compensate for this when resolving symlinks.
+// this will keep following the path at entry i in ldlibs until
+// it finds something that is not a symlink.
+static void resolve_symlink_prefixed (ld_libs *ldlibs, int i)
+{
+    int count = 0;
+    char resolved[PATH_MAX];
+
+    assert( ldlibs->prefix.path[ ldlibs->prefix.len ] == '\0' );
+    // prefix is unset or is /, nothing to do here (we can rely on
+    // libc's built-in symlink following if there's no prefix):
+    if( ldlibs->prefix.len == 0 ||
+        (ldlibs->prefix.path[0] == '/' && ldlibs->prefix.path[1] == '\0') )
+        return;
+
+    LDLIB_DEBUG( ldlibs, DEBUG_PATH,
+                 "resolving (un)prefixed link in %s", ldlibs->needed[i].path );
+
+    // set the resolved path to the current needed path as a starting point:
+    safe_strncpy( resolved, ldlibs->needed[i].path, PATH_MAX );
+
+    // now keep poking resolve_link (resolved will be updated each time)
+    // until it returns false:
+    while( resolve_link(ldlibs->prefix.path, resolved) )
+    {
+        LDLIB_DEBUG( ldlibs, DEBUG_PATH, "  resolved to: %s", resolved );
+
+        if( ++count > MAXSYMLINKS )
+        {
+            fprintf( stderr, "%s: MAXSYMLINKS (%d) exceeded resolving %s\n",
+                     __PRETTY_FUNCTION__, MAXSYMLINKS,
+                     ldlibs->needed[i].path );
+            break;
+        }
+    }
+
+    // if the path changed, copy `resolved' back into needed[].path:
+    if( count )
+        safe_strncpy( ldlibs->needed[i].path, resolved, PATH_MAX );
+}
+
+
+// set the ldlibs elf class and machine based on the link map entry
+// passed to us if possible: if we found values for these, return 1,
+// otherwise return 0:
+static int
+find_elf_constraints(ld_libs *ldlibs, struct link_map *m)
+{
+    int fd = -1;
+    Elf *dso = NULL;
+    GElf_Ehdr ehdr = { };
+
+    // absolute path or it's a "fake" link map entry which we can't use
+    // as there's no actual file to open and inspect:
+    if( !m || !m->l_name || (m->l_name[0] != '/'))
+        return 0;
+
+    // if we can't open the DSO pointed to by the link map, bail:
+    fd = open( m->l_name, O_RDONLY );
+
+    if( fd < 0 )
+        return 0;
+
+    dso = elf_begin( fd, ELF_C_READ_MMAP, NULL );
+
+    if( dso && gelf_getehdr( dso, &ehdr ) )
+    {
+        ldlibs->elf_class   = gelf_getclass( dso );
+        ldlibs->elf_machine = ehdr.e_machine;
+        DEBUG( DEBUG_SEARCH|DEBUG_CAPSULE,
+               "elf class: %d; elf machine: %d; set from: %s",
+               ldlibs->elf_class, ldlibs->elf_machine, m->l_name );
+    }
+
+    if( dso != NULL )
+        elf_end( dso );
+
+    if( fd >= 0 )
+        close( fd );
+
+    return ( ldlibs->elf_class != ELFCLASSNONE );
+}
+
+// record the class & machine of the start of the link chain
+// so that we can only consider matching libraries later
+// this matters on multi-arch systems so we don't pick an
+// i386 or x32 DSO to statisfy a DT_NEEDED from an x86-64 one.
+// return true if we found a valid DSO, false (can't happen?) otherwise
+static int
+set_elf_constraints (ld_libs *ldlibs)
+{
+    void *handle;
+    struct link_map *map;
+    struct link_map *m;
+
+    static int elf_class = ELFCLASSNONE;
+    static Elf64_Half elf_machine = EM_NONE;
+
+    if( elf_class != ELFCLASSNONE )
+    {
+        ldlibs->elf_class   = elf_class;
+        ldlibs->elf_machine = elf_machine;
+    }
+    else if( (handle = dlmopen( LM_ID_BASE, NULL, RTLD_LAZY|RTLD_NOLOAD )) &&
+             (dlinfo( handle, RTLD_DI_LINKMAP, &map ) == 0)   )
+    {
+        // we're not guaranteed to be at the start of the link map chain:
+        while( map->l_prev )
+            map = map->l_prev;
+
+        // check link maps until we find one we can fill in
+        // our constraints from:
+        for( m = map; m; m = m->l_next )
+            if( find_elf_constraints( ldlibs, m ) )
+                break;
+    }
+    else
+    {
+        // this would be frankly beyond bizarre:
+        fprintf(stderr, "dlopen/dlinfo on self failed: %s\n", dlerror() );
+    }
+
+    return ( ( ldlibs->elf_class   != ELFCLASSNONE ) &&
+             ( ldlibs->elf_machine |= EM_NONE      ) );
+}
+
+// check that the currently opened DSO at offset idx in the needed array
+// matches the class & architecture of the DSO we started with:
+// return true on a match, false otherwise
+static int
+check_elf_constraints (ld_libs *ldlibs, int idx, int *code, char **message)
+{
+    GElf_Ehdr ehdr = {};
+    int eclass;
+
+    // bogus ELF DSO - no ehdr available?
+    if( !gelf_getehdr( ldlibs->needed[ idx ].dso, &ehdr ) )
+    {
+        // FIXME: elf_errno() isn't actually in the same domain as errno
+        _capsule_set_error( code, message, elf_errno(), "gelf_getehdr(%s): %s",
+                            ldlibs->needed[ idx ].path,
+                            elf_errmsg( elf_errno() ) );
+        return 0;
+    }
+
+    eclass = gelf_getclass( ldlibs->needed[ idx ].dso );
+
+    // check class (32 vs 64 bit)
+    if( ldlibs->elf_class != eclass )
+    {
+        _capsule_set_error( code, message, ENOEXEC,
+                            "gelf_getclass(%s): expected %d, found %d",
+                            ldlibs->needed[ idx ].path, ldlibs->elf_class,
+                            eclass );
+        return 0;
+    }
+
+    // check target architecture (i386, x86-64)
+    // x32 ABI is class 32 but machine x86-64
+    if( ldlibs->elf_machine != ehdr.e_machine )
+    {
+        _capsule_set_error( code, message, ENOEXEC,
+                            "ehdr.e_machine of %s: expected %d, found %d",
+                            ldlibs->needed[ idx ].path, ldlibs->elf_machine,
+                            ehdr.e_machine );
+        return 0;
+    }
+
+    DEBUG( DEBUG_ELF, "constraints: class %d; machine: %d;",
+           ldlibs->elf_class, ldlibs->elf_machine );
+    DEBUG( DEBUG_ELF, "results    : class %d; machine: %d;",
+           eclass, ehdr.e_machine );
+
+    // both the class (word size) and machine (architecture) match
+    return 1;
+}
+
+// check that the library we're opening is NOT ourself (this is to stop
+// a proxy libfoo.so.X from opening itself instead of the real target):
+// In normal situations we won't find ourself by accident as the path
+// prefix should place the real library ahead of us in the search order.
+// However LD_PRELOAD or a weirdly configured system or a missing target
+// library could result in the capsule library finding itself and
+// inifinitely looping, so we try to avoid that with this test:
+static int
+target_is_ourself  (ld_libs *ldlibs, int idx)
+{
+    const struct stat *cdso = stat_caller();
+    struct stat xdso = { 0 };
+
+    // Can't check, no stat info for orginal caller.
+    // Assume things will mostly be ok, mostly:
+    if( cdso == NULL )
+        return 0;
+
+    // if stat says we have the same device and inode as
+    // the target we're considering ourselves as a target:
+    if( fstat( ldlibs->needed[idx].fd, &xdso ) == 0 )
+        return ( cdso->st_dev == xdso.st_dev &&
+                 cdso->st_ino == xdso.st_ino );
+
+    // placeholder while this test is developed:
+    return 0;
+}
+
+static void clear_needed (dso_needed_t *needed)
+{
+    elf_end( needed->dso );
+    needed->dso = NULL;
+
+    if( needed->fd >= 0 )
+        close( needed->fd );
+    needed->fd = -1;
+
+    free( needed->name );
+    needed->name = NULL;
+
+    needed->depcount = 0;
+
+    memset( needed->path, 0, PATH_MAX );
+    memset( needed->requestors, 0, sizeof(int) * DSO_LIMIT );
+}
+
+// open the dso at offset i in the needed array, but only accept it
+// if it matches the class & architecture of the starting DSO:
+// return a true value only if we finish with a valid fd for the DSO
+//
+// will set up the needed entry at offset i correctly if we are
+// successful, and clear it if we are not:
+//
+// designed to be called on a populated ldlib needed entry, 'name'
+// is the original requested name (typically an absolute path to
+// a DSO or a standard DT_NEEDED style specifier following 'libfoo.so.X')
+//
+// note that this expects the prefix to have already been prepended
+// to the DSO path in the ldlibs->needed[i].path buffer if necessary
+// this is to allow both prefixed and unprefixed DSOs to be handled
+// here:
+static int
+ld_lib_open (ld_libs *ldlibs, const char *name, int i, int *code, char **message)
+{
+
+    LDLIB_DEBUG( ldlibs, DEBUG_SEARCH,
+                 "ldlib_open: target -: %s", ldlibs->needed[i].path );
+
+    // resolve the symlink manually if there's a prefix:
+    resolve_symlink_prefixed( ldlibs, i );
+
+    LDLIB_DEBUG( ldlibs, DEBUG_SEARCH,
+                 "ldlib_open: target +: %s", ldlibs->needed[i].path );
+
+    ldlibs->needed[i].fd = open( ldlibs->needed[i].path, O_RDONLY );
+
+    if( ldlibs->needed[i].fd >= 0 )
+    {
+        int acceptable = 0;
+
+        ldlibs->needed[i].name = NULL;
+        ldlibs->needed[i].dso  =
+          elf_begin( ldlibs->needed[i].fd, ELF_C_READ_MMAP, NULL );
+
+        acceptable = check_elf_constraints( ldlibs, i, code, message );
+
+        LDLIB_DEBUG( ldlibs, DEBUG_SEARCH,
+                     "[%03d] %s on fd #%d; elf: %p; acceptable: %d",
+                     i,
+                     ldlibs->needed[i].path,
+                     ldlibs->needed[i].fd  ,
+                     ldlibs->needed[i].dso ,
+                     acceptable );
+
+        if( !acceptable )
+        {
+            clear_needed( &ldlibs->needed[i] );
+            return 0;
+        }
+
+        acceptable = !target_is_ourself( ldlibs, i );
+
+        // either clean up the current entry so we can find a better DSO
+        // or (for a valid candidate) copy the original requested name in:
+        if( !acceptable )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "\"%s\" appears to be the libcapsule shim",
+                                ldlibs->needed[i].path );
+            clear_needed( &ldlibs->needed[i] );
+            return 0;
+        }
+
+        ldlibs->needed[i].name = xstrdup( name );
+        return 1;
+    }
+    else
+    {
+        int errsv = errno;
+
+        _capsule_set_error( code, message, errsv,
+                            "Cannot open \"%s\": %s",
+                            ldlibs->needed[i].path, strerror( errsv ) );
+        clear_needed( &ldlibs->needed[i] );
+        return 0;
+    }
+}
+
+// search callback for search_ldcache. see search_ldcache and ld_cache_foreach:
+// returning a true value means we found (and set up) the DSO we wanted:
+static intptr_t
+search_ldcache_cb (const char *name, // name of the DSO in the ldcache
+                   int flag,         // 1 for an ELF DSO
+                   unsigned int osv, // OS version. we don't use this
+                   uint64_t hwcap,   // HW caps. Ibid.
+                   const char *path, // absolute path to DSO (may be a symlink)
+                   void *data)
+{
+    struct dso_cache_search *target = data;
+
+    // passed an empty query, just abort the whole search
+    if( !target->name || !(*target->name) )
+        return 1;
+
+    // what would this even mean? malformed cache entry?
+    // skip it and move on
+    if( !name || !*name )
+        return 0;
+
+    if( strcmp( name, target->name ) == 0 )
+    {
+        const char *prefix = target->ldlibs->prefix.path;
+        int    idx    = target->idx;
+        char  *lpath  = target->ldlibs->needed[ idx ].path;
+
+        LDLIB_DEBUG( target->ldlibs, DEBUG_SEARCH|DEBUG_LDCACHE,
+                     "checking %s vs %s [%s]",
+                     target->name, name, path );
+        // copy in the prefix and append the DSO path to it
+        if( build_filename( lpath, PATH_MAX, prefix, path, NULL ) >= PATH_MAX )
+        {
+            return 0;
+        }
+
+        // try to open the DSO. This will finish setting up the
+        // needed[idx] slot if successful, and reset it ready for
+        // another attempt if it fails.
+        // TODO: Can we propagate error information?
+        if( !ld_lib_open( target->ldlibs, name, idx, NULL, NULL ) )
+        {
+            // search_ldcache() relies on fd >= 0 iff we succeeded
+            assert( target->ldlibs->needed[idx].fd < 0 );
+            assert( target->ldlibs->needed[idx].name == NULL );
+            return 0;
+        }
+
+        // search_ldcache() relies on fd >= 0 iff we succeeded
+        assert( target->ldlibs->needed[idx].fd >= 0 );
+        assert( target->ldlibs->needed[idx].name != NULL );
+        return 1;
+    }
+
+    return 0;
+}
+
+// search the ld.so.cache loaded into ldlibs for one matching `name'
+// name should be unadorned: eg just libfoo.so.X - no path elements
+// attached (as the cache lookup is for unadorned library names):
+//
+// if a match is found, the needed array entry at i will be populated
+// and will contain a valid fd for the DSO. (and search_ldcache will
+// return true). Otherwise the entry will be empty and we will return false:
+//
+// this function will respect any path prefix specified in ldlibs
+static int
+search_ldcache (const char *name, ld_libs *ldlibs, int i)
+{
+    struct dso_cache_search target;
+
+    target.idx    = i;
+    target.name   = name;
+    target.ldlibs = ldlibs;
+
+    if( !ldlibs->ldcache.is_open )
+    {
+        if( !ld_libs_load_cache( ldlibs, NULL, NULL ) )
+        {
+            // TODO: report error?
+            return 0;
+        }
+    }
+
+    ld_cache_foreach( &ldlibs->ldcache,
+                      search_ldcache_cb, &target );
+
+    return ldlibs->needed[i].fd >= 0;
+}
+
+// search a : separated path (such as LD_LIBRARY_PATH from the environment)
+// for a DSO matching the bare `name' (eg libfoo.so.X)
+//
+// if a match is found, the needed array entry at i will be populated
+// and will contain a valid fd for the DSO. (and search_ldcache will
+// return true). Otherwise the entry will be empty and we will return false:
+//
+// this function will respect any path prefix specified in ldlibs
+static int
+search_ldpath (const char *name, const char *ldpath, ld_libs *ldlibs, int i)
+{
+    const char *sp = ldpath;
+    const char *prefix = ldlibs->prefix.path;
+    size_t plen   = ldlibs->prefix.len;
+    char search[PATH_MAX];
+    char prefixed[PATH_MAX] = { '\0' };
+
+    assert( prefix[plen] == '\0' );
+
+    LDLIB_DEBUG( ldlibs, DEBUG_SEARCH,
+                 "searching for %s in %s (prefix: %s)",
+                 name, ldpath, plen ? prefix : "-none-" );
+
+    while( sp && *sp )
+    {
+        size_t len;
+        const char *end;
+
+        end = strchr( sp, ':' );
+        if( end )
+            len = MIN((size_t) (end - sp), sizeof(search) - 1);
+        else
+            len = MIN(strlen( sp ), sizeof(search) - 1);
+
+        // If this gets truncated, then there will be no space left in
+        // prefixed either, so we'll skip it anyway
+        safe_strncpy( search, sp, len + 1 );
+
+        LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, "  searchpath element: %s", search );
+
+        if( build_filename( prefixed, sizeof(prefixed), prefix, search,
+                            name, NULL ) >= sizeof(prefixed) )
+        {
+            LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, "    (too long)" );
+        }
+        else
+        {
+            LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, "examining %s", prefixed );
+            // if path resolution succeeds _and_ we can open an acceptable
+            // DSO at that location, we're good to go (ldlib_open will
+            // finish setting up or clearing the needed[] entry for us):
+            // FIXME: this is broken if the target is an absolute symlink
+            // TODO: Propagate error information from ld_lib_open()?
+            if( realpath( prefixed, ldlibs->needed[i].path ) &&
+                ld_lib_open( ldlibs, name, i, NULL, NULL ) )
+                return 1;
+        }
+
+        // search the next path element if there is one
+        if( !end )
+            break;
+
+        sp = end + 1;
+    }
+
+    return 0;
+}
+
+// find a DSO using an algorithm that matches the one used by the
+// normal dynamic linker and set up the needed array entry at offset i.
+//
+// Exceptions:
+// we don't support DT_RPATH/DT_RUNPATH
+// we don't handle ${ORIGIN} and similar
+// we will respect any path prefix specified in ldlibs
+//
+// if a match is found, the needed array entry at i will be populated
+// and will contain a valid fd for the DSO. (and search_ldcache will
+// return true). Otherwise the entry will be empty and we will return false:
+static int
+dso_find (const char *name, ld_libs *ldlibs, int i, int *code, char **message)
+{
+    int found = 0;
+    const char *ldpath = NULL;
+    int absolute = (name && (name[0] == '/'));
+
+    // 'name' is an absolute path, or relative to CWD:
+    // we may to need to do some path manipulation
+    if( strchr( name, '/' ) )
+    {
+        char prefixed[PATH_MAX];
+        const char *target;
+
+        // we have a path prefix, so yes, we need to do some path manipulation:
+        if( ldlibs->prefix.len )
+        {
+            assert( ldlibs->prefix.path[ ldlibs->prefix.len ] == '\0' );
+
+            if( build_filename( prefixed, sizeof(prefixed),
+                                ldlibs->prefix.path, name,
+                                NULL ) >= sizeof(prefixed) )
+            {
+                _capsule_set_error( code, message, ENAMETOOLONG,
+                                    "path to DSO is too long: \"%s...\"",
+                                    prefixed );
+                return 0;
+            }
+            else if( absolute )
+            {
+                LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_PATH,
+                             "absolute path to DSO %s", prefixed );
+            }
+            else
+            {   // name is relative... this is probably wrong?
+                // I don't think this can ever really happen but
+                // worst case is we'll simply not open a DSO whose
+                // path we couldn't resolve, and then move on
+                // (if name is something like ./mylib.so then we'll
+                // have treated it as though it was $prefix/./mylib.so)
+                LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_PATH,
+                             "relative path to DSO %s", prefixed );
+            }
+
+            target = prefixed;
+        }
+        else
+        {   // name is a standard bare 'libfoo.so.X' spec:
+            target = name;
+        }
+
+        LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_PATH,
+                     "resolving path %s", target );
+        // this will fail for a non-absolute path, but that's OK
+        // if realpath lookup succeeds needed[i].path will be set correctly:
+        if( realpath( target, ldlibs->needed[i].path ) )
+        {
+            return ld_lib_open( ldlibs, name, i, code, message );
+        }
+        else if( absolute )
+        {
+            int saved_errno = errno;
+
+            // path was absolute and we couldn't resolve it. give up:
+            _capsule_set_error( code, message, saved_errno,
+                                "realpath(\"%s\"): %s",
+                                ldlibs->needed[i].path,
+                                strerror( saved_errno ) );
+            return 0;
+        }
+    }
+    else
+    {
+        assert( !absolute );
+    }
+
+    LDLIB_DEBUG( ldlibs, DEBUG_SEARCH, "target DSO is %s", name );
+    // now search LD_LIBRARY_PATH, the ld.so.cache, and the default locations
+    // in that order (similar algorithm to the linker, but with the RPATH and
+    // ${ORIGIN} support dropped)
+    if( (ldpath = getenv( "LD_LIBRARY_PATH" )) )
+        if( (found = search_ldpath( name, ldpath, ldlibs, i )) )
+            return found;
+
+    if( (found = search_ldcache( name, ldlibs, i )) )
+        return found;
+
+    if( (found = search_ldpath( name, "/lib:/usr/lib", ldlibs, i )) )
+        return found;
+
+    if (ldpath)
+        _capsule_set_error( code, message, ENOENT,
+                            "Could not find \"%s\" in LD_LIBRARY_PATH \"%s\", "
+                            "ld.so.cache, /lib or /usr/lib", name, ldpath );
+    else
+        _capsule_set_error( code, message, ENOENT,
+                            "Could not find \"%s\" in LD_LIBRARY_PATH (unset), "
+                            "ld.so.cache, /lib or /usr/lib", name );
+
+    return 0;
+}
+
+// if a DSO has already been requested and found as a result of a DT_NEEDED
+// entry we've seen before then it's already in the needed array - check
+// for such pre-required entries and simply record the dependency instead
+// of reopening the DSO (and return true to indicate that we already have the
+// DSO)
+//
+// we have assumed that the root of the DSO chain can never be already-needed
+// as this would indicate a circular dependency.
+static int
+already_needed (dso_needed_t *needed, int requesting_idx, const char *name)
+{
+    for( int i = DSO_LIMIT - 1; i > 0; i-- )
+    {
+        if( needed[i].name && strcmp( needed[i].name, name ) == 0)
+        {
+            DEBUG( DEBUG_CAPSULE, "needed[%d] (%s) also needs needed[%d] (%s)",
+                   requesting_idx, needed[requesting_idx].name, i, name );
+            needed[i].requestors[requesting_idx] = 1;
+            return i;
+        }
+    }
+
+    return 0;
+}
+
+// we're getting to the meat of it: process a DSO at offset idx in the
+// needed array, extract each SHT_DYNAMIC section, then make sure we
+// can find a DSO to satisfy every DT_NEEDED sub-entry in the section.
+// this function recurses into itself each time it finds a previously
+// unseen DT_NEEDED value (but not if the DT_NEEDED value is for a DSO
+// it has already found and recorded in the needed array)
+//
+// NOTE: you must use dso_find to seed the 0th entry in the needed array
+// or the elf handle in needed[0].dso will not be set up and hilarity*
+// will ensue.
+static int
+_dso_iterate_sections (ld_libs *ldlibs, int idx, int *code, char **error)
+{
+    int had_error = 0;
+    Elf_Scn *scn = NULL;
+
+    //debug(" ldlibs: %p; idx: %d (%s)", ldlibs, idx, ldlibs->needed[idx].name);
+
+    ldlibs->last_idx = idx;
+
+    LDLIB_DEBUG( ldlibs, DEBUG_ELF,
+                 "%03d: fd:%d dso:%p ← %s",
+                 idx,
+                 ldlibs->needed[idx].fd,
+                 ldlibs->needed[idx].dso,
+                 ldlibs->needed[idx].path );
+
+    while((scn = elf_nextscn( ldlibs->needed[idx].dso, scn )) != NULL)
+    {
+        GElf_Shdr shdr = {};
+        gelf_getshdr( scn, &shdr );
+
+        // SHT_DYNAMIC is the only section type we care about here:
+        if( shdr.sh_type == SHT_DYNAMIC )
+        {
+            int i = 0;
+            GElf_Dyn dyn = {};
+            Elf_Data *edata = NULL;
+
+            edata = elf_getdata( scn, edata );
+
+            // process eaach DT_* entry in the SHT_DYNAMIC section:
+            while( !had_error                      &&
+                   gelf_getdyn( edata, i++, &dyn ) &&
+                   (dyn.d_tag != DT_NULL)          )
+            {
+                int skip = 0;
+                int next = ldlibs->last_idx;
+                dso_needed_t *needed = ldlibs->needed;
+                char *next_dso; // name of the dependency we're going to need
+                char *local_error = NULL;
+
+                // we're only gathering DT_NEEDED (dependency) entries here:
+                if( dyn.d_tag != DT_NEEDED )
+                    continue;
+
+                next_dso =
+                  elf_strptr( needed[idx].dso, shdr.sh_link, dyn.d_un.d_val );
+
+                //////////////////////////////////////////////////
+                // ignore the linker itself
+                if( strstr( next_dso, "ld-" ) == next_dso )
+                    continue;
+
+                // ignore any DSOs we've been specifically told to leave out:
+                for( char **x = (char **)ldlibs->exclude; x && *x; x++ )
+                {
+                    if( strcmp( *x, next_dso ) == 0 )
+                    {
+                        LDLIB_DEBUG( ldlibs, DEBUG_SEARCH|DEBUG_ELF,
+                                     "skipping %s / %s", next_dso, *x );
+                        skip = 1;
+                        break;
+                    }
+                }
+
+                if( skip )
+                    continue;
+
+                //////////////////////////////////////////////////
+                // if we got this far, we have another dependency:
+                needed[idx].depcount++;
+
+                // already on our list, no need to do anything else here:
+                if( already_needed( needed, idx, next_dso ) )
+                    continue;
+
+                next++;
+                if( next >= DSO_LIMIT )
+                {
+                    had_error = 1;
+                    _capsule_set_error_literal( code, error, ELIBMAX,
+                                                "Too many dependencies" );
+                    break;
+                }
+
+                if( !dso_find( next_dso, ldlibs, next, code, &local_error ) )
+                {
+                    had_error = 1;
+                    ldlibs->not_found[ ldlibs->last_not_found++ ] =
+                      _capsule_steal_pointer( &local_error );
+                    // Avoid "piling up" errors which would be a memory
+                    // leak
+                    if( error != NULL && *error == NULL )
+                        *error = xstrdup( "Missing dependencies:" );
+                }
+                else
+                {
+                    LDLIB_DEBUG( ldlibs, DEBUG_CAPSULE,
+                                 "needed[%d] (%s) is the first to need needed[%d] (%s)",
+                                 idx, needed[idx].name, next, next_dso );
+                    // record which DSO requested the new library we found:
+                    needed[next].requestors[idx] = 1;
+                    // now find the dependencies of our newest dependency:
+                    _dso_iterate_sections( ldlibs, next, code, error );
+                }
+            }
+        }
+    }
+
+    return !had_error;
+}
+
+static void
+_dso_iterator_format_error (ld_libs * ldlibs, char **message)
+{
+    size_t extra_space = 0;
+
+    assert( message );
+    assert( *message );
+
+    if( ! ldlibs->not_found[0] )
+        return;
+
+    for( int i = 0; (i < DSO_LIMIT) && ldlibs->not_found[i]; i++ )
+        extra_space += strlen( ldlibs->not_found[i] ) + 1;
+
+    if( extra_space )
+    {
+        char *append_here;
+        char *end;
+        size_t prev_space = strlen( *message );
+
+        *message = xrealloc( *message, prev_space + extra_space + 2 );
+        append_here = *message + prev_space;
+        end = *message + prev_space + extra_space + 1;
+        memset( append_here, 0, extra_space + 2 );
+
+        for( int i = 0; (i < DSO_LIMIT) && ldlibs->not_found[i]; i++ )
+        {
+            append_here +=
+              snprintf( append_here, end - append_here,
+                        " %s", ldlibs->not_found[i] );
+            free( ldlibs->not_found[i] );
+            ldlibs->not_found[i] = NULL;
+        }
+    }
+}
+
+// wrapper to format any accumulated errors and similar after
+// invoking the actual dso iterator: returns true if we gathered
+// all the needed info witout error, false otherwise:
+static int
+dso_iterate_sections (ld_libs *ldlibs, int idx, int *code, char **message)
+{
+    if( _dso_iterate_sections( ldlibs, idx, code, message ) )
+        return 1;
+
+    if( message )
+        _dso_iterator_format_error( ldlibs, message );
+
+    return 0;
+}
+
+
+////////////////////////////////////////////////////////////////////////////
+// public API:
+int
+ld_libs_init (ld_libs *ldlibs,
+              const char **exclude,
+              const char *prefix,
+              unsigned long dbg,
+              int *errcode,
+              char **error)
+{
+    memset( ldlibs, 0, sizeof(ld_libs) );
+
+    ld_cache_close( &ldlibs->ldcache );
+
+    ldlibs->elf_class   = ELFCLASSNONE;
+    ldlibs->elf_machine = EM_NONE;
+    ldlibs->exclude     = exclude;
+    ldlibs->debug       = dbg;
+
+    if( errcode )
+        *errcode = 0;
+
+    if( error )
+        *error = NULL;
+
+    // super important, 0 is valid but is usually stdin,
+    // don't want to go stomping all over that by accident:
+    for( int x = 0; x < DSO_LIMIT; x++ )
+        ldlibs->needed[x].fd = -1;
+
+    if( elf_version(EV_CURRENT) == EV_NONE )
+    {
+        // FIXME: elf_errno() isn't actually in the same domain as errno
+        _capsule_set_error( errcode, error, elf_errno(),
+                            "elf_version(EV_CURRENT): %s",
+                            elf_errmsg( elf_errno() ) );
+        return 0;
+    }
+
+    stat_caller();
+    set_elf_constraints( ldlibs );
+    // ==================================================================
+    // set up the path prefix at which we expect to find the encapsulated
+    // library and its ld.so.cache and dependencies and so forth:
+    if( prefix && prefix[0] != '\0' )
+    {
+        size_t prefix_len = strlen( prefix );
+        ssize_t space = PATH_MAX - prefix_len;
+
+        if( prefix[0] != '/' )
+        {
+            _capsule_set_error( errcode, error, EINVAL,
+                                "An absolute path is required, not \"%s\"",
+                                prefix );
+        }
+
+        // if we don't have at least this much space it's not
+        // going to work out:
+        if( (space - strlen( "/usr/lib/libx.so.x" )) <= 0 )
+        {
+            _capsule_set_error_literal( errcode, error, ENAMETOOLONG,
+                                        "capsule_dlmopen: prefix is too large" );
+            return 0;
+        }
+
+        // This now can't get truncated
+        safe_strncpy( ldlibs->prefix.path, prefix, PATH_MAX );
+        ldlibs->prefix.len = prefix_len;
+    }
+    else
+    {
+        ldlibs->prefix.path[0] = '\0';
+        ldlibs->prefix.len     = 0;
+    }
+
+    return 1;
+}
+
+// set up the starting point for our capsule load
+// (ie the DSO we actually want):
+// returns false on error:
+int
+ld_libs_set_target (ld_libs *ldlibs, const char *target, int *code, char **message)
+{
+    return dso_find( target, ldlibs, 0, code, message );
+}
+
+int
+ld_libs_find_dependencies (ld_libs *ldlibs, int *code, char **message)
+{
+    return dso_iterate_sections( ldlibs, 0, code, message );
+}
+
+// And now we actually open everything we have found, in reverse
+// dependency order (which prevents dlmopen from going and finding
+// DT_NEEDED values from outside the capsule), which it will do
+// if we don't work backwards:
+void *
+ld_libs_load (ld_libs *ldlibs, Lmid_t *namespace, int flag, int *error,
+              char **message)
+{
+    int go;
+    Lmid_t lm = (*namespace >= 0) ? *namespace : LM_ID_NEWLM;
+    void *ret = NULL;
+
+    if( !flag )
+        flag = RTLD_LAZY;
+
+    do
+    {
+        go = 0;
+
+        for( int j = 0; j < DSO_LIMIT; j++ )
+        {
+            // reached the end of the list
+            if( !ldlibs->needed[j].name )
+                continue;
+
+            // library has no further dependencies which have not already
+            // been satisfied (except for the libc and linker DSOs),
+            // this means we can safely open it without dlmopen accidentally
+            // pulling in DSOs from outside the encapsulated tree:
+            if( ldlibs->needed[j].depcount == 0 )
+            {
+                const char *path = ldlibs->needed[j].path;
+                go++;
+
+                LDLIB_DEBUG( ldlibs, DEBUG_CAPSULE,
+                             "DLMOPEN needed[%d]: %p %s %s",
+                             j, (void *)lm, _rtldstr(flag), path );
+
+                // The actual dlmopen. If this was the first one, it may
+                // have created a new link map id, wich we record later on:
+
+                // note that since we do the opens in reverse dependency order,
+                // the _last_ one we open will be the DSO we actually asked for
+                // so if we succeed, ret has to contain the right handle.
+                ret = dlmopen( lm, path, flag );
+
+                if( !ret )
+                {
+                    if (lm == LM_ID_NEWLM)
+                        _capsule_set_error( error, message, EINVAL,
+                                            "dlmopen(LM_ID_NEWLM, \"%s\", %s): %s",
+                                            path, _rtldstr( flag ), dlerror() );
+                    else
+                        _capsule_set_error( error, message, EINVAL,
+                                            "dlmopen(%p, \"%s\", %s): %s",
+                                            (void *) lm, path,
+                                            _rtldstr( flag ), dlerror() );
+
+                    return NULL;
+                }
+
+                // If this was the first dlmopen, record the new LM Id
+                // for return to our caller:
+                if( lm == LM_ID_NEWLM )
+                {
+                    dlinfo( ret, RTLD_DI_LMID, namespace );
+                    lm = *namespace;
+                    LDLIB_DEBUG( ldlibs, DEBUG_CAPSULE,
+                                 "new Lmid_t handle %p\n", (void *)lm );
+                }
+
+                // go through the map of DSOs and reduce the dependency
+                // count for any DSOs which had the current DSO as a dep:
+                for( int k = 0; k < DSO_LIMIT; k++ )
+                {
+                    if( ldlibs->needed[j].requestors[k] )
+                    {
+                        ldlibs->needed[k].depcount--;
+                        LDLIB_DEBUG( ldlibs, DEBUG_CAPSULE,
+                                     "needed[%d] (%s) dependency on \"%s\" "
+                                     "satisfied, %d more libraries needed",
+                                     k, ldlibs->needed[k].name, path,
+                                     ldlibs->needed[k].depcount );
+                    }
+                }
+
+                LDLIB_DEBUG( ldlibs, DEBUG_CAPSULE,
+                             "Forgetting about needed[%d] (%s)",
+                             j, ldlibs->needed[j].name );
+                clear_needed( &ldlibs->needed[j] );
+            }
+        }
+    } while (go);
+
+    if( !ret )
+    {
+        _capsule_set_error( error, message, EINVAL,
+                            "How do we get here? go = 0" );
+        return NULL;
+    }
+
+    return ret;
+}
+
+void
+ld_libs_finish (ld_libs *ldlibs)
+{
+    for( int i = 0; i < DSO_LIMIT; i++ )
+        clear_needed( &ldlibs->needed[i] );
+
+    for( int i = ldlibs->last_not_found; i >= 0; i-- )
+    {
+        free( ldlibs->not_found[i] );
+        ldlibs->not_found[i] = NULL;
+    }
+
+    ldlibs->last_not_found = 0;
+
+    ld_cache_close( &ldlibs->ldcache );
+
+    ldlibs->last_idx = 0;
+    ldlibs->elf_class = ELFCLASSNONE;
+    ldlibs->elf_machine = EM_NONE;
+
+    ldlibs->prefix.len = 0;
+    ldlibs->prefix.path[0] = '\0';
+}
+
+// this walks backwards along (up? down?) the stack until it finds
+// a DSO with a base address that differs from its own, then caches
+// the stat info for said path.
+// This is to support the libcapsule-proxies-will-not-reopen-themselves
+// infinite loop prevention mechanism.
+static const struct stat *
+stat_caller (void)
+{
+    static struct stat cdso = { 0 };
+    static int done = 0;
+
+    Dl_info self = { 0 };
+    char caller[PATH_MAX] = { '\0' };
+
+    if( !done && dladdr( ld_libs_init, &self ) )
+    {
+        void *trace[16] = { NULL };
+        Dl_info dso     = { 0 };
+        void *origin    = self.dli_fbase;
+        int traced;
+
+        traced = backtrace( trace, N_ELEMENTS( trace ) );
+
+        for( int x = 0; x < traced && caller[0] == '\0'; x++ )
+            if( dladdr( trace[x], &dso ) )
+                if( origin != dso.dli_fbase )
+                    safe_strncpy( caller, dso.dli_fname, PATH_MAX );
+
+        done = 1;
+
+        if( caller[0] == '\0' )
+        {
+            DEBUG( DEBUG_SEARCH|DEBUG_ELF,
+                   "unable to test for infinitely-looped capsule, "
+                   "proceeding anyway" );
+        }
+        else
+        {
+            DEBUG( DEBUG_SEARCH|DEBUG_ELF,
+                   "initial libcapsule user is %s", caller );
+            if( soname_matches_path( "libc.so", caller ) )
+            {
+                // if we get back to here we're looking at a normal
+                // library from one of the build helper tools and
+                // we don't care about loop protection:
+                DEBUG( DEBUG_SEARCH|DEBUG_ELF,
+                       "  … which is not a capsule: disabling loop protection" );
+            }
+            else
+            {
+                stat( caller, &cdso );
+            }
+        }
+    }
+
+    return (cdso.st_size > 0) ? &cdso : NULL;
+}
+
+// map the ld.so.cache for the system into memory so that we can search it
+// for DSOs in the same way as the dynamic linker.
+//
+// returns true on success, false otherwise.
+//
+// this function respects any path prefix specified in ldlibs
+int
+ld_libs_load_cache (ld_libs *libs, int *code, char **message)
+{
+    char prefixed[PATH_MAX] = { '\0' };
+    size_t i;
+
+    for( i = 0; ld_cache_filenames[i] != NULL; i++ )
+    {
+        if( message != NULL )
+            _capsule_clear( message );
+
+        if( build_filename( prefixed, sizeof(prefixed), libs->prefix.path,
+                            ld_cache_filenames[i], NULL ) >= sizeof(prefixed) )
+        {
+            _capsule_set_error( code, message, ENAMETOOLONG,
+                                "Cannot append \"%s\" to prefix \"%s\": too long",
+                                ld_cache_filenames[i], libs->prefix.path );
+            continue;
+        }
+
+        if( ld_cache_open( &libs->ldcache, prefixed, code, message ) )
+            return 1;
+    }
+
+    // return the last error
+    return 0;
+}
diff --git a/subprojects/libcapsule/utils/ld-libs.h b/subprojects/libcapsule/utils/ld-libs.h
new file mode 100644
index 0000000000000000000000000000000000000000..1fa7acfb8aeffe16c07436dbf4f6583a07b1acbb
--- /dev/null
+++ b/subprojects/libcapsule/utils/ld-libs.h
@@ -0,0 +1,110 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#include <limits.h>
+#include <link.h>
+
+#include <libelf.h>
+#include <gelf.h>
+
+#include "ld-cache.h"
+
+// we only handle up to this many library dependencies -
+// yes, hardwired limits are bad but there's already enough complexity
+// here - can revisit this decision if it ever becomes close to being
+// an issue (shouldn't affect the api or abi):
+#define DSO_LIMIT 256
+
+/*
+ * dso_needed_t:
+ * @fd: @path opened for reading (only valid after ld_lib_open())
+ * @name: The name we are looking for, either a bare SONAME or an
+ *  absolute path (only valid after ld_lib_open())
+ * @path: Absolute path to the library we need to load, including the
+ *  %CAPSULE_PREFIX if required (must be set before ld_lib_open())
+ * @requestors: `needed[i].requestors[j]` is 1 if `needed[j]`
+ *  depends on `needed[i]`
+ * @depcount: the number of libraries that this one depends on,
+ *  such that `needed[j].depcount` is the number of nonzero
+ *  `needed[i].requestors[j]` for each value of *i* where `needed[i]`
+ *  has not yet been loaded
+ * @dso: @fd opened for ELF inspection (only valid after ld_lib_open())
+ *
+ * A library that we need to load.
+ */
+typedef struct
+{
+    int   fd;
+    char *name;
+    char  path[PATH_MAX];
+    int   requestors[DSO_LIMIT];
+    int   depcount;
+    Elf  *dso;
+} dso_needed_t;
+
+/**
+ * ld_libs:
+ * @ldcache: the runtime linker cache, or all-zeroes
+ *  if ld_libs_load_cache() has not yet been called
+ * @last_idx: private, used internally by the ld-libs code
+ * @elf_class: the ELF class of the caller that initialized this
+ * @elf_machine: the ELF machine type of the caller that initialized this
+ * @prefix: the sysroot from which we will load encapsulated libraries
+ * @exclude: (array zero-terminated=1) (nullable): libraries to ignore
+ * @needed: private, used internally by the ld-libs code.
+ *  needed[0] is the library we are looking for, and needed[1...]
+ *  are the libraries in its recursive dependency tree.
+ * @not_found: (transfer full): private, used internally by the ld-libs
+ *  code. Each item is a copy of the name of a missing dependency.
+ * @last_not_found: private, used internally by the ld-libs code.
+ *  Number of items in @not_found used.
+ * @debug: The debug flags passed to ld_libs_init()
+ *
+ * Data structure representing the libraries used in a capsule.
+ */
+typedef struct
+{
+    ld_cache ldcache;
+    int last_idx;
+    int elf_class;
+    Elf64_Half elf_machine;
+    struct { char path[PATH_MAX]; size_t len; } prefix;
+    const char **exclude;
+    dso_needed_t needed[DSO_LIMIT];
+    char *not_found[DSO_LIMIT];
+    int last_not_found;
+    unsigned long debug;
+} ld_libs;
+
+int   ld_libs_init (ld_libs *ldlibs,
+                    const char **exclude,
+                    const char *prefix,
+                    unsigned long dbg,
+                    int *error,
+                    char **message);
+
+int   ld_libs_set_target        (ld_libs *ldlibs, const char *target,
+                                 int *code, char **message);
+int   ld_libs_find_dependencies (ld_libs *ldlibs, int *code, char **message);
+void  ld_libs_finish            (ld_libs *ldlibs);
+int   ld_libs_load_cache        (ld_libs *libs, int *code, char **message);
+
+void *ld_libs_load (ld_libs *ldlibs, Lmid_t *namespace, int flag, int *error,
+                    char **message);
+
diff --git a/subprojects/libcapsule/utils/library-cmp.c b/subprojects/libcapsule/utils/library-cmp.c
new file mode 100644
index 0000000000000000000000000000000000000000..8aeed8024f291e235cfefccdb4214ac9ac2b4601
--- /dev/null
+++ b/subprojects/libcapsule/utils/library-cmp.c
@@ -0,0 +1,1618 @@
+// Copyright © 2020 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "library-cmp.h"
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fnmatch.h>
+#include <search.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <libelf.h>
+#include <gelf.h>
+
+#include "debug.h"
+#include "utils.h"
+
+// From binutils/include/elf/common.h (this doesn't appear to be documented
+// anywhere else).
+//
+// /* This flag appears in a Versym structure.  It means that the symbol
+//    is hidden, and is only visible with an explicit version number.
+//    This is a GNU extension.  */
+// #define VERSYM_HIDDEN 0x8000
+//
+// /* This is the mask for the rest of the Versym information.  */
+// #define VERSYM_VERSION 0x7fff
+
+#define VERSYM_HIDDEN 0x8000
+#define VERSYM_VERSION 0x7fff
+
+static int
+bsearch_strcmp_cb( const void *n, const void *ip )
+{
+    const char *needle = n;
+    const char * const *item_p = ip;
+    return strcmp( needle, *item_p );
+}
+
+static int
+qsort_strcmp_cb( const void* s1, const void* s2 )
+{
+    const char * const *a = (const char* const*) s1;
+    const char* const *b = (const char* const*) s2;
+    return strcmp( *a, *b );
+}
+
+/*
+ * string_set_diff_flags:
+ * @STRING_SET_DIFF_ONLY_IN_FIRST: At least one element is in the first set but not the second
+ * @STRING_SET_DIFF_ONLY_IN_SECOND: At least one element is in the second set but not the first
+ * @STRING_SET_DIFF_NONE: All elements are equal
+ *
+ * The result of comparing two sets of strings. If each set
+ * contains elements that the other does not, then
+ * both @STRING_SET_DIFF_ONLY_IN_FIRST
+ * and @STRING_SET_DIFF_ONLY_IN_SECOND will be set.
+ */
+typedef enum
+{
+  STRING_SET_DIFF_ONLY_IN_FIRST = (1 << 0),
+  STRING_SET_DIFF_ONLY_IN_SECOND = (1 << 1),
+  STRING_SET_DIFF_NONE = 0
+} string_set_diff_flags;
+
+/*
+ * compare_string_sets:
+ * @first: the first set to compare
+ * @first_length: number of elements in the first set
+ * @second: the second set to compare
+ * @second_length: number of elements in the second set
+ *
+ * The two sets needs to be ordered because we will use a binary search to do
+ * the comparison.
+ */
+static string_set_diff_flags
+compare_string_sets ( char **first, size_t first_length,
+                      char **second, size_t second_length )
+{
+    string_set_diff_flags result = STRING_SET_DIFF_NONE;
+
+    assert( first != NULL );
+    assert( second != NULL );
+
+    if( first_length > second_length )
+    {
+        result |= STRING_SET_DIFF_ONLY_IN_FIRST;
+    }
+    else
+    {
+        for( size_t i = 0; i < first_length; i++ )
+        {
+            char *found = bsearch( first[i], second, second_length, sizeof(char *), bsearch_strcmp_cb );
+            if( found == NULL )
+            {
+                result |= STRING_SET_DIFF_ONLY_IN_FIRST;
+                break;
+            }
+        }
+    }
+
+    if( first_length < second_length )
+    {
+        result |= STRING_SET_DIFF_ONLY_IN_SECOND;
+    }
+    else
+    {
+        for( size_t i = 0; i < second_length; i++ )
+        {
+            char *found = bsearch( second[i], first, first_length, sizeof(char *), bsearch_strcmp_cb );
+            if( found == NULL )
+            {
+                result |= STRING_SET_DIFF_ONLY_IN_SECOND;
+                break;
+            }
+        }
+    }
+    return result;
+}
+
+static void
+close_elf (Elf **elfp, int *fdp)
+{
+  if (elfp != NULL && *elfp != NULL)
+    {
+      elf_end( *elfp );
+      *elfp = NULL;
+    }
+
+  if (fdp != NULL && *fdp >= 0)
+    {
+      close( *fdp );
+      *fdp = -1;
+    }
+}
+
+/*
+ * open_elf_library:
+ * @path: (type filename): The path where the library is located
+ * @fd: (out) (not optional): Used to return a file descriptor of the
+ *  opened library
+ * @elf: (out) (not optional): Used to return an initialized elf of the
+ *  library
+ * @code: (out) (optional): Used to return an error code on failure
+ * @message: (out) (optional) (nullable): Used to return an error message
+ *  on failure
+ *
+ * Returns: %TRUE if the elf opening succeded, %FALSE otherwise.
+ */
+static bool
+open_elf_library ( const char *path, int *fd, Elf **elf,
+                   int *code, char **message)
+{
+    GElf_Ehdr ehdr;
+    bool result = true;
+
+    assert( *elf == NULL );
+
+    if( elf_version(EV_CURRENT) == EV_NONE )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "elf_version(EV_CURRENT): %s",
+                            elf_errmsg( elf_errno() ) );
+        result = false;
+        goto out;
+    }
+
+    if( ( *fd = open( path, O_RDONLY | O_CLOEXEC, 0 ) ) < 0 )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "failed to open %s", path );
+        result = false;
+        goto out;
+    }
+
+    if( ( *elf = elf_begin( *fd, ELF_C_READ, NULL ) ) == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "elf_begin() failed: %s",
+                            elf_errmsg( elf_errno() ) );
+        result = false;
+        goto out;
+    }
+
+    if( elf_kind( *elf ) != ELF_K_ELF )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "%s is not in ELF format", path );
+        result = false;
+        goto out;
+    }
+
+    if( gelf_getehdr( *elf, &ehdr ) == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "gelf_getehdr() failed: %s",
+                            elf_errmsg( elf_errno() ) );
+        result = false;
+        goto out;
+    }
+    if( ehdr.e_type != ET_DYN )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "%s is not a shared library, elf type is %d",
+                            path, ehdr.e_type );
+        result = false;
+        goto out;
+    }
+
+out:
+    if( !result )
+        close_elf( elf, fd );
+
+    return result;
+}
+
+static void
+print_debug_string_list( char **list, const char *begin_message )
+{
+    if( list == NULL || list[0] == NULL )
+        return;
+
+    if( begin_message != NULL )
+        DEBUG( DEBUG_ELF, "%s", begin_message );
+
+    for( size_t i = 0; list[i] != NULL; i++ )
+        DEBUG( DEBUG_ELF, "%s", list[i] );
+}
+
+static int
+library_details_cmp( const void *pa, const void *pb )
+{
+    const library_details *a = pa;
+    const library_details *b = pb;
+
+    return strcmp( a->name, b->name );
+}
+
+static void
+library_details_free( library_details *self )
+{
+    free_strv_full( self->public_symbol_versions );
+    free_strv_full( self->public_symbols );
+    free( self->comparators );
+    free( self->name );
+    free( self );
+}
+
+static void
+library_details_free_generic( void *p )
+{
+    library_details_free( p );
+}
+
+void
+library_knowledge_clear( library_knowledge *self )
+{
+    if( self->tree != NULL )
+        tdestroy( self->tree, library_details_free_generic );
+
+    self->tree = NULL;
+}
+
+/*
+ * library_knowledge_lookup:
+ * @self: Details of all known libraries
+ * @library: The name of a library, normally the SONAME
+ *
+ * Returns: Details of @library, or %NULL if nothing is known about it
+ */
+const library_details *
+library_knowledge_lookup( const library_knowledge *self,
+                          const char *library )
+{
+    const library_details key = { (char *) library, NULL };
+    library_details **node;
+
+    node = tfind( &key, &self->tree, library_details_cmp );
+
+    if( node == NULL )
+        return NULL;
+
+    return *node;
+}
+
+/*
+ * library_cmp_split_string_by_delimiters:
+ * @spec: A string of elements separated by @delimiters
+ * @delimiters: Any character from this set separates the elements in @spec
+ *
+ * Parse a string of elements into an array.
+ *
+ * Returns: (transfer full): An array of the elements from @spec
+ */
+static char **
+library_cmp_split_string_by_delimiters( const char *spec,
+                                        const char *delimiters )
+{
+    char **ret = NULL;
+    char *buf = xstrdup( spec );
+    char *saveptr = NULL;
+    char *token;
+    ptr_list *list;
+
+    /* Start with an arbitrary size of 8 */
+    list = ptr_list_alloc( 8 );
+
+    for( token = strtok_r( buf, delimiters, &saveptr );
+         token != NULL;
+         token = strtok_r( NULL, delimiters, &saveptr ) )
+    {
+        if( token[0] == '\0' )
+            continue;
+
+        ptr_list_push_ptr( list, xstrdup( token ) );
+    }
+
+    ret = (char **) ptr_list_free_to_array( _capsule_steal_pointer( &list ), NULL );
+
+    free( buf );
+    return ret;
+}
+
+/*
+ * library_knowledge_load_from_stream:
+ * @stream: A stream
+ * @name: The filename of @stream or a placeholder, for diagnostic messages
+ * @code: (out) (optional): Set to an `errno` value on failure
+ * @message: (out) (optional): Set to an error message on failure
+ *
+ * Load knowledge of libraries from a stream. It is merged with anything
+ * previously known by @self, with the new version preferred (so if there
+ * are multiple files containing library knowledge, they should be loaded
+ * in least-important-first order).
+ *
+ * Returns: true on success
+ */
+bool
+library_knowledge_load_from_stream( library_knowledge *self,
+                                    FILE *stream, const char *name,
+                                    int *code, char **message )
+{
+    library_details *current = NULL;
+    bool in_a_section = false;
+    bool ok = false;
+    int line_number = 0;
+    char *line = NULL;
+    size_t buf_len = 0;
+    ssize_t len;
+
+    while( ( len = getline( &line, &buf_len, stream ) ) >= 0 )
+    {
+        line_number++;
+
+        if( len == 0 || *line == '\0' || *line == '\n' || *line == '#' )
+            continue;
+
+        if( line[len - 1] == '\n' )
+        {
+            line[--len] = '\0';
+        }
+
+        if( *line == '[' )
+        {
+            library_details **node;
+
+            // new section
+            if( line[len - 1] != ']' )
+            {
+                _capsule_set_error( code, message, EINVAL,
+                                    "%s:%d: Invalid section heading \"%s\"",
+                                    name, line_number, line );
+                goto out;
+            }
+
+            // Wipe out the ']'
+            line[--len] = '\0';
+
+            if( strstarts( line, "[Library " ) )
+            {
+                current = xcalloc( 1, sizeof( library_details ) );
+                current->name = xstrdup( line + strlen( "[Library " ) );
+                current->comparators = NULL;
+
+                node = tsearch( current, &self->tree, library_details_cmp );
+
+                if( node == NULL )
+                {
+                    oom();
+                }
+                else if( *node != current )
+                {
+                    // we have seen this one before: keep the existing version
+                    library_details_free( current );
+                    current = *node;
+                }
+                // else self->tree takes ownership of current
+            }
+            // TODO: Future expansion: we could have glob matches if we
+            // want them, for example [Match libGLX_*.so.0]
+            else
+            {
+                DEBUG( DEBUG_TOOL, "Ignoring unknown section heading \"%s\"", line + 1 );
+                current = NULL;
+            }
+
+            in_a_section = true;
+        }
+        else if( !in_a_section )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "%s:%d: Unexpected line not in a section: \"%s\"",
+                                name, line_number, line );
+           goto out;
+        }
+        else if( current != NULL && strstarts( line, "CompareBy=" ) )
+        {
+            char *values = line + strlen( "CompareBy=" );
+
+            free( current->comparators );
+            current->comparators = library_cmp_list_from_string( values, ";",
+                                                                 code, message );
+
+            if( current->comparators == NULL )
+            {
+                if( message != NULL )
+                {
+                    // Prepend file:line: before failing
+                    char *tmp = *message;
+
+                    *message = NULL;
+
+                    if( asprintf( message, "%s:%d: %s", name, line_number, tmp ) < 0 )
+                        oom();
+
+                    free( tmp );
+                }
+
+                goto out;
+            }
+        }
+        else if( current != NULL && strstarts( line, "PublicSymbolVersions=" ) )
+        {
+            char *values = line + strlen( "PublicSymbolVersions=" );
+            free_strv_full( current->public_symbol_versions );
+
+            current->public_symbol_versions = library_cmp_split_string_by_delimiters( values,
+                                                                                      ";" );
+        }
+        else if( current != NULL && strstarts( line, "PublicSymbols=" ) )
+        {
+            char *values = line + strlen( "PublicSymbols=" );
+            free_strv_full( current->public_symbols );
+
+            current->public_symbols = library_cmp_split_string_by_delimiters( values,
+                                                                              ";" );
+        }
+        else if( strchr( line, '=' ) != NULL )
+        {
+            DEBUG( DEBUG_TOOL, "%s:%d: Ignoring unknown key/value pair \"%s\"",
+                   name, line_number, line );
+        }
+        else
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "%s:%d: Unexpected line not a key/value pair: \"%s\"",
+                                name, line_number, line );
+            goto out;
+        }
+    }
+
+    ok = true;
+
+out:
+    free( line );
+
+    if( ok )
+    {
+        DEBUG( DEBUG_TOOL, "Loaded library knowledge from \"%s\"", name );
+    }
+    else
+    {
+        DEBUG( DEBUG_TOOL, "Failed to load library knowledge from \"%s\"", name );
+    }
+
+    return ok;
+}
+
+/*
+ * get_versions:
+ * @elf: The object's elf of which we want to get the versions
+ * @versions_number: (out) (not optional): The number of versions found
+ * @code: (out) (optional): Used to return an error code on
+ *  failure
+ * @message: (out) (optional) (nullable): Used to return an error message
+ *  on failure
+ *
+ * Returns: (transfer full): The list of versions that the
+ *  shared object has, on failure %NULL.
+ */
+static char **
+get_versions( Elf *elf, size_t *versions_number, int *code, char **message )
+{
+    char **versions = NULL;
+    Elf_Scn *scn = NULL;
+    Elf_Data *data;
+    GElf_Shdr shdr_mem;
+    GElf_Shdr *shdr = NULL;
+    GElf_Verdef def_mem;
+    GElf_Verdef *def;
+    bool found_verdef = false;
+    uintptr_t verdef_ptr = 0;
+    size_t auxoffset;
+    size_t offset = 0;
+    size_t phnum;
+    size_t sh_entsize;
+    ptr_list *versions_list = NULL;
+
+    assert( versions_number != NULL );
+
+    *versions_number = 0;
+
+    if( elf_getphdrnum( elf, &phnum ) < 0 )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Unable to determine the number of program headers: %s",
+                            elf_errmsg( elf_errno() ) );
+        return versions;
+    }
+
+    /* Get the dynamic section */
+    for( size_t i = 0; i < phnum; i++ )
+    {
+        GElf_Phdr phdr_mem;
+        GElf_Phdr *phdr = gelf_getphdr( elf, i, &phdr_mem );
+        if( phdr != NULL && phdr->p_type == PT_DYNAMIC )
+        {
+            scn = gelf_offscn( elf, phdr->p_offset );
+            shdr = gelf_getshdr( scn, &shdr_mem );
+            break;
+        }
+    }
+
+    if( shdr == NULL )
+    {
+        int err = elf_errno();
+        if( err == 0 )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "Unable to find the section header from the dynamic section" );
+        }
+        else
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "Unable to get the section header: %s",
+                                elf_errmsg( err ) );
+        }
+
+        return versions;
+    }
+
+    data = elf_getdata( scn, NULL );
+    if( data == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Unable to get the dynamic section data: %s",
+                            elf_errmsg( elf_errno() ) );
+        return versions;
+    }
+
+    sh_entsize = gelf_fsize( elf, ELF_T_DYN, 1, EV_CURRENT );
+    for( size_t i = 0; i < shdr->sh_size / sh_entsize; i++ )
+    {
+        GElf_Dyn dyn_mem;
+        GElf_Dyn *dyn = gelf_getdyn( data, i, &dyn_mem );
+        if( dyn == NULL )
+            break;
+
+        if( dyn->d_tag == DT_VERDEF )
+        {
+            verdef_ptr = dyn->d_un.d_ptr;
+            found_verdef = true;
+            break;
+        }
+    }
+
+    if( !found_verdef )
+    {
+        DEBUG( DEBUG_ELF, "The version definition table is not available" );
+        versions = calloc( 1, sizeof(char *) );
+        versions[0] = NULL;
+        return versions;
+    }
+    scn = gelf_offscn( elf, verdef_ptr );
+    data = elf_getdata( scn, NULL );
+    if( data == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Unable to get symbols data: %s", elf_errmsg( elf_errno() ) );
+        return versions;
+    }
+
+    def = gelf_getverdef( data, 0, &def_mem );
+    if( def == NULL )
+    {
+        DEBUG( DEBUG_ELF, "Verdef is not available: %s", elf_errmsg( elf_errno() ) );
+        versions = calloc( 1, sizeof(char *) );
+        versions[0] = NULL;
+        return versions;
+    }
+
+    /* Arbitrarily start the list with 8 elements */
+    versions_list = ptr_list_alloc( 8 );
+    while( def != NULL )
+    {
+        GElf_Verdaux aux_mem, *aux;
+        const char *version;
+
+        auxoffset = offset + def->vd_aux;
+        offset += def->vd_next;
+
+        /* The first Verdaux array must exist and it points to the version
+         * definition string that Verdef defines. Every possible additional
+         * Verdaux arrays are the dependencies of said version definition.
+         * In our case we don't need to list the dependencies, so we just
+         * get the first Verdaux of every Verdef. */
+        aux = gelf_getverdaux( data, auxoffset, &aux_mem );
+        if( aux == NULL )
+            continue;
+        version = elf_strptr( elf, shdr->sh_link, aux->vda_name );
+        if( version == NULL )
+            continue;
+
+        if( ( def->vd_flags & VER_FLG_BASE ) == 0 )
+            ptr_list_push_ptr( versions_list, strdup( version ) );
+
+        if( def->vd_next == 0 )
+            def = NULL;
+        else
+            def = gelf_getverdef( data, offset, &def_mem );
+
+    }
+
+    versions = (char **) ptr_list_free_to_array ( versions_list, versions_number );
+    qsort( versions, *versions_number, sizeof(char *), qsort_strcmp_cb );
+    return versions;
+}
+
+static const char * const ignore_symbols[] =
+{
+    /* Libraries on at least SteamOS 2 'brewmaster' sometimes have
+     * symbols that appear to have an empty name. */
+    "",
+
+    /* These symbols can appear in libraries witout actually being part
+     * of anyone's ABI. List taken from dpkg-gensymbols. */
+    "__bss_end__",
+    "__bss_end",
+    "_bss_end__",
+    "__bss_start",
+    "__bss_start__",
+    "__data_start",
+    "__do_global_ctors_aux",
+    "__do_global_dtors_aux",
+    "__do_jv_register_classes",
+    "_DYNAMIC",
+    "_edata",
+    "_end",
+    "__end__",
+    "__exidx_end",
+    "__exidx_start",
+    "_fbss",
+    "_fdata",
+    "_fini",
+    "_ftext",
+    "_GLOBAL_OFFSET_TABLE_",
+    "__gmon_start__",
+    "__gnu_local_gp",
+    "_gp",
+    "_init",
+    "_PROCEDURE_LINKAGE_TABLE_",
+    "_SDA2_BASE_",
+    "_SDA_BASE_",
+};
+
+/*
+ * get_symbols:
+ * @elf: The object's elf of which we want to get the symbols
+ * @symbols_number: (out) (not optional): The number of symbols found
+ * @code: (out) (optional): Used to return an error code on
+ *  failure
+ * @message: (out) (optional) (nullable): Used to return an error message
+ *  on failure
+ *
+ * Returns: (transfer full): The list of symbols that the
+ *  shared object has, on failure %NULL.
+ */
+static char **
+get_symbols ( Elf *elf, size_t *symbols_number, int *code, char **message )
+{
+    char **symbols = NULL;
+    Elf_Scn *scn = NULL;
+    Elf_Scn *scn_sym = NULL;
+    Elf_Scn *scn_ver = NULL;
+    Elf_Scn *scn_verdef = NULL;
+    Elf_Data *data;
+    Elf_Data *sym_data;
+    Elf_Data *versym_data = NULL;
+    Elf_Data *verdef_data = NULL;
+    GElf_Ehdr ehdr;
+    GElf_Shdr shdr_mem;
+    GElf_Shdr *shdr = NULL;
+    bool found_symtab = false;
+    bool found_versym = false;
+    bool found_verdef = false;
+    uintptr_t symtab_ptr = 0;
+    uintptr_t versym_ptr = 0;
+    uintptr_t verdef_ptr = 0;
+    size_t elsize = 0;
+    size_t phnum;
+    size_t sh_entsize;
+    ptr_list *symbols_list = NULL;
+
+    assert( symbols_number != NULL );
+
+    *symbols_number = 0;
+
+    if( elf_getphdrnum( elf, &phnum ) < 0 )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Unable to determine the number of program headers: %s",
+                            elf_errmsg( elf_errno() ) );
+        return symbols;
+    }
+
+    /* Get the dynamic section */
+    for( size_t i = 0; i < phnum; i++ )
+    {
+        GElf_Phdr phdr_mem;
+        GElf_Phdr *phdr = gelf_getphdr( elf, i, &phdr_mem );
+        if( phdr != NULL && phdr->p_type == PT_DYNAMIC )
+        {
+            scn = gelf_offscn( elf, phdr->p_offset );
+            shdr = gelf_getshdr( scn, &shdr_mem );
+            break;
+        }
+    }
+
+    if( shdr == NULL )
+    {
+        int err = elf_errno();
+        if( err == 0 )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "Unable to find the section header from the dynamic section" );
+        }
+        else
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "Unable to get the section header: %s",
+                                elf_errmsg( err ) );
+        }
+
+        return symbols;
+    }
+
+    data = elf_getdata( scn, NULL );
+    if( data == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Unable to get dynamic section data: %s", elf_errmsg( elf_errno() ) );
+        return symbols;
+    }
+
+    sh_entsize = gelf_fsize( elf, ELF_T_DYN, 1, EV_CURRENT );
+
+    for( size_t i = 0; i < shdr->sh_size / sh_entsize; i++ )
+    {
+        GElf_Dyn dyn_mem;
+        GElf_Dyn *dyn = gelf_getdyn (data, i, &dyn_mem);
+        if( dyn == NULL )
+            break;
+
+        switch( dyn->d_tag )
+        {
+            case DT_SYMTAB:
+                symtab_ptr = dyn->d_un.d_ptr;
+                found_symtab = true;
+                break;
+            case DT_VERSYM:
+                versym_ptr = dyn->d_un.d_ptr;
+                found_versym = true;
+                break;
+            case DT_VERDEF:
+                verdef_ptr = dyn->d_un.d_ptr;
+                found_verdef = true;
+                break;
+            default:
+                break;
+        }
+    }
+
+
+    if( !found_symtab )
+    {
+        _capsule_set_error( code, message, EINVAL, "Unable to find the symbols table" );
+        return symbols;
+    }
+    scn_sym = gelf_offscn( elf, symtab_ptr );
+    sym_data = elf_getdata( scn_sym, NULL );
+    if( sym_data == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Unable to get symbols table data: %s", elf_errmsg( elf_errno() ) );
+        return symbols;
+    }
+
+    if( found_versym )
+    {
+        scn_ver = gelf_offscn( elf, versym_ptr );
+        versym_data = elf_getdata( scn_ver, NULL );
+        if( versym_data == NULL )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "Unable to get symbols version information data: %s",
+                                elf_errmsg( elf_errno() ) );
+            return symbols;
+        }
+    }
+
+    if( found_verdef )
+    {
+        scn_verdef = gelf_offscn( elf, verdef_ptr );
+        verdef_data = elf_getdata( scn_verdef, NULL );
+        if( verdef_data == NULL )
+        {
+            _capsule_set_error( code, message, EINVAL,
+                                "Unable to get symbols version definition data: %s",
+                                elf_errmsg( elf_errno() ) );
+            return symbols;
+        }
+    }
+
+    if( gelf_getehdr( elf, &ehdr ) == NULL )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Unable to retrieve Ehdr header: %s",
+                            elf_errmsg( elf_errno() ) );
+        return symbols;
+    }
+
+    elsize = gelf_fsize( elf, ELF_T_SYM, 1, ehdr.e_version );
+    if( elsize == 0 )
+    {
+        _capsule_set_error( code, message, EINVAL,
+                            "Size of symbols in Ehdr array is zero: %s",
+                            elf_errmsg( elf_errno() ) );
+        return symbols;
+    }
+
+    /* Arbitrarily start the list with 8 elements */
+    symbols_list = ptr_list_alloc( 8 );
+
+    for( size_t index = 0; index < sym_data->d_size / elsize; index++ )
+    {
+        GElf_Sym *sym;
+        GElf_Sym sym_mem;
+        const char *symbol;
+        GElf_Versym versym_mem;
+        GElf_Versym *versym;
+        GElf_Verdaux aux_mem;
+        GElf_Verdaux *aux = NULL;
+        GElf_Verdef def_mem;
+        GElf_Verdef *def = NULL;
+        bool interesting = true;
+
+        sym = gelf_getsymshndx( sym_data, NULL, index, &sym_mem, NULL );
+
+        /* If the symbol info is not available, or undefined, we skip it */
+        if( sym == NULL || sym->st_shndx == SHN_UNDEF )
+            continue;
+
+        symbol = elf_strptr( elf, shdr->sh_link, sym->st_name );
+        if( symbol == NULL )
+            break;
+
+        for( size_t i = 0; i < N_ELEMENTS( ignore_symbols ); i++ )
+        {
+            if( strcmp( symbol, ignore_symbols[i] ) == 0 )
+            {
+                interesting = false;
+                break;
+            }
+        }
+
+        if ( !interesting )
+        {
+            DEBUG( DEBUG_ELF, "Symbol '%s' is uninteresting", symbol );
+            continue;
+        }
+
+        /* Search the version of the symbol */
+        if( found_versym && found_verdef )
+        {
+            versym = gelf_getversym( versym_data, index, &versym_mem );
+            def = gelf_getverdef( verdef_data, 0, &def_mem );
+
+            size_t auxoffset;
+            size_t offset = 0;
+            while( def != NULL )
+            {
+                auxoffset = offset + def->vd_aux;
+                offset += def->vd_next;
+
+                /* The first Verdaux array must exist and it points to the version
+                * definition string that Verdef defines. Every possible additional
+                * Verdaux arrays are the dependencies of said version definition.
+                * In our case we don't need to list the dependencies, so we just
+                * get the first Verdaux of every Verdef. */
+                aux = gelf_getverdaux( verdef_data, auxoffset, &aux_mem );
+                if( aux == NULL )
+                    continue;
+
+                /* Break if we have found the symbol's version */
+                if( def->vd_ndx == ( *versym & VERSYM_VERSION ) )
+                    break;
+
+                if( def->vd_next == 0 )
+                    def = NULL;
+                else
+                    def = gelf_getverdef( verdef_data, offset, &def_mem );
+            }
+        }
+
+        /* If the symbol is versioned */
+        if( def != NULL && aux != NULL )
+        {
+            const char *version = elf_strptr( elf, shdr->sh_link, aux->vda_name );
+            char *symbol_versioned;
+            xasprintf( &symbol_versioned, "%s@%s", symbol, version );
+            ptr_list_push_ptr( symbols_list, symbol_versioned );
+        }
+        else
+        {
+            ptr_list_push_ptr( symbols_list, strdup( symbol ) );
+        }
+    }
+
+    symbols = (char **) ptr_list_free_to_array ( symbols_list, symbols_number );
+    qsort( symbols, *symbols_number, sizeof(char *), qsort_strcmp_cb );
+    return symbols;
+}
+
+/*
+ * library_cmp_by_name:
+ * @details: The library we are interested in and how to compare it
+ * @left_path: The path to the "left" instance of the library
+ * @left_from: Arbitrary description of the container/provider/sysroot
+ *  where we found @left_path, used in debug logging
+ * @right_path: The path to the "right" instance of the library
+ * @right_from: Arbitrary description of the container/provider/sysroot
+ *  where we found @right_path, used in debug logging
+ *
+ * Attempt to determine whether @left_path is older than, newer than or
+ * the same as than @right_path by inspecting their filenames.
+ *
+ * Return a strcmp-style result: negative if left < right,
+ * positive if left > right, zero if left == right or if left and right
+ * are non-comparable.
+ */
+static int
+library_cmp_by_name( const library_details *details,
+                     const char *left_path,
+                     const char *left_from,
+                     const char *right_path,
+                     const char *right_from )
+{
+    _capsule_autofree char *left_realpath = NULL;
+    _capsule_autofree char *right_realpath = NULL;
+    const char *left_basename;
+    const char *right_basename;
+
+    // This might look redundant when our arguments come from the ld_libs,
+    // but resolve_symlink_prefixed() doesn't chase symlinks if the
+    // prefix is '/' or empty.
+    left_realpath = realpath( left_path, NULL );
+    right_realpath = realpath( right_path, NULL );
+    left_basename = _capsule_basename( left_realpath );
+    right_basename = _capsule_basename( right_realpath );
+
+    DEBUG( DEBUG_TOOL,
+           "Comparing %s \"%s\" from \"%s\" with "
+           "\"%s\" from \"%s\"",
+           details->name, left_basename, left_from, right_basename, right_from );
+
+    if( strcmp( left_basename, right_basename ) == 0 )
+    {
+        DEBUG( DEBUG_TOOL,
+               "Name of %s \"%s\" from \"%s\" compares the same as "
+               "\"%s\" from \"%s\"",
+               details->name, left_basename, left_from, right_basename, right_from );
+        return 0;
+    }
+
+    if( strcmp( details->name, left_basename ) == 0 )
+    {
+        /* In some distributions (Debian, Ubuntu, Manjaro)
+         * libgcc_s.so.1 is a plain file, not a symlink to a
+         * version-suffixed version. We cannot know just from the name
+         * whether that's older or newer, so assume equal. The caller is
+         * responsible for figuring out which one to prefer. */
+        DEBUG( DEBUG_TOOL,
+               "Unversioned %s \"%s\" from \"%s\" cannot be compared with "
+               "\"%s\" from \"%s\"",
+               details->name, left_basename, left_from,
+               right_basename, right_from );
+        return 0;
+    }
+
+    if( strcmp( details->name, right_basename ) == 0 )
+    {
+        /* The same, but the other way round */
+        DEBUG( DEBUG_TOOL,
+               "%s \"%s\" from \"%s\" cannot be compared with "
+               "unversioned \"%s\" from \"%s\"",
+               details->name, left_basename, left_from,
+               right_basename, right_from );
+        return 0;
+    }
+
+    return ( strverscmp( left_basename, right_basename ) );
+}
+
+/*
+ * library_cmp_filter_list:
+ * @filters: The list of patterns that will be checked against @list.
+ *  Patterns that start with '!' are considered negated (privates),
+ *  i.e. the elements in @list, that matches said pattern, will be removed.
+ *  A pattern that is just '!' is used to separate what's known to the
+ *  guessing.
+ * @list: (array zero-terminated=1): The list to filter
+ * @list_number: The number of elements in @list excluding the %NULL
+ *  terminator
+ * @filtered_list_number_out: (out): Used to return the number of elements in
+ *  the newly created filtered list excluding the %NULL terminator
+ *
+ * Creates a new filtered list starting from the given @list and applying the
+ * patterns in @filters.
+ * The patterns are evaluated in order.
+ * If there are elements that don't match any of the provided filters, or they
+ * match a filter after the special '!' pattern, a warning will be printed.
+ * The default filter behavior for elements that don't match any patterns
+ * is to exclude them (treat as private). However it is higly recommended
+ * to be explicit and end @filters with a wildcard allow everything "*",
+ * or reject everything "!*".
+ *
+ * Returns: (array zero-terminated=1) (transfer full): An array with
+ *  the elements that matches the provided @filters.
+ */
+static char **
+library_cmp_filter_list( char ** const filters,
+                         char ** const list,
+                         size_t list_number,
+                         size_t *filtered_list_number_out )
+{
+    ptr_list *filtered_list;
+    size_t i = 0;
+    size_t j = 0;
+    bool guessing = false;
+    char *buf = NULL;
+    char *token;
+    filtered_list = ptr_list_alloc( list_number );
+
+    assert( filters != NULL );
+    assert( list != NULL );
+    assert( filtered_list_number_out != NULL );
+
+    *filtered_list_number_out = 0;
+
+    for( i = 0; list[i] != NULL; i++ )
+    {
+        char *saveptr = NULL;
+        guessing = false;
+
+        free( buf );
+        buf = xstrdup( list[i] );
+        /* If we have a versioned symbol, like "symbol@version", remove the
+         * version part because the filters are just for the symbols name. */
+        token = strtok_r( buf, "@", &saveptr );
+
+        for( j = 0; filters[j] != NULL; j++ )
+        {
+            if( strcmp( filters[j], "!" ) == 0 )
+            {
+                DEBUG( DEBUG_TOOL, "After this point we are just guessing" );
+                guessing = true;
+                continue;
+            }
+
+            if( filters[j][0] == '!' )
+            {
+                if( fnmatch( filters[j] + 1, token, 0 ) == 0 )
+                {
+                    if( guessing )
+                        warnx( "warning: we are assuming \"%s\" to be private, but it's just a guess",
+                               token );
+                    else
+                        DEBUG( DEBUG_TOOL,
+                               "Ignoring \"%s\" because it has been declared as private",
+                               token );
+                    break;
+                }
+            }
+            else
+            {
+                if( fnmatch( filters[j], token, 0 ) == 0 )
+                {
+                    if( guessing )
+                        warnx( "warning: we are assuming \"%s\" to be public, but it's just a guess",
+                               token );
+
+                    ptr_list_push_ptr( filtered_list, xstrdup( list[i] ) );
+                    break;
+                }
+            }
+        }
+        /* If we checked all the patterns and didn't have a match */
+        if( filters[j] == NULL )
+            warnx( "warning: \"%s\" does not have a match in the given filters, treating it as private",
+                   token );
+    }
+
+    free( buf );
+
+    return (char **) ptr_list_free_to_array( _capsule_steal_pointer( &filtered_list ),
+                                            filtered_list_number_out );
+}
+
+/*
+ * library_cmp_by_symbols:
+ * @details: The library we are interested in and how to compare it
+ * @container_path: (type filename): The path where the container's soname is
+ *  located
+ * @provider_path: (type filename): The path where the provider's soname is
+ *  located
+ *
+ * Attempt to determine whether soname is older, newer or
+ * the same in the container or the provider inspecting their
+ * symbols.
+ *
+ * Return a strcmp-style result: negative if container < provider,
+ * positive if container > provider, zero if container == provider
+ * or if container and provider are non-comparable.
+ */
+static int
+library_cmp_by_symbols( const library_details *details,
+                        const char *container_path,
+                        const char *container_root,
+                        const char *provider_path,
+                        const char *provider_root )
+{
+    string_set_diff_flags symbol_result = STRING_SET_DIFF_NONE;
+    int cmp_result = 0;
+    int container_fd = -1;
+    int provider_fd = -1;
+    Elf *container_elf = NULL;
+    Elf *provider_elf = NULL;
+    char **container_symbols = NULL;
+    char **provider_symbols = NULL;
+    size_t container_symbols_number = 0;
+    size_t provider_symbols_number = 0;
+    int code = 0;
+    char *message = NULL;
+    char *symbol_message = NULL;
+
+    if( !open_elf_library( container_path, &container_fd, &container_elf,
+                           &code, &message ) )
+    {
+        DEBUG( DEBUG_TOOL,
+               "an error occurred while opening %s (%d): %s",
+               container_path, code, message );
+        goto out;
+    }
+
+    container_symbols = get_symbols( container_elf, &container_symbols_number,
+                                     &code, &message );
+
+    if( container_symbols == NULL )
+    {
+        warnx( "failed to get container versions for %s (%d): %s",
+               details->name, code, message );
+        goto out;
+    }
+
+    if( details->public_symbols != NULL )
+    {
+        char **container_symbols_filtered;
+        size_t container_symbols_number_filtered = 0;
+        container_symbols_filtered = library_cmp_filter_list( details->public_symbols,
+                                                              container_symbols,
+                                                              container_symbols_number,
+                                                              &container_symbols_number_filtered );
+        free_strv_full( container_symbols );
+
+        container_symbols = container_symbols_filtered;
+        container_symbols_number = container_symbols_number_filtered;
+    }
+
+    xasprintf( &symbol_message, "Container Symbols of %s:", details->name );
+    print_debug_string_list( container_symbols, symbol_message );
+    _capsule_clear( &symbol_message );
+
+    if( !open_elf_library( provider_path, &provider_fd, &provider_elf,
+                           &code, &message ) )
+    {
+        DEBUG( DEBUG_TOOL,
+               "an error occurred while opening %s (%d): %s",
+               container_path, code, message );
+        goto out;
+    }
+
+    provider_symbols = get_symbols( provider_elf, &provider_symbols_number,
+                                       &code, &message );
+
+    if( provider_symbols == NULL )
+    {
+        warnx( "failed to get provider versions for %s (%d): %s",
+               details->name, code, message );
+        goto out;
+    }
+
+    if( details->public_symbols != NULL )
+    {
+        char **provider_symbols_filtered;
+        size_t provider_symbols_number_filtered = 0;
+        provider_symbols_filtered = library_cmp_filter_list( details->public_symbols,
+                                                             provider_symbols,
+                                                             provider_symbols_number,
+                                                             &provider_symbols_number_filtered );
+        free_strv_full( provider_symbols );
+
+        provider_symbols = provider_symbols_filtered;
+        provider_symbols_number = provider_symbols_number_filtered;
+    }
+
+    xasprintf( &symbol_message, "Provider Symbols of %s:", details->name );
+    print_debug_string_list( provider_symbols, symbol_message );
+    _capsule_clear( &symbol_message );
+
+    symbol_result = compare_string_sets( container_symbols, container_symbols_number,
+                                         provider_symbols, provider_symbols_number );
+
+    /* In container we have strictly more symbols: don't symlink the one
+     * from the provider */
+    if( symbol_result == STRING_SET_DIFF_ONLY_IN_FIRST )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container is newer because its symbols are a strict superset",
+               details->name );
+        cmp_result = 1;
+    }
+    /* In provider we have strictly more symbols: create the symlink */
+    else if( symbol_result == STRING_SET_DIFF_ONLY_IN_SECOND )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the provider is newer because its symbols are a strict superset",
+               details->name );
+        cmp_result = -1;
+    }
+    /* With the following two cases we are still unsure which library is newer, so we
+     * will choose the provider */
+    else if( symbol_result == STRING_SET_DIFF_NONE )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container and the provider have the same symbols",
+               details->name );
+    }
+    else
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container and the provider have different symbols and neither is a superset of the other",
+               details->name );
+    }
+
+out:
+    _capsule_clear( &message );
+    free_strv_full( container_symbols );
+    free_strv_full( provider_symbols );
+    close_elf( &container_elf, &container_fd );
+    close_elf( &provider_elf, &provider_fd );
+    return cmp_result;
+
+}
+
+/*
+ * library_cmp_by_versions:
+ * @details: The library we are interested in and how to compare it
+ * @container_path: (type filename): The path where the container's soname is
+ *  located
+ * @provider_path: (type filename): The path where the provider's soname is
+ *  located
+ *
+ * Attempt to determine whether soname is older, newer or
+ * the same in the container or the provider inspecting their
+ * symbols versions.
+ *
+ * Return a strcmp-style result: negative if container < provider,
+ * positive if container > provider, zero if container == provider
+ * or if container and provider are non-comparable.
+ */
+static int
+library_cmp_by_versions( const library_details *details,
+                         const char *container_path,
+                         const char *container_root,
+                         const char *provider_path,
+                         const char *provider_root )
+{
+    string_set_diff_flags version_result = STRING_SET_DIFF_NONE;
+    int cmp_result = 0;
+    int container_fd = -1;
+    int provider_fd = -1;
+    Elf *container_elf = NULL;
+    Elf *provider_elf = NULL;
+    char **container_versions = NULL;
+    char **provider_versions = NULL;
+    size_t container_versions_number = 0;
+    size_t provider_versions_number = 0;
+    int code = 0;
+    char *message = NULL;
+    char *version_message;
+
+    if( !open_elf_library( container_path, &container_fd, &container_elf,
+                           &code, &message ) )
+    {
+        DEBUG( DEBUG_TOOL,
+               "an error occurred while opening %s (%d): %s",
+               container_path, code, message );
+        goto out;
+    }
+
+    container_versions = get_versions( container_elf, &container_versions_number,
+                                       &code, &message );
+
+    if( container_versions == NULL )
+    {
+        warnx( "failed to get container versions for %s (%d): %s",
+               details->name, code, message );
+        goto out;
+    }
+
+    if( details->public_symbol_versions != NULL )
+    {
+        char **container_versions_filtered;
+        size_t container_versions_number_filtered = 0;
+        container_versions_filtered = library_cmp_filter_list( details->public_symbol_versions,
+                                                               container_versions,
+                                                               container_versions_number,
+                                                               &container_versions_number_filtered );
+        free_strv_full( container_versions );
+
+        container_versions = container_versions_filtered;
+        container_versions_number = container_versions_number_filtered;
+    }
+
+    xasprintf( &version_message, "Container versions of %s:", details->name);
+    print_debug_string_list( container_versions, version_message );
+    _capsule_clear( &version_message );
+
+    if( !open_elf_library( provider_path, &provider_fd, &provider_elf,
+                           &code, &message ) )
+    {
+        DEBUG( DEBUG_TOOL,
+               "an error occurred while opening %s (%d): %s",
+               container_path, code, message );
+        goto out;
+    }
+
+    provider_versions = get_versions( provider_elf, &provider_versions_number,
+                                      &code, &message );
+
+    if( provider_versions == NULL )
+    {
+        warnx( "failed to get provider versions for %s (%d): %s",
+               details->name, code, message );
+        goto out;
+    }
+
+    if( details->public_symbol_versions != NULL )
+    {
+        char **provider_versions_filtered;
+        size_t provider_versions_number_filtered = 0;
+        provider_versions_filtered = library_cmp_filter_list( details->public_symbol_versions,
+                                                              provider_versions,
+                                                              provider_versions_number,
+                                                              &provider_versions_number_filtered );
+        free_strv_full( provider_versions );
+
+        provider_versions = provider_versions_filtered;
+        provider_versions_number = provider_versions_number_filtered;
+    }
+
+    xasprintf( &version_message, "Provider versions of %s:", details->name);
+    print_debug_string_list( provider_versions, version_message );
+    _capsule_clear( &version_message );
+
+    version_result = compare_string_sets( container_versions, container_versions_number,
+                                          provider_versions, provider_versions_number );
+
+    /* Version in container is strictly newer: don't symlink the one
+     * from the provider */
+    if( version_result == STRING_SET_DIFF_ONLY_IN_FIRST )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container is newer because its version definitions are a strict superset",
+               details->name );
+        cmp_result = 1;
+    }
+    /* Version in the provider is strictly newer: create the symlink */
+    else if( version_result == STRING_SET_DIFF_ONLY_IN_SECOND )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the provider is newer because its version definitions are a strict superset",
+               details->name );
+        cmp_result = -1;
+    }
+    /* With the following two cases we are still unsure which library is newer */
+    else if( version_result == STRING_SET_DIFF_NONE )
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container and the provider have the same symbol versions",
+               details->name );
+    }
+    else
+    {
+        DEBUG( DEBUG_TOOL,
+               "%s in the container and the provider have different symbol versions and neither is a superset of the other",
+               details->name );
+    }
+
+out:
+    _capsule_clear( &message );
+    free_strv_full( container_versions );
+    free_strv_full( provider_versions );
+    close_elf( &container_elf, &container_fd );
+    close_elf( &provider_elf, &provider_fd );
+    return cmp_result;
+}
+
+static int
+library_cmp_choose_container( const library_details *details,
+                              const char *container_path,
+                              const char *container_root,
+                              const char *provider_path,
+                              const char *provider_root )
+{
+    DEBUG( DEBUG_TOOL,
+           "Choosing %s from container \"%s\", ignoring provider \"%s\"",
+           details->name, container_root, provider_root );
+    return 1;
+}
+
+static int
+library_cmp_choose_provider( const library_details *details,
+                             const char *container_path,
+                             const char *container_root,
+                             const char *provider_path,
+                             const char *provider_root )
+{
+    DEBUG( DEBUG_TOOL,
+           "Choosing %s from provider \"%s\", ignoring container \"%s\"",
+           details->name, provider_root, container_root );
+    return -1;
+}
+
+typedef struct
+{
+    const char *name;
+    library_cmp_function comparator;
+} named_comparator;
+
+static const named_comparator named_comparators[] =
+{
+    { "name", library_cmp_by_name },
+    { "symbols", library_cmp_by_symbols },
+    { "versions", library_cmp_by_versions },
+    { "container", library_cmp_choose_container },
+    { "provider", library_cmp_choose_provider },
+};
+
+/*
+ * library_cmp_list_from_string:
+ * @spec: A string of tokens separated by @delimiters
+ * @delimiters: Any character from this set separates tokens
+ * @code: (out) (optional): Set to an errno value on error
+ * @message: (out) (optional): Set to a string error message on error
+ *
+ * Parse a list of comparators into an array of functions.
+ *
+ * Returns: (free-function free): An array of comparators
+ */
+library_cmp_function *
+library_cmp_list_from_string( const char *spec,
+                              const char *delimiters,
+                              int *code,
+                              char **message )
+{
+    library_cmp_function *ret = NULL;
+    char *buf = xstrdup( spec );
+    char *saveptr = NULL;
+    char *token;
+    ptr_list *list;
+
+    static_assert( sizeof( void * ) == sizeof( library_cmp_function ),
+                   "Function pointer assumed to be same size as void *");
+    static_assert( alignof( void * ) == alignof( library_cmp_function ),
+                   "Function pointer assumed to be same alignment as void *");
+
+    list = ptr_list_alloc( N_ELEMENTS( named_comparators ) );
+
+    for( token = strtok_r( buf, delimiters, &saveptr );
+         token != NULL;
+         token = strtok_r( NULL, delimiters, &saveptr ) )
+    {
+        library_cmp_function comparator = NULL;
+        size_t i;
+
+        if( token[0] == '\0' )
+            continue;
+
+        for( i = 0; i < N_ELEMENTS( named_comparators ); i++ )
+        {
+            if( strcmp( token, named_comparators[i].name ) == 0 )
+            {
+                comparator = named_comparators[i].comparator;
+                break;
+            }
+        }
+
+        if( comparator == NULL )
+        {
+            _capsule_set_error( code, message,
+                                EINVAL, "Unknown library comparison mode \"%s\"",
+                                token );
+            goto out;
+        }
+
+        ptr_list_push_ptr( list, (void *) comparator );
+    }
+
+    ret = (library_cmp_function *) ptr_list_free_to_array( _capsule_steal_pointer( &list ), NULL );
+
+out:
+    if( list != NULL )
+        ptr_list_free( list );
+
+    free( buf );
+    return ret;
+}
+
+/*
+ * library_cmp_list_iterate:
+ * @details: The library we are interersted in and how to compare it
+ * @container_path: The path to the library in the container
+ * @container_root: The path to the top-level directory of the container
+ * @provider_path: The path to the library in the provider
+ * @provider_root: The path to the top-level directory of the provider
+ *
+ * Iterate through a %NULL-terminated array of comparators from the given
+ * @details, highest-precedence first, calling each one in turn until one of
+ * them returns a nonzero value. If none of them return nonzero, return 0.
+ *
+ * Returns: Negative if the container version appears newer, zero if they
+ *  appear the same or we cannot tell, or positive if the provider version
+ *  appears newer.
+ */
+int
+library_cmp_list_iterate( const library_details *details,
+                          const char *container_path,
+                          const char *container_root,
+                          const char *provider_path,
+                          const char *provider_root )
+{
+    const library_cmp_function *iter;
+
+    assert( details != NULL );
+
+    for( iter = details->comparators; iter != NULL && *iter != NULL; iter++ )
+    {
+        int decision = (*iter)( details,
+                                container_path, container_root,
+                                provider_path, provider_root );
+
+        if( decision != 0 )
+            return decision;
+    }
+
+    return 0;
+}
diff --git a/subprojects/libcapsule/utils/library-cmp.h b/subprojects/libcapsule/utils/library-cmp.h
new file mode 100644
index 0000000000000000000000000000000000000000..480de5bb15c848256da02c49c60d2bab8393ea71
--- /dev/null
+++ b/subprojects/libcapsule/utils/library-cmp.h
@@ -0,0 +1,95 @@
+// Copyright © 2020 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdio.h>
+
+typedef struct _library_details library_details;
+
+/*
+ * library_cmp_function:
+ * @details: The details about the library we are interested in and how
+ *  to compare it
+ * @container_path: The path to the library in the container
+ * @container_root: The path to the top-level directory of the container
+ * @provider_path: The path to the library in the provider
+ * @provider_root: The path to the top-level directory of the provider
+ *
+ * Compare two libraries, returning a result with the same convention
+ * as strcmp(): informally, `cmp(a, b) < 0` if `a < b`, and the same
+ * for `>`.
+ *
+ * Returns: Negative if the container version appears newer, zero if they
+ *  appear the same or we cannot tell, or positive if the provider version
+ *  appears newer.
+ */
+typedef int ( *library_cmp_function ) ( const library_details *details,
+                                        const char *container_path,
+                                        const char *container_root,
+                                        const char *provider_path,
+                                        const char *provider_root );
+
+/*
+ * library_details:
+ * @name: The name we look for the library under, normally a SONAME
+ * @comparators: Functions to use to compare versions, or %NULL for
+ *  default behaviour
+ * @public_symbol_versions: An array of patterns to discriminate public
+ *  versions from private versions
+ * @public_symbols: An array of patterns to discriminate public
+ *  symbols from private symbols
+ */
+struct _library_details
+{
+    char *name;
+    library_cmp_function *comparators;
+    char **public_symbol_versions;
+    char **public_symbols;
+};
+
+library_cmp_function *library_cmp_list_from_string( const char *spec,
+                                                    const char *delimiters,
+                                                    int *code,
+                                                    char **message );
+int library_cmp_list_iterate( const library_details *details,
+                              const char *container_path,
+                              const char *container_root,
+                              const char *provider_path,
+                              const char *provider_root );
+
+/*
+ * library_knowledge:
+ * @tree: (element-type library_details): A tsearch(3) tree
+ *
+ * Details of all known libraries.
+ */
+typedef struct
+{
+    /*< private >*/
+    void *tree;
+} library_knowledge;
+
+#define LIBRARY_KNOWLEDGE_INIT { NULL }
+
+bool library_knowledge_load_from_stream( library_knowledge *self,
+                                         FILE *stream, const char *name,
+                                         int *code, char **message );
+const library_details *library_knowledge_lookup( const library_knowledge *self,
+                                                 const char *library );
+void library_knowledge_clear( library_knowledge *self );
diff --git a/subprojects/libcapsule/utils/mmap-info.c b/subprojects/libcapsule/utils/mmap-info.c
new file mode 100644
index 0000000000000000000000000000000000000000..8cdc10c6b4476db28af7924849bfac5ae992facd
--- /dev/null
+++ b/subprojects/libcapsule/utils/mmap-info.c
@@ -0,0 +1,264 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <ctype.h>
+#include <errno.h>
+#include <string.h>
+#include <link.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include "mmap-info.h"
+#include "utils.h"
+
+typedef enum
+{
+    MAPS_START  ,
+    MAPS_END    ,
+    MAPS_DATA   ,
+    MAPS_PROT_R ,
+    MAPS_PROT_W ,
+    MAPS_PROT_X ,
+    MAPS_PROT_P ,
+    MAPS_SKIP   ,
+    MAPS_PATH   ,
+} map_parse_state;
+
+#define PROC_FILE "/proc/self/maps"
+#define ERROR(s) \
+    ({ if(err   ) *err    = errno; \
+       if(errstr) *errstr = s;    \
+       return NULL; })
+
+void
+free_mmap_info(mmapinfo *ptr)
+{
+    if( ptr )
+        free( ptr );
+}
+
+mmapinfo *
+find_mmap_info(mmapinfo *maps, void *addr)
+{
+    if( !maps )
+        return NULL;
+
+    for( int i = 0; maps[i].start != MAP_FAILED; i++ )
+    {
+        if( (ElfW(Addr)) maps[i].start > (ElfW(Addr)) addr )
+            continue;
+
+        if( (ElfW(Addr)) maps[i].end < (ElfW(Addr)) addr )
+            continue;
+
+        return &maps[i];
+    }
+
+    return NULL;
+}
+
+int
+add_mmap_protection(mmapinfo *mmap_info, unsigned int flags)
+{
+    ElfW(Addr)   start = (ElfW(Addr)) mmap_info->start;
+    ElfW(Addr)   end   = (ElfW(Addr)) mmap_info->end;
+    size_t       size  = end - start;
+    unsigned int prot  = mmap_info->protect | flags;
+
+    return mprotect( (void *)start, size, prot );
+}
+
+int
+reset_mmap_protection(mmapinfo *mmap_info)
+{
+    ElfW(Addr)    start = (ElfW(Addr)) mmap_info->start;
+    ElfW(Addr)    end   = (ElfW(Addr)) mmap_info->end;
+    size_t        size  = end - start;
+
+    return mprotect( (void *)start, size, mmap_info->protect );
+}
+
+mmapinfo *
+load_mmap_info (int *err, const char **errstr)
+{
+    FILE *maps = fopen( PROC_FILE, "r" );
+    char map_line[80 + PATH_MAX];
+    int map_entries = 0;
+    mmapinfo *entries = NULL;
+
+    if( !maps )
+        ERROR("Warning: could not open " PROC_FILE);
+
+    while( fgets(&map_line[0], sizeof(map_line), maps) )
+        map_entries++;
+
+    if( fseek(maps, 0, SEEK_SET) < 0 )
+        ERROR("Warning: Unable to seek to start of " PROC_FILE);
+
+    if( map_entries > 0 )
+        entries = xcalloc( map_entries + 1, sizeof(mmapinfo) );
+    else
+        ERROR("Warning: no mmap entries found in " PROC_FILE);
+
+    int l = 0;
+
+    while( fgets(&map_line[0], sizeof(map_line), maps) )
+    {
+        size_t offs;
+        char *s = NULL;
+        int skip = 0;
+        map_parse_state p = MAPS_START;
+        map_parse_state item = MAPS_START;
+        int last_blank = 0;
+        char *path = NULL;
+
+        for( offs = 0; offs < sizeof(map_line) && l < map_entries; offs++ )
+        {
+            char *c = map_line + offs;
+
+            // we've hit the end of the line: prematurely or not, clean up:
+            if( *c == '\n' )
+            {
+                *c = '\0';
+                if( path )
+                    strncpy( &entries[l].path[0], path, PATH_MAX );
+                entries[l].path[PATH_MAX - 1] = '\0';
+                l++;
+                break;
+            }
+
+            switch (p)
+            {
+              case MAPS_START:
+              case MAPS_END:
+                if( isxdigit(*c) )
+                {
+                    s = c;
+                    item = p;
+                    p = MAPS_DATA;
+                }
+                break;
+
+              case MAPS_DATA:
+                if( !isxdigit(*c) )
+                {
+                    char **addr = ( item == MAPS_START ?
+                                    &entries[l].start  :
+                                    &entries[l].end    );
+                    *c = '\0';
+                    *addr = (char *)strtoul( s, NULL, 16 );
+                    p = item == MAPS_START ? MAPS_END : MAPS_PROT_R;
+                }
+                break;
+
+              case MAPS_PROT_R:
+                if( *c != 'r' && *c != '-' )
+                    continue;
+
+                entries[l].protect  = PROT_NONE;
+
+                entries[l].protect |= (*c == 'r' ? PROT_READ : PROT_NONE);
+                p = MAPS_PROT_W;
+                break;
+
+              case MAPS_PROT_W:
+                entries[l].protect |= (*c == 'w' ? PROT_WRITE : PROT_NONE);
+                p = MAPS_PROT_X;
+                break;
+
+              case MAPS_PROT_X:
+                entries[l].protect |= (*c == 'x' ? PROT_EXEC : PROT_NONE);
+                p = MAPS_PROT_P;
+                break;
+
+              case MAPS_PROT_P:
+                last_blank = isblank(*c);
+                skip = 7;
+                p = MAPS_SKIP;
+                break;
+
+              case MAPS_SKIP:
+                if( isblank(*c) )
+                {
+                    if( last_blank == 0 )
+                        skip--;
+                }
+                else if( !isblank(*c) )
+                {
+                    if( last_blank == 1 )
+                        skip--;
+                }
+
+                last_blank = isblank(*c);
+
+                if( skip <= 0 )
+                    p = MAPS_PATH;
+                break;
+
+              case MAPS_PATH:
+                if( !path && !isblank(*c) )
+                    path = c;
+                break;
+
+              default:
+                // invalid state reached: flag and move on:
+                fprintf( stderr, "Cannot parse mmap info: %s\n", map_line );
+                entries[l].invalid = 1;
+                entries[l].protect = PROT_NONE;
+                entries[l].path[PATH_MAX - 1] = '\0';
+                break;
+            }
+
+            // bogus/unparseable line, proceed to next entry:
+            if( entries[l].invalid )
+                continue;
+        }
+    }
+
+    entries[map_entries].start   = MAP_FAILED;
+    entries[map_entries].end     = MAP_FAILED;
+    entries[map_entries].protect = PROT_NONE;
+    entries[map_entries].path[0] = '\0';
+
+    fclose( maps );
+
+    return entries;
+}
+
+int
+mmap_entry_should_be_writable (mmapinfo *mmap_info)
+{
+    // malformed or unparseable entry - cannot handle:
+    if( mmap_info->invalid )
+        return 0;
+
+    // already has write permissions, don't care:
+    if( mmap_info->protect & PROT_WRITE )
+        return 0;
+
+    // or not a 'real' DSO, leave the hell alone:
+    if( strchr( mmap_info->path, '[' ) )
+        return 0;
+
+    return 1;
+}
+
diff --git a/subprojects/libcapsule/utils/mmap-info.h b/subprojects/libcapsule/utils/mmap-info.h
new file mode 100644
index 0000000000000000000000000000000000000000..265925e61bc7750b62efb30b23edb0c8c25ee641
--- /dev/null
+++ b/subprojects/libcapsule/utils/mmap-info.h
@@ -0,0 +1,40 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#include <sys/param.h>
+#include <sys/mman.h>
+
+typedef struct
+{
+    char path[PATH_MAX];
+    char *start;
+    char *end;
+    unsigned int protect;
+    int invalid;
+} mmapinfo;
+
+mmapinfo *load_mmap_info (int *err, const char **errstr);
+mmapinfo *find_mmap_info (mmapinfo *maps, void *addr);
+void        free_mmap_info (mmapinfo *ptr);
+
+int add_mmap_protection   (mmapinfo *mmap_info, unsigned int flags);
+int reset_mmap_protection (mmapinfo *mmap_info);
+
+int mmap_entry_should_be_writable (mmapinfo *mmap_info);
+
diff --git a/subprojects/libcapsule/utils/print-libstubs.c b/subprojects/libcapsule/utils/print-libstubs.c
new file mode 100644
index 0000000000000000000000000000000000000000..649e8521a08f401f3bc4868bae6e8496260799c1
--- /dev/null
+++ b/subprojects/libcapsule/utils/print-libstubs.c
@@ -0,0 +1,405 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <dlfcn.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <link.h>
+
+#include "tools.h"
+#include "utils.h"
+#include "ld-cache.h"
+#include "ld-libs.h"
+
+static void usage (int code) __attribute__((noreturn));
+static void usage (int code)
+{
+  FILE *fh;
+
+  if( code == 0 )
+  {
+      fh = stdout;
+  }
+  else
+  {
+      fh = stderr;
+  }
+
+  fprintf( fh, "Usage: %s SONAME [SYSROOT]\n",
+           program_invocation_short_name );
+  fprintf( fh, "SONAME is the machine-readable name of a shared library,\n"
+               "for example 'libz.so.1'.\n" );
+  fprintf( fh, "SYSROOT is the root directory where we look for SONAME.\n" );
+  exit( code );
+}
+
+typedef struct
+{
+    size_t plen;
+    const char *prefix;
+    const char *target;
+} dso_search_t;
+
+// these macros are secretly the same for elf32 & elf64:
+#define ELFW_ST_TYPE(a)       ELF32_ST_TYPE(a)
+#define ELFW_ST_BIND(a)       ELF32_ST_BIND(a)
+#define ELFW_ST_VISIBILITY(a) ELF32_ST_VISIBILITY(a)
+
+// given a symbol in DT_SYMTAB at index 𝒊, jump to its
+// entry in DT_VERSYM (also at offset 𝒊 for an array of ElfW(Versym))
+// and extract its value 𝒗𝒔 (a number). The corresponding DT_VERDEF entry
+// (ElfW(Verdef)) is the one whose vd_ndx member == 𝒗𝒔 & 0x7fff
+//
+// NOTE: if 𝒗𝒔 & 0x8000 then the version is the default or base version
+// of the symbol, which should be used if the requestor has not specified
+// a version for this symbol
+//
+// NOTE: in practice the vd_ndx member is the 1-based array position in
+// the DT_VERDEF array, but the linker/elfutils code does not rely on
+// this, so neither do we.
+//
+// next we check that the vd_flags member in the DT_VERDEF entry does not
+// contain VER_FLG_BASE, as that is the DT_VERDEF entry for the entire DSO
+// and must not be used as a symbol version (this should never happen:
+// the spec does not allow it, but it's not physically impossible).
+//
+// if we have a valid DT_VERDEF entry the ElfW(Verdaux) array entry at offset
+// vd_aux (from the address of the DT_VERDEF entry itself) will give
+// the address of an ElfW(Verdaux) struct whose vda_name entry points
+// to (𝑓𝑖𝑛𝑎𝑙𝑙𝑦) an offset into the DT_STRTAB which gives the version name.
+//
+// And that's how symbol version lookup works, as near as I can tell.
+static void
+symbol_version ( ElfW(Sym) *symbol,
+                 int i,
+                 const char *strtab,
+                 const ElfW(Versym) *versym,
+                 const void *verdef,
+                 const int verdefnum,
+                 int *default_version,
+                 const char **version,
+                 ElfW(Versym) *vs)
+{
+    *default_version = 0;
+    *version = NULL;
+
+    if( versym == NULL )
+        return;
+
+    switch( symbol->st_shndx )
+    {
+      case SHN_UNDEF:
+      case SHN_ABS:
+      case SHN_COMMON:
+      case SHN_BEFORE:
+      case SHN_AFTER:
+      case SHN_XINDEX:
+        // none of these are handled (and we're very unlikely to need to)
+        break;
+
+      default:
+        if( symbol->st_shndx < SHN_LORESERVE )
+        {
+            const char  *vd = verdef;
+            *vs = *(versym + i);
+
+            for( int x = 0; x < verdefnum; x++ )
+            {
+                ElfW(Verdef) *entry = (ElfW(Verdef) *) vd;
+
+                if( entry->vd_ndx == (*vs & 0x7fff) )
+                {
+                    const char *au;
+
+                    if( entry->vd_flags & VER_FLG_BASE )
+                        break;
+
+                    au = vd + entry->vd_aux;
+                    ElfW(Verdaux) *aux = (ElfW(Verdaux) *) au;
+                    *version = strtab + aux->vda_name;
+                    *default_version = (*vs & 0x8000) ? 0 : 1;
+                }
+
+                vd = vd + entry->vd_next;
+            }
+        }
+    }
+}
+
+
+static int
+symbol_excluded (const char *name)
+{
+    if( !strcmp(name, "_init") ||
+        !strcmp(name, "_fini") )
+        return 1;
+
+    return 0;
+}
+
+static void
+parse_symtab (const void *start,
+              const char *strtab,
+              const ElfW(Versym) *versym,
+              const void *verdef,
+              const int verdefnum)
+{
+    int x = 0;
+    ElfW(Sym) *entry;
+
+    for( entry = (ElfW(Sym) *)start;
+         ( (ELFW_ST_TYPE(entry->st_info) < STT_NUM) &&
+           (ELFW_ST_BIND(entry->st_info) < STB_NUM) );
+         entry++, x++ )
+    {
+        int defversym = 0;
+        const char *version = NULL;
+        ElfW(Versym) vs = 0;
+
+        switch( ELFW_ST_TYPE(entry->st_info) )
+        {
+          case STT_FUNC:
+          case STT_OBJECT:
+            symbol_version( entry, x, strtab, versym, verdef, verdefnum,
+                            &defversym, &version, &vs );
+
+            if( !vs )
+                continue;
+
+            if( symbol_excluded(strtab + entry->st_name) )
+                continue;
+
+            fprintf( stdout, "%s %s%s%s\n",
+                     strtab + entry->st_name  ,
+                     version   ? "@"     : "" ,
+                     defversym ? "@"     : "" ,
+                     version   ? version : "" );
+            break;
+
+          default:
+            // Assumed to be uninteresting?
+            break;
+        }
+    }
+}
+
+static void
+parse_dynamic (ElfW(Addr) base, ElfW(Dyn) *dyn)
+{
+
+    int verdefnum  = -1;
+    void *start    = NULL;
+    const void *symtab = NULL;
+    const void *versym = NULL;
+    const void *verdef = NULL;
+    const char *strtab = NULL;
+    ElfW(Dyn) *entry   = NULL;
+
+    start  = (void *) ((ElfW(Addr)) dyn - base);
+    strtab = dynamic_section_find_strtab( dyn, (const void *) base, NULL );
+
+    for( entry = dyn; entry->d_tag != DT_NULL; entry++ )
+    {
+        switch( entry->d_tag )
+        {
+          case DT_SYMTAB:
+            if( versym == NULL )
+                versym = (const void *) find_ptr( base, start, DT_VERSYM );
+            if( verdef == NULL )
+                verdef = (const void *) find_ptr( base, start, DT_VERDEF );
+            if( verdefnum == -1 )
+                verdefnum = find_value( base, start, DT_VERDEFNUM );
+
+            symtab = (const void *) entry->d_un.d_ptr;
+            parse_symtab( symtab, strtab, versym, verdef, verdefnum );
+            break;
+
+          case DT_VERDEFNUM:
+            verdefnum = entry->d_un.d_val;
+            break;
+
+          case DT_VERDEF:
+            if( verdefnum == -1 )
+                verdefnum = find_value( base, start, DT_VERDEFNUM );
+            verdef = (const void *) entry->d_un.d_ptr;
+            // parse_verdef( verdef, strtab, verdefnum );
+            break;
+
+          case DT_VERSYM:
+            if( versym == NULL )
+                versym = (const void *) entry->d_un.d_ptr;
+            break;
+
+          default:
+            break;
+        }
+    }
+
+    return;
+}
+
+static int
+dso_name_matches (const char *target, const char *maybe)
+{
+    const char *dir = strrchr( maybe, '/' );
+    const size_t len = strlen( target );
+
+    // maybe contains a full path:
+    if( !dir )
+        return 0;
+
+    // maybe's filename is at least long enough to match target
+    if( strlen(++dir) < len )
+        return 0;
+
+    // /maybefilename matches /target
+    if( memcmp(dir, target, len) )
+        return 0;
+
+    // the last major/minor/etc number didn't actually match
+    // eg libfoo.so.1 vs libfoo.so.10
+    if ( *(dir + len) != '.' && *(dir + len) != '\0' )
+        return 0;
+
+    return 1;
+}
+
+static void
+dump_symbols (void *handle, const char *libname)
+{
+    int dlcode = 0;
+    struct link_map *map;
+
+    if( (dlcode = dlinfo( handle, RTLD_DI_LINKMAP, &map )) )
+    {
+        fprintf( stderr, "cannot access symbols for %s via handle %p [%d]\n",
+                 libname, handle, dlcode );
+        exit( dlcode );
+    }
+
+    // find start of link map chain:
+    while( map->l_prev )
+        map = map->l_prev;
+
+    for( struct link_map *m = map; m; m = m->l_next )
+        if( dso_name_matches( libname, m->l_name ) )
+            parse_dynamic( m->l_addr, m->l_ld );
+
+}
+
+enum
+{
+  OPTION_HELP,
+  OPTION_VERSION,
+};
+
+static struct option long_options[] =
+{
+    { "help", no_argument, NULL, OPTION_HELP },
+    { "version", no_argument, NULL, OPTION_VERSION },
+    { NULL }
+};
+
+int main (int argc, char **argv)
+{
+    const char *libname;
+    const char *prefix = NULL;
+    char *message = NULL;
+    ld_libs ldlibs = {};
+    int error = 0;
+    Lmid_t ns = LM_ID_BASE;
+    void *handle;
+
+    while( 1 )
+    {
+        int opt = getopt_long( argc, argv, "", long_options, NULL );
+
+        if( opt == -1 )
+        {
+            break;
+        }
+
+        switch( opt )
+        {
+            case '?':
+            default:
+                usage( 2 );
+                break;  // not reached
+
+            case OPTION_HELP:
+                usage( 0 );
+                break;  // not reached
+
+            case OPTION_VERSION:
+                _capsule_tools_print_version( "capsule-symbols" );
+                return 0;
+        }
+    }
+
+    if( argc < optind + 1 || argc > optind + 2)
+        usage( 1 );
+
+    if( argc > optind + 1 )
+        prefix = argv[optind + 1];
+
+    set_debug_flags( getenv("CAPSULE_DEBUG") );
+
+    if( !ld_libs_init( &ldlibs, NULL, prefix, 0, &error, &message ) )
+    {
+        fprintf( stderr, "%s: failed to initialize for prefix %s (%d: %s)\n",
+                 program_invocation_short_name, prefix, error, message );
+        exit( error ? error : ENOENT );
+    }
+
+    if( !ld_libs_set_target( &ldlibs, argv[optind], &error, &message ) ||
+        !ld_libs_find_dependencies( &ldlibs, &error, &message ) )
+    {
+        fprintf( stderr, "%s: failed to open [%s]%s (%d: %s)\n",
+                 program_invocation_short_name, prefix, argv[optind], error, message );
+        exit( error ? error : ENOENT );
+    }
+
+    if( ( handle = ld_libs_load( &ldlibs, &ns, 0, &error, &message ) ) )
+    {
+        if( (libname = strrchr( argv[optind], '/' )) )
+            libname = libname + 1;
+        else
+            libname = argv[optind];
+
+        // dl_iterate_phdr won't work with private dlmopen namespaces:
+        dump_symbols( handle, libname );
+    }
+    else
+    {
+        fprintf( stderr, "%s: failed to open [%s]%s (%d: %s)\n",
+                 program_invocation_short_name, prefix, argv[optind], error, message );
+        exit(error ? error : ENOENT);
+    }
+
+    exit(0);
+}
diff --git a/subprojects/libcapsule/utils/print-version.c b/subprojects/libcapsule/utils/print-version.c
new file mode 100644
index 0000000000000000000000000000000000000000..adc08c2f0ae67b8b86dc2de98b8aeb49a1044c5c
--- /dev/null
+++ b/subprojects/libcapsule/utils/print-version.c
@@ -0,0 +1,154 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <dlfcn.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#include "debug.h"
+#include "ld-cache.h"
+#include "ld-libs.h"
+#include "tools.h"
+
+static void usage (int code) __attribute__((noreturn));
+static void usage (int code)
+{
+  FILE *fh;
+
+  if( code == 0 )
+  {
+      fh = stdout;
+  }
+  else
+  {
+      fh = stderr;
+  }
+
+  fprintf( fh, "Usage: %s SONAME [SYSROOT]\n",
+           program_invocation_short_name );
+  fprintf( fh, "SONAME is the machine-readable name of a shared library,\n"
+               "for example 'libz.so.1'.\n" );
+  fprintf( fh, "SYSROOT is the root directory where we look for SONAME.\n" );
+  exit( code );
+}
+
+enum
+{
+  OPTION_HELP,
+  OPTION_VERSION,
+};
+
+static struct option long_options[] =
+{
+    { "help", no_argument, NULL, OPTION_HELP },
+    { "version", no_argument, NULL, OPTION_VERSION },
+    { NULL }
+};
+
+int main (int argc, char **argv)
+{
+    const char *libname;
+    const char *prefix = NULL;
+    char *message = NULL;
+    ld_libs ldlibs = {};
+    int error = 0;
+    int e = 0;
+
+    set_debug_flags( getenv("CAPSULE_DEBUG") );
+
+    while( 1 )
+    {
+        int opt = getopt_long( argc, argv, "", long_options, NULL );
+
+        if( opt == -1 )
+        {
+            break;
+        }
+
+        switch( opt )
+        {
+            case '?':
+            default:
+                usage( 2 );
+                break;  // not reached
+
+            case OPTION_HELP:
+                usage( 0 );
+                break;  // not reached
+
+            case OPTION_VERSION:
+                _capsule_tools_print_version( "capsule-version" );
+                return 0;
+        }
+    }
+
+    if( argc < optind + 1 || argc > optind + 2)
+        usage( 1 );
+
+    if( argc > optind + 1 )
+        prefix = argv[optind + 1];
+
+    if( !ld_libs_init( &ldlibs, NULL, prefix, 0, &error, &message ) )
+    {
+        fprintf( stderr, "%s: failed to initialize for prefix %s (%d: %s)\n",
+                 program_invocation_short_name, prefix, error, message );
+        exit( error ? error : ENOENT );
+    }
+
+    if( ld_libs_set_target( &ldlibs, argv[optind], &error, &message ) )
+    {
+        const char *path;
+        const char *buf;
+
+        if( (libname = strrchr( argv[optind], '/' )) )
+            libname = libname + 1;
+        else
+            libname = argv[optind];
+
+        path = &ldlibs.needed[0].path[0];
+
+        while( (buf = strstr( path + 1, libname )) )
+            path = buf;
+
+        if( path )
+            path = strstr( path, ".so." );
+
+        if( path )
+            path += 4;
+
+        if( !path || !*path )
+            if( (path = strstr( libname, ".so." )) )
+                path += 4;
+
+        fprintf( stdout, "%s %s %s %s\n",
+                 prefix, libname,
+                 (path && *path) ?  path : "1", // wild guess if we failed
+                 &ldlibs.needed[0].path[0] );
+    }
+    else
+    {
+        fprintf( stderr, "%s: failed to open [%s]%s (%d: %s)\n",
+                 program_invocation_short_name, prefix, argv[optind], error, message );
+        exit( error ? error : ENOENT );
+    }
+
+    ld_libs_finish( &ldlibs );
+    exit(e);
+}
diff --git a/subprojects/libcapsule/utils/process-pt-dynamic.c b/subprojects/libcapsule/utils/process-pt-dynamic.c
new file mode 100644
index 0000000000000000000000000000000000000000..57dac19db72c2661fba95abd250943ca216d8067
--- /dev/null
+++ b/subprojects/libcapsule/utils/process-pt-dynamic.c
@@ -0,0 +1,785 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <assert.h>
+#include <dlfcn.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <capsule/capsule.h>
+#include "utils.h"
+#include "process-pt-dynamic.h"
+#include "mmap-info.h"
+
+static void *
+#if __ELF_NATIVE_CLASS == 32
+addr (void *base, ElfW(Addr) offset, ElfW(Sword) addend)
+#elif __ELF_NATIVE_CLASS == 64
+addr (void *base, ElfW(Addr) offset, ElfW(Sxword) addend)
+#else
+#error "Unsupported __ELF_NATIVE_CLASS size (not 32 or 64)"
+#endif
+{
+    return base + offset + addend;
+}
+
+
+static int
+try_relocation (ElfW(Addr) *reloc_addr, const char *name, void *data)
+{
+    capsule_item *map;
+    relocation_data *rdata = data;
+
+    if( !name || !*name || !reloc_addr )
+        return 0;
+
+    for( map = rdata->relocs; map->name; map++ )
+    {
+        if( strcmp( name, map->name ) )
+            continue;
+
+        DEBUG( DEBUG_RELOCS,
+               "relocation for %s (%p->{ %p }, %p, %p)",
+               name, reloc_addr, NULL, (void *)map->shim, (void *)map->real );
+
+        // we used to check for the shim address here but it's possible
+        // that we can't look it up if the proxy library was dlopen()ed
+        // in which case map->shim will be null.
+        // this turns out not to be a problem as we only need it when
+        // working around RELRO linking, which doesn't apply to dlopen()
+
+        // sought after symbols is not available in the private namespace
+        if( !map->real )
+        {
+            rdata->count.failure++;
+            DEBUG( DEBUG_RELOCS, "--failed" );
+
+            return 1;
+        }
+
+        // our work here is already done, apparently
+        if( *reloc_addr == map->real )
+        {
+            DEBUG( DEBUG_RELOCS, "==target %p already contains %p (%p)",
+                   reloc_addr, (void *)*reloc_addr, (void *)map->real );
+            return 0;
+        }
+        // ======================================================================
+        // exegesis:
+
+        // linking goes like this: we start with a PLT entry pointing at the
+        // 'trampoline' entry which patches up the relocations. The first
+        // time we call a function, we go to the PLT which sends us to the
+        // trampoline, which  finds the shim (in the case of our proxy library)
+        // or the real address (in the case of a normal library) and pastes that
+        // address into the PLT.
+
+        // This function scribbles over the trampoline address with the real
+        // address, thus bypassing the trampoline _and_ the shim permanently.
+
+        /// IOW the 0th, 1st and second function calls normally look like this:
+        // 0: function-call → PLT → trampoline : (PLT ← address) → address
+        // 1: function-call → PLT → address
+        // 2: ibid
+
+        // If we are already pointing to the shim instead of the trampoline
+        // that indicates we have RELRO linking - the linker has already resolved
+        // the address to the shim (as it doesn't know about the real address
+        // which is hidden inside the capsule).
+
+        // -1: linker → function-lookup : (PLT ← address)
+        //  0: function-call → PLT → address
+        //  1: ibid
+
+        // but⁰ RELRO linking also mprotect()s the relevant pages to be read-only
+        // which prevents us from overwriting the address.
+
+        // but¹ we are smarter than the average bear, and we tried to harvest
+        // the mprotect info: If we did, then we will already have toggled the
+        // write permission on everything that didn't have it and can proceed
+        // (we're also not savages, so we'll put those permissions back later)
+
+        // however, if we don't have any mprotect into for this relocation entry,
+        // then we can't de-shim the RELROd PLT entry, and it's sad 🐼 time.
+        // ======================================================================
+        if( (*reloc_addr == map->shim) &&
+            !find_mmap_info(rdata->mmap_info, reloc_addr) )
+        {
+            DEBUG( DEBUG_RELOCS|DEBUG_MPROTECT,
+                   " ERROR: cannot update relocation record for %s", name );
+            return 1; // FIXME - already shimmed, can't seem to override?
+        }
+
+        *reloc_addr = map->real;
+        rdata->count.success++;
+        DEBUG( DEBUG_RELOCS, "++relocated" );
+        return 0;
+    }
+
+    // nothing to relocate
+    return 0;
+}
+
+#if defined(__x86_64__)
+
+static const char *
+reloc_type_name (int type)
+{
+    switch (type)
+    {
+      // Please keep these in numerical order.
+
+#define CASE(x) \
+      case x: \
+        return #x;
+
+      CASE( R_X86_64_NONE )
+      CASE( R_X86_64_64 )
+      CASE( R_X86_64_PC32 )
+      CASE( R_X86_64_GOT32 )
+      CASE( R_X86_64_PLT32 )
+      CASE( R_X86_64_COPY )
+      CASE( R_X86_64_GLOB_DAT )
+      CASE( R_X86_64_JUMP_SLOT )
+      CASE( R_X86_64_RELATIVE )
+      CASE( R_X86_64_GOTPCREL )
+      CASE( R_X86_64_32 )
+      CASE( R_X86_64_32S )
+      CASE( R_X86_64_16 )
+      CASE( R_X86_64_PC16 )
+      CASE( R_X86_64_8 )
+      CASE( R_X86_64_PC8 )
+      CASE( R_X86_64_DTPMOD64 )
+      CASE( R_X86_64_DTPOFF64 )
+      CASE( R_X86_64_TPOFF64 )
+      CASE( R_X86_64_TLSGD )
+      CASE( R_X86_64_TLSLD )
+      CASE( R_X86_64_DTPOFF32 )
+      CASE( R_X86_64_GOTTPOFF )
+      CASE( R_X86_64_TPOFF32 )
+      CASE( R_X86_64_PC64 )
+      CASE( R_X86_64_GOTOFF64 )
+      CASE( R_X86_64_GOTPC32 )
+      CASE( R_X86_64_GOT64 )
+      CASE( R_X86_64_GOTPCREL64 )
+      CASE( R_X86_64_GOTPC64 )
+      CASE( R_X86_64_GOTPLT64 )
+      CASE( R_X86_64_PLTOFF64 )
+      CASE( R_X86_64_SIZE32 )
+      CASE( R_X86_64_SIZE64 )
+      CASE( R_X86_64_GOTPC32_TLSDESC )
+      CASE( R_X86_64_TLSDESC_CALL )
+      CASE( R_X86_64_TLSDESC )
+      CASE( R_X86_64_IRELATIVE )
+
+      // Entries below this point are new since glibc 2.19
+
+#ifdef R_X86_64_RELATIVE64
+      CASE( R_X86_64_RELATIVE64 )
+#endif
+#ifdef R_X64_64_GOTPCRELX
+      CASE( R_X86_64_GOTPCRELX )
+#endif
+#ifdef R_X64_64_REX_GOTPCRELX
+      CASE( R_X86_64_REX_GOTPCRELX )
+#endif
+
+#undef CASE
+
+      default:
+        return "UNKNOWN";
+    }
+}
+
+#elif defined(__i386__)
+
+static const char *
+reloc_type_name (int type)
+{
+    switch (type)
+    {
+      // Please keep these in numerical order.
+
+#define CASE(x) \
+      case x: \
+        return #x;
+
+      CASE( R_386_NONE )
+      CASE( R_386_32 )
+      CASE( R_386_PC32 )
+      CASE( R_386_GOT32 )
+      CASE( R_386_PLT32 )
+      CASE( R_386_COPY )
+      CASE( R_386_GLOB_DAT )
+      CASE( R_386_JMP_SLOT )
+      CASE( R_386_RELATIVE )
+      CASE( R_386_GOTOFF )
+      CASE( R_386_GOTPC )
+      CASE( R_386_32PLT )
+      CASE( R_386_TLS_TPOFF )
+      CASE( R_386_TLS_IE )
+      CASE( R_386_TLS_GOTIE )
+      CASE( R_386_TLS_LE )
+      CASE( R_386_TLS_GD )
+      CASE( R_386_TLS_LDM )
+      CASE( R_386_16 )
+      CASE( R_386_PC16 )
+      CASE( R_386_8 )
+      CASE( R_386_PC8 )
+      CASE( R_386_TLS_GD_32 )
+      CASE( R_386_TLS_GD_PUSH )
+      CASE( R_386_TLS_GD_CALL )
+      CASE( R_386_TLS_GD_POP )
+      CASE( R_386_TLS_LDM_32 )
+      CASE( R_386_TLS_LDM_PUSH )
+      CASE( R_386_TLS_LDM_CALL )
+      CASE( R_386_TLS_LDM_POP )
+      CASE( R_386_TLS_LDO_32 )
+      CASE( R_386_TLS_IE_32 )
+      CASE( R_386_TLS_LE_32 )
+      CASE( R_386_TLS_DTPMOD32 )
+      CASE( R_386_TLS_DTPOFF32 )
+      CASE( R_386_TLS_TPOFF32 )
+#ifdef R_386_SIZE32
+      CASE( R_386_SIZE32 )
+#endif
+      CASE( R_386_TLS_GOTDESC )
+      CASE( R_386_TLS_DESC_CALL )
+      CASE( R_386_TLS_DESC )
+      CASE( R_386_IRELATIVE )
+
+      // Entries below this point are new since glibc 2.19
+
+#ifdef R_386_GOT32X
+      CASE( R_386_GOT32X )
+#endif
+
+#undef CASE
+
+      default:
+        return "UNKNOWN";
+    }
+}
+
+#else
+
+#error Unsupported CPU architecture
+
+#endif
+
+int
+process_dt_rela (const ElfW(Rela) *start,
+                 size_t relasz,
+                 const char *strtab,
+                 size_t strsz,
+                 const ElfW(Sym) *symtab,
+                 size_t symsz,
+                 void *base,
+                 void *data)
+{
+    const ElfW(Rela) *entry;
+
+    DEBUG( DEBUG_ELF,
+           "%zu RELA entries (%zu bytes) starting at %p",
+           relasz / sizeof(*entry),
+           relasz,
+           start );
+
+    if( relasz % sizeof(*entry) != 0 )
+        DEBUG( DEBUG_ELF, "%zu bytes left over?!", relasz % sizeof(*entry) );
+
+    for( entry = start; entry < start + (relasz / sizeof(*entry)); entry++ )
+    {
+        int sym;
+        int chr;
+        const char *name = NULL;
+        const ElfW(Sym) *symbol;
+
+#if __ELF_NATIVE_CLASS == 32
+        sym = ELF32_R_SYM (entry->r_info);
+        chr = ELF32_R_TYPE(entry->r_info);
+#elif __ELF_NATIVE_CLASS == 64
+        sym = ELF64_R_SYM (entry->r_info);
+        chr = ELF64_R_TYPE(entry->r_info);
+#else
+        fprintf( stderr, "__ELF_NATIVE_CLASS is neither 32 nor 64" );
+        exit( 22 );
+#endif
+
+        DEBUG( DEBUG_ELF, "RELA entry at %p", entry );
+
+        symbol = find_symbol( sym, symtab, symsz, strtab, strsz, &name );
+
+        DEBUG( DEBUG_ELF,
+               "symbol %p; name: %p:%s", symbol, name, name ? name : "-" );
+
+        if( !symbol || !name || !*name )
+            continue;
+
+#if defined(__i386__) || defined(__x86_64__)
+        switch( chr )
+        {
+            void *slot;
+       // details at: https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
+#if defined(__i386__)
+          case R_386_32:
+          case R_386_GLOB_DAT:
+          case R_386_JMP_SLOT:
+#elif defined(__x86_64__)
+          case R_X86_64_64:
+          case R_X86_64_GLOB_DAT:
+          case R_X86_64_JUMP_SLOT:
+#else
+#error Unsupported CPU architecture
+#endif
+            slot = addr( base, entry->r_offset, entry->r_addend );
+            DEBUG( DEBUG_ELF,
+                   " %30s %30s: %p ← { offset: %"FMT_ADDR"; add: %"FMT_SIZE" }",
+                   name, reloc_type_name( chr ),
+                   slot, entry->r_offset, entry->r_addend );
+            try_relocation( slot, name, data );
+            break;
+
+          default:
+            DEBUG( DEBUG_ELF,
+                   "%s has slot type %s (%d), not doing anything special",
+                   name, reloc_type_name( chr ), chr );
+            break;
+        }
+#else
+#error Unsupported CPU architecture
+#endif
+    }
+
+    return 0;
+}
+
+int
+process_dt_rel (const ElfW(Rel) *start,
+                size_t relasz,
+                const char *strtab,
+                size_t strsz,
+                const ElfW(Sym) *symtab,
+                size_t symsz,
+                void *base,
+                void *data)
+{
+    const ElfW(Rel) *entry;
+
+    DEBUG( DEBUG_ELF,
+           "%zu REL entries (%zu bytes) starting at %p",
+           relasz / sizeof(*entry),
+           relasz,
+           start );
+
+    if( relasz % sizeof(*entry) != 0 )
+        DEBUG( DEBUG_ELF, "%zu bytes left over?!", relasz % sizeof(*entry) );
+
+    for( entry = start; entry < start + (relasz / sizeof(*entry)); entry++ )
+    {
+        int sym;
+        int chr;
+        const char *name = NULL;
+
+        const ElfW(Sym) *symbol;
+
+#if __ELF_NATIVE_CLASS == 32
+        sym = ELF32_R_SYM (entry->r_info);
+        chr = ELF32_R_TYPE(entry->r_info);
+#elif __ELF_NATIVE_CLASS == 64
+        sym = ELF64_R_SYM (entry->r_info);
+        chr = ELF64_R_TYPE(entry->r_info);
+#else
+#error Unsupported CPU architecture
+#endif
+
+        symbol = find_symbol( sym, symtab, symsz, strtab, strsz, &name );
+
+        DEBUG( DEBUG_ELF,
+               "symbol %p; name: %p:%s", symbol, name, name ? name : "-" );
+
+        if( !symbol || !name || !*name )
+            continue;
+
+#if defined(__i386__) || defined(__x86_64__)
+        switch( chr )
+        {
+            void *slot;
+       // details at: https://github.com/hjl-tools/x86-psABI/wiki/X86-psABI
+#if defined(__i386__)
+          case R_386_32:
+          case R_386_GLOB_DAT:
+          case R_386_JMP_SLOT:
+#elif defined(__x86_64__)
+          case R_X86_64_64:
+          case R_X86_64_GLOB_DAT:
+          case R_X86_64_JUMP_SLOT:
+#else
+#error Unsupported CPU architecture
+#endif
+            slot = addr( base, entry->r_offset, 0 );
+            DEBUG( DEBUG_ELF,
+                   " %30s %30s: %p ← { offset: %"FMT_ADDR"; addend: n/a }",
+                   name,
+                   reloc_type_name( chr ),
+                   slot, entry->r_offset );
+            try_relocation( slot, name, data );
+            break;
+
+          default:
+            DEBUG( DEBUG_ELF,
+                   "%s has slot type %s (%d), not doing anything special",
+                   name, reloc_type_name( chr ), chr );
+            break;
+        }
+#else
+#error Unsupported CPU architecture
+#endif
+    }
+
+    return 0;
+}
+
+/*
+ * process_pt_dynamic:
+ * @start: offset of dynamic section (an array of ElfW(Dyn) structures)
+ *  relative to @base
+ * @size: size of dynamic section in bytes (not structs!), or 0
+ *  if the dynamic section is terminated by an entry with d_tag == DT_NULL
+ * @base: Starting address of the program header (the shared object)
+ *  in memory. @start is relative to this. Addresses are normally
+ *  relative to this, except for when they are absolute (see fix_addr()).
+ * @process_rela: called when we find the DT_RELA section
+ * @process_rel: called when we find the DT_REL section
+ * @data: arbitrary user data to be passed to both @process_rela
+ *  and @process_rel
+ *
+ * Iterate over the PT_DYNAMIC entry in a shared library and perform
+ * relocations using the given callbacks.
+ */
+int
+process_pt_dynamic (ElfW(Addr) start,
+                    size_t size,
+                    void *base,
+                    relocate_rela_cb process_rela,
+                    relocate_rel_cb process_rel,
+                    void *data)
+{
+    int ret = 0;
+    const ElfW(Dyn) *entries;
+    const ElfW(Dyn) *entry;
+    size_t relasz   = (size_t) -1;
+    size_t relsz    = (size_t) -1;
+    size_t jmprelsz = (size_t) -1;
+    int jmpreltype = DT_NULL;
+    const ElfW(Sym) *symtab = NULL;
+    size_t strsz = (size_t) -1;
+    const char *strtab = dynamic_section_find_strtab( base + start, base, &strsz );
+    size_t symsz;
+
+    DEBUG( DEBUG_ELF,
+           "start: %#" PRIxPTR "; size: %" FMT_SIZE "; base: %p; handlers: %p %p; …",
+           start, size, base, process_rela, process_rel );
+    entries = base + start;
+    DEBUG( DEBUG_ELF, "dyn entry: %p", entries );
+
+    DEBUG( DEBUG_ELF,
+           "strtab is at %p: %s", strtab, strtab ? "…" : "");
+
+    // Do a first pass to find the bits we'll need later
+    for( entry = entries;
+         (entry->d_tag != DT_NULL) &&
+           ((size == 0) || ((void *)entry < (start + base + size)));
+         entry++ ) {
+        switch( entry->d_tag )
+        {
+          case DT_PLTRELSZ:
+            jmprelsz = entry->d_un.d_val;
+            DEBUG( DEBUG_ELF, "jmprelsz is %zu", jmprelsz );
+            break;
+
+          case DT_SYMTAB:
+            symtab = fix_addr( base, entry->d_un.d_ptr );
+            DEBUG( DEBUG_ELF, "symtab is %p", symtab );
+            break;
+
+          case DT_RELASZ:
+            relasz = entry->d_un.d_val;
+            DEBUG( DEBUG_ELF, "relasz is %zu", relasz );
+            break;
+
+          case DT_RELSZ:
+            relsz = entry->d_un.d_val;
+            DEBUG( DEBUG_ELF, "relsz is %zu", relsz );
+            break;
+
+          case DT_PLTREL:
+            jmpreltype = entry->d_un.d_val;
+            DEBUG( DEBUG_ELF, "jmpreltype is %d : %s", jmpreltype,
+                   jmpreltype == DT_REL  ? "DT_REL"  :
+                   jmpreltype == DT_RELA ? "DT_RELA" : "???" );
+            break;
+
+          default:
+            // We'll deal with this later
+            break;
+        }
+    }
+
+    /* XXX Apparently the only way to find out the size of the dynamic
+       symbol section is to assume that the string table follows right
+       afterwards... —glibc elf/dl-fptr.c */
+    assert( strtab >= (const char *) symtab );
+    symsz = strtab - (const char *) symtab;
+
+    DEBUG( DEBUG_ELF,
+           "%zu symbol table entries (%zu bytes) starting at %p",
+           symsz / sizeof(*symtab),
+           symsz,
+           symtab );
+
+    if( symsz % sizeof(*symtab) != 0 )
+        DEBUG( DEBUG_ELF, "%zu bytes left over?!", symsz % sizeof(*symtab) );
+
+    for( entry = entries;
+         (entry->d_tag != DT_NULL) &&
+           ((size == 0) || ((void *)entry < (start + base + size)));
+         entry++ ) {
+        switch( entry->d_tag )
+        {
+          // Please keep the contents of this switch in numerical order.
+
+          // IGNORE(x): Ignore a known d_tag that we believe we don't need
+          // to know about
+#define IGNORE(x) \
+          case x: \
+            DEBUG( DEBUG_ELF, "ignoring %s (0x%zx): 0x%zx", \
+                   #x, (size_t) x, (size_t) entry->d_un.d_val); \
+            break;
+
+          // ALREADY_DID(x): Silently ignore a case we dealt with in
+          // the previous loop
+#define ALREADY_DID(x) \
+          case x: \
+            break;
+
+          IGNORE( DT_NEEDED )
+          ALREADY_DID( DT_PLTRELSZ )
+          IGNORE( DT_PLTGOT )
+          IGNORE( DT_HASH )
+          IGNORE( DT_STRTAB )
+          ALREADY_DID( DT_SYMTAB )
+
+          case DT_RELA:
+            if( process_rela != NULL )
+            {
+                const ElfW(Rela) *relstart;
+
+                DEBUG( DEBUG_ELF, "processing DT_RELA section" );
+                if( relasz == (size_t) -1 )
+                {
+                    fprintf( stderr, "libcapsule: DT_RELA section not accompanied by DT_RELASZ, ignoring" );
+                    break;
+                }
+                relstart = fix_addr( base, entry->d_un.d_ptr );
+                process_rela( relstart, relasz, strtab, strsz, symtab, symsz, base, data );
+            }
+            else
+            {
+                DEBUG( DEBUG_ELF,
+                       "skipping DT_RELA section: no handler" );
+            }
+            break;
+
+          ALREADY_DID( DT_RELASZ )
+          IGNORE( DT_RELAENT )
+          IGNORE( DT_STRSZ )
+          IGNORE( DT_SYMENT )
+          IGNORE( DT_INIT )
+          IGNORE( DT_FINI )
+          IGNORE( DT_SONAME )
+          IGNORE( DT_RPATH )
+          IGNORE( DT_SYMBOLIC )
+
+          case DT_REL:
+            if( process_rel != NULL )
+            {
+                const ElfW(Rel) *relstart;
+
+                DEBUG( DEBUG_ELF, "processing DT_REL section" );
+                if( relsz == (size_t) -1 )
+                {
+                    fprintf( stderr, "libcapsule: DT_REL section not accompanied by DT_RELSZ, ignoring" );
+                    break;
+                }
+                relstart = fix_addr( base, entry->d_un.d_ptr );
+                process_rel( relstart, relsz, strtab, strsz, symtab, symsz, base, data );
+            }
+            else
+            {
+                DEBUG( DEBUG_ELF, "skipping DT_REL section: no handler" );
+            }
+            break;
+
+          ALREADY_DID( DT_RELSZ )
+          IGNORE( DT_RELENT )
+          ALREADY_DID( DT_PLTREL )
+          IGNORE( DT_DEBUG )
+          IGNORE( DT_TEXTREL )
+
+          case DT_JMPREL:
+            if( jmprelsz == (size_t) -1 )
+            {
+                fprintf( stderr, "libcapsule: DT_JMPREL section not accompanied by DT_PLTRELSZ, ignoring" );
+                break;
+            }
+
+            if( jmpreltype == DT_NULL )
+            {
+                fprintf( stderr, "libcapsule: DT_JMPREL section not accompanied by DT_PLTREL, ignoring" );
+                break;
+            }
+
+            switch( jmpreltype )
+            {
+              case DT_REL:
+                if( process_rel != NULL )
+                {
+                    const ElfW(Rel) *relstart;
+
+                    DEBUG( DEBUG_ELF,
+                           "processing DT_JMPREL/DT_REL section" );
+                    relstart = fix_addr( base, entry->d_un.d_ptr );
+                    DEBUG( DEBUG_ELF, "  -> REL entry #0 at %p", relstart );
+                    ret = process_rel( relstart, jmprelsz, strtab, strsz,
+                                       symtab, symsz, base, data );
+                }
+                else
+                {
+                    DEBUG( DEBUG_ELF,
+                           "skipping DT_JMPREL/DT_REL section: no handler" );
+                }
+                break;
+
+              case DT_RELA:
+                if( process_rela != NULL )
+                {
+                    const ElfW(Rela) *relstart;
+
+                    DEBUG( DEBUG_ELF,
+                           "processing DT_JMPREL/DT_RELA section" );
+                    relstart = fix_addr( base, entry->d_un.d_ptr );
+                    ret = process_rela( relstart, jmprelsz, strtab, strsz,
+                                        symtab, symsz, base, data );
+                }
+                else
+                {
+                    DEBUG( DEBUG_ELF,
+                           "skipping DT_JMPREL/DT_RELA section: no handler" );
+                }
+                break;
+
+              default:
+                DEBUG( DEBUG_ELF,
+                       "Unknown DT_PLTREL value: %d (expected %d or %d)",
+                       jmpreltype, DT_REL, DT_RELA );
+                ret = 1;
+                break;
+            }
+            break;
+
+          IGNORE( DT_BIND_NOW )
+          IGNORE( DT_INIT_ARRAY )
+          IGNORE( DT_FINI_ARRAY )
+          IGNORE( DT_INIT_ARRAYSZ )
+          IGNORE( DT_FINI_ARRAYSZ )
+          IGNORE( DT_RUNPATH )
+          IGNORE( DT_FLAGS )
+          // DT_ENCODING is numerically equal to DT_PREINIT_ARRAY
+          // so we can't separate them
+          case DT_ENCODING:
+            DEBUG( DEBUG_ELF,
+                   "ignoring DT_ENCODING or DT_PREINIT_ARRAY (0x%zx): 0x%zx",
+                   (size_t) DT_ENCODING, (size_t) entry->d_un.d_val); \
+            break;
+          IGNORE( DT_PREINIT_ARRAYSZ )
+          IGNORE( DT_NUM )
+
+          // OS-specifics, in range DT_LOOS to DT_HIOS
+
+          // Uses Dyn.d_un.d_val
+          IGNORE( DT_GNU_PRELINKED )
+          IGNORE( DT_GNU_CONFLICTSZ )
+          IGNORE( DT_GNU_LIBLISTSZ )
+          IGNORE( DT_CHECKSUM )
+          IGNORE( DT_PLTPADSZ )
+          IGNORE( DT_MOVEENT )
+          IGNORE( DT_MOVESZ )
+          IGNORE( DT_FEATURE_1 )
+          IGNORE( DT_POSFLAG_1 )
+          IGNORE( DT_SYMINSZ )
+          IGNORE( DT_SYMINENT )
+
+          // Uses Dyn.d_un.d_ptr
+          IGNORE( DT_GNU_HASH )
+          IGNORE( DT_TLSDESC_PLT )
+          IGNORE( DT_TLSDESC_GOT )
+          IGNORE( DT_GNU_CONFLICT )
+          IGNORE( DT_GNU_LIBLIST )
+          IGNORE( DT_CONFIG )
+          IGNORE( DT_DEPAUDIT )
+          IGNORE( DT_AUDIT )
+          IGNORE( DT_PLTPAD )
+          IGNORE( DT_MOVETAB )
+          IGNORE( DT_SYMINFO )
+
+          IGNORE( DT_VERSYM )
+
+          IGNORE( DT_RELACOUNT )
+          IGNORE( DT_RELCOUNT )
+
+          // Sun-compatible
+          IGNORE( DT_FLAGS_1 )
+          IGNORE( DT_VERDEF )
+          IGNORE( DT_VERDEFNUM )
+          IGNORE( DT_VERNEED )
+          IGNORE( DT_VERNEEDNUM )
+
+          // Sun-compatible
+          IGNORE( DT_AUXILIARY )
+          IGNORE( DT_FILTER )
+
+#undef IGNORE
+#undef ALREADY_DID
+
+          default:
+            DEBUG( DEBUG_ELF, "Ignoring unknown dynamic section entry tag 0x%zx",
+                   (size_t) entry->d_tag );
+            break;
+        }
+    }
+
+    return ret;
+}
+
diff --git a/subprojects/libcapsule/utils/process-pt-dynamic.h b/subprojects/libcapsule/utils/process-pt-dynamic.h
new file mode 100644
index 0000000000000000000000000000000000000000..d2305d5c360a30d9b9c4f9ffac36e104695523cc
--- /dev/null
+++ b/subprojects/libcapsule/utils/process-pt-dynamic.h
@@ -0,0 +1,116 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#include <dlfcn.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <link.h>
+#include "utils/utils.h"
+#include "mmap-info.h"
+
+// these macros are secretly the same for elf32 & elf64:
+#define ELFW_ST_TYPE(a)       ELF32_ST_TYPE(a)
+#define ELFW_ST_BIND(a)       ELF32_ST_BIND(a)
+#define ELFW_ST_VISIBILITY(a) ELF32_ST_VISIBILITY(a)
+
+typedef enum
+{
+    RELOCATION_FLAGS_NONE = 0,
+    RELOCATION_FLAGS_AVOID_LIBC = (1 << 0),
+} relocation_flags;
+
+typedef struct
+{
+    capsule_item *relocs;
+    struct { int success; int failure; } count;
+    int debug;
+    char *error;
+    mmapinfo *mmap_info;
+    relocation_flags flags;
+    ptr_list *seen;
+} relocation_data;
+
+/*
+ * relocate_rela_cb:
+ * @start: array of relocation entries
+ * @relasz: number of bytes (not number of structs!) following @start
+ * @strtab: string table, a series of 0-terminated strings concatenated
+ * @strsz: length of string table in bytes
+ * @symtab: symbol table, an array of ElfW(Sym) structs
+ * @symsz: length of symbol table in bytes
+ * @base: base address of the shared object
+ * @data: the same data that was passed to process_pt_dynamic()
+ *
+ * Callback used to iterate over relocations.
+ */
+typedef int (*relocate_rela_cb)(const ElfW(Rela) *start,
+                                  const size_t relasz,
+                                  const char *strtab,
+                                  const size_t strsz,
+                                  const ElfW(Sym) *symtab,
+                                  const size_t symsz,
+                                  void *base,
+                                  void *data);
+
+/*
+ * relocate_rel_cb:
+ * @start: beginning of an array of relocation entries
+ * @relasz: number of bytes (not number of structs!) following @start
+ * @strtab: string table, a series of 0-terminated strings concatenated
+ * @strsz: length of string table in bytes
+ * @symtab: symbol table, an array of ElfW(Sym) structs
+ * @symsz: length of symbol table in bytes
+ * @base: base address of the shared object
+ * @data: the same data that was passed to process_pt_dynamic()
+ *
+ * Callback used to iterate over relocations.
+ */
+typedef int (*relocate_rel_cb)(const ElfW(Rel) *start,
+                                 const size_t relasz,
+                                 const char *strtab,
+                                 const size_t strsz,
+                                 const ElfW(Sym) *symtab,
+                                 const size_t symsz,
+                                 void *base,
+                                 void *data);
+
+int process_dt_rela (const ElfW(Rela) *start,
+                     const size_t relasz,
+                     const char *strtab,
+                     const size_t strsz,
+                     const ElfW(Sym) *symtab,
+                     const size_t symsz,
+                     void *base,
+                     void *data);
+
+int process_dt_rel  (const ElfW(Rel) *start,
+                     const size_t relasz,
+                     const char *strtab,
+                     const size_t strsz,
+                     const ElfW(Sym) *symtab,
+                     const size_t symsz,
+                     void *base,
+                     void *data);
+
+int process_pt_dynamic (ElfW(Addr) start,
+                        size_t size,
+                        void *base,
+                        relocate_rela_cb process_rela,
+                        relocate_rel_cb process_rel,
+                        void *data);
diff --git a/subprojects/libcapsule/utils/tools.c b/subprojects/libcapsule/utils/tools.c
new file mode 100644
index 0000000000000000000000000000000000000000..32fcfcaa3b0d73fca391b4d8a25571f7b2c37d6c
--- /dev/null
+++ b/subprojects/libcapsule/utils/tools.c
@@ -0,0 +1,31 @@
+// Copyright © 2017-2019 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include "tools.h"
+
+#include <stdio.h>
+
+void
+_capsule_tools_print_version( const char *command )
+{
+    /* This is actually valid/parseable YAML, so that it's both
+     * human-readable and machine-readable. Inspired by `docker version`
+     * and `ostree --version`. */
+    printf( "%s%s:\n", HOST_PREFIX, command );
+    printf( " Package: %s\n", PACKAGE_TARNAME );
+    printf( " Version: '%s'\n", PACKAGE_VERSION );
+}
diff --git a/subprojects/libcapsule/utils/tools.h b/subprojects/libcapsule/utils/tools.h
new file mode 100644
index 0000000000000000000000000000000000000000..ac8f4f75e7d7b915c44cd0df867a50d599e9a4d2
--- /dev/null
+++ b/subprojects/libcapsule/utils/tools.h
@@ -0,0 +1,20 @@
+// Copyright © 2017-2019 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+void _capsule_tools_print_version( const char *command );
diff --git a/subprojects/libcapsule/utils/utils.c b/subprojects/libcapsule/utils/utils.c
new file mode 100644
index 0000000000000000000000000000000000000000..915836297378307e265065f7f4cfe255ce4c9d3c
--- /dev/null
+++ b/subprojects/libcapsule/utils/utils.c
@@ -0,0 +1,726 @@
+// Copyright © 2017 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#include <assert.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include "debug.h"
+#include "utils.h"
+
+unsigned long debug_flags;
+
+// ==========================================================================
+// these are for finding entries in the dynamic section
+// note that tha d_un.d_ptr member may be pre-offset by the
+// linker, or we may beed to adjust it by the value of base ourselves:
+// this is effectively private linker information and there's no
+// hard and fast rule:
+const void *
+fix_addr (const void *base, ElfW(Addr) offset_or_addr)
+{
+    if (offset_or_addr < (ElfW(Addr)) base)
+    {
+        // Assume it's an offset, so an address relative to addr
+        return base + offset_or_addr;
+    }
+    else
+    {
+        // Assume it's an absolute address
+        return (const void *) offset_or_addr;
+    }
+}
+
+const ElfW(Dyn) *
+find_dyn (ElfW(Addr) base, void *start, int what)
+{
+    ElfW(Dyn) *entry = start + base;
+
+    for( ; entry->d_tag != DT_NULL; entry++ )
+        if( entry->d_tag == what )
+            return entry;
+
+    return NULL;
+}
+
+size_t
+find_value (ElfW(Addr) base, void *start, int what)
+{
+    const ElfW(Dyn) *entry = find_dyn( base, start, what );
+    return entry ? entry->d_un.d_val : (size_t) -1;
+}
+
+ElfW(Addr)
+find_ptr (ElfW(Addr) base, void *start, int what)
+{
+    const ElfW(Dyn) *entry = find_dyn( base, start, what );
+
+    if( entry )
+    {
+        if( entry->d_un.d_ptr < base )
+            return base + entry->d_un.d_ptr;
+        else
+            return entry->d_un.d_ptr;
+    }
+
+    return (ElfW(Addr)) NULL;
+}
+
+/*
+ * dynamic_section_find_strtab:
+ * @start: Array of dynamic section entries
+ * @base: Starting address of the shared object in memory
+ * @siz: (out) (optional): Used to return the length of the string
+ *  table
+ *
+ * Find the string table for the given dynamic section.
+ *
+ * Returns: (nullable): The string table, a series of concatenated
+ *  0-terminated strings whose total size is written through @siz if
+ *  non-%NULL, or %NULL if not found
+ */
+const char *
+dynamic_section_find_strtab (const ElfW(Dyn) *entries, const void *base, size_t *siz)
+{
+    const ElfW(Dyn) *entry;
+    ElfW(Addr) stab = 0;
+
+    for( entry = entries; entry->d_tag != DT_NULL; entry++ )
+    {
+        if( entry->d_tag == DT_STRTAB )
+        {
+            stab = entry->d_un.d_ptr;
+        }
+        else if( entry->d_tag == DT_STRSZ  )
+        {
+            if( siz )
+                *siz = entry->d_un.d_val;
+        }
+    }
+
+    if (stab == 0)
+    {
+        return NULL;
+    }
+    else if (stab < (ElfW(Addr)) base)
+    {
+        return base + stab;
+    }
+    else
+    {
+        return (const char *) stab;
+    }
+}
+
+/*
+ * find_symbol:
+ * @idx: The symbol index
+ * @stab: (array element-type=ElfW(Sym)): The symbol table
+ * @symsz: The length of the symbol table in bytes
+ * @str: The string table
+ * @strsz: The length of the string table in bytes
+ * @name: (out) (optional): Used to return the name, a pointer into @str
+ *
+ * If @idx is in-bounds for @stab, return a pointer to the @idx'th
+ * entry in @stab. Otherwise return %NULL.
+ */
+const ElfW(Sym) *
+find_symbol (int idx, const ElfW(Sym) *stab, size_t symsz, const char *str, size_t strsz, const char **name)
+{
+    const ElfW(Sym) *target = stab + idx;
+
+    if( idx < 0 )
+        return NULL;
+
+    // we could just accept the index as legitimate but then we'd
+    // run the risk of popping off into an unknown hyperspace coordinate
+    // this way we stop if the target is past the known end of the table:
+    if( (idx + 1) * sizeof(*target) > symsz )
+        return NULL;
+
+    if( name )
+        *name = str + target->st_name;
+
+    return target;
+}
+
+// ==========================================================================
+
+// like strncpy except it guarantees the final byte is NUL in
+// case we truncated the string. does not yet warn you in any
+// way about truncation though, should probably fix that:
+char *safe_strncpy (char *dest, const char *src, size_t n)
+{
+    char *rv = strncpy( dest, src, n - 1 );
+    dest[ n - 1 ] = '\0';
+    return rv;
+}
+
+// prefix is the root of the external tree we're patching in
+// with libcapsule, path is what we're trying to resolve if
+// it is a symlink. path must have at least PATH_MAX chars allocated.
+//
+// Designed to be called repeatedly, starting with an ABSOLUTE
+// path the first time. Will write the resolved link back into
+// path each time and return true, until fed a path which is
+// not a symlink, at which point it will leave path alone and
+// return false:
+int resolve_link(const char *prefix, char path[PATH_MAX])
+{
+    int dfd;
+    char rl[PATH_MAX];
+    char dir[PATH_MAX];
+    char *end = NULL;
+    int rv = 0;
+
+    safe_strncpy( dir, path, PATH_MAX );
+    end = strrchr( dir, '/' );
+
+    if( end )
+        *end = '\0';
+    else
+        strcpy( dir, "." ); // not sure this is right, FIXME?
+                            // but as long as the first call to us
+                            // in any sequence was an absolute path
+                            // this will never come up
+    dfd = open( dir, O_RDONLY );
+
+    if( dfd < 0 )
+        return 0;
+
+    rv = readlinkat( dfd, path, rl, sizeof(rl) );
+
+    if( rv == sizeof(rl) )
+    {
+        // Either rl was truncated, or there is no longer space for '\0'
+        rv = -1;
+        goto out;
+    }
+
+    if( rv >= 0 )
+    {
+        rl[ rv ] = '\0';
+
+        if( rl[0] == '/' )
+        {
+            if( build_filename( path, PATH_MAX, prefix, rl, NULL ) >= PATH_MAX )
+            {
+                goto out;
+            }
+        }
+        else
+        {
+            if( build_filename( path, PATH_MAX, dir, rl, NULL ) >= PATH_MAX )
+            {
+                goto out;
+            }
+        }
+    }
+
+out:
+    close( dfd );
+
+    return rv != -1;
+}
+
+// todo - check this properly for actual word boundaries and
+// make it warn about unknown debug keywords:
+void set_debug_flags (const char *control)
+{
+    debug_flags = DEBUG_NONE;
+
+    if ( !control )
+        return;
+
+    if( strstr( control, "path"     ) ) debug_flags |= DEBUG_PATH;
+    if( strstr( control, "search"   ) ) debug_flags |= DEBUG_SEARCH;
+    if( strstr( control, "ldcache"  ) ) debug_flags |= DEBUG_LDCACHE;
+    if( strstr( control, "capsule"  ) ) debug_flags |= DEBUG_CAPSULE;
+    if( strstr( control, "mprotect" ) ) debug_flags |= DEBUG_MPROTECT;
+    if( strstr( control, "wrappers" ) ) debug_flags |= DEBUG_WRAPPERS;
+    if( strstr( control, "reloc"    ) ) debug_flags |= DEBUG_RELOCS;
+    if( strstr( control, "elf"      ) ) debug_flags |= DEBUG_ELF;
+    if( strstr( control, "dlfunc"   ) ) debug_flags |= DEBUG_DLFUNC;
+    if( strstr( control, "tool"     ) ) debug_flags |= DEBUG_TOOL;
+    if( strstr( control, "all"      ) ) debug_flags |= DEBUG_ALL;
+
+    if( !debug_flags )
+        return;
+
+    fprintf(stderr, "capsule debug flags: \n"
+            "  path    : %c # path manipulation and translation"           "\n"
+            "  search  : %c # searching for DSOs"                          "\n"
+            "  ldcache : %c # loading/processing the ld cache"             "\n"
+            "  capsule : %c # setting up the proxy capsule"                "\n"
+            "  mprotect: %c # handling mprotect (for RELRO)"               "\n"
+            "  wrappers: %c # function wrappers installed in the capsule"  "\n"
+            "  reloc   : %c # patching capsule symbols into external DSOs" "\n"
+            "  dlfunc  : %c # special handling of dlopen/dlsym calls"      "\n"
+            "  elf     : %c # detailed ELF introspection logging"          "\n"
+            "  tool    : %c # command-line tools"                          "\n",
+            (debug_flags & DEBUG_PATH    ) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_SEARCH  ) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_LDCACHE ) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_CAPSULE ) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_MPROTECT) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_WRAPPERS) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_RELOCS  ) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_DLFUNC  ) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_ELF     ) ? 'Y' : 'n' ,
+            (debug_flags & DEBUG_TOOL    ) ? 'Y' : 'n' );
+}
+
+// soname: bare libfoo.so.X style name
+// path: [possibly absolute] path to DSO
+// return true if soname: libFOO.so.X matches
+// path: /path/to/libFOO.so.X.Y or /path/to/libFOO.so.X
+int soname_matches_path (const char *soname, const char *path)
+{
+    const char *path_soname = strrchr( path, '/' );
+    const char *pattern = path_soname ? path_soname + 1: path;
+    const size_t slen = strlen( soname );
+
+    if( strncmp( soname, pattern, slen ) != 0 )
+        return 0;
+
+    const char *end = pattern + slen;
+
+    return ( *end == '\0' || *end == '.' );
+}
+
+/*
+ * build_filename_va:
+ * @buf: a buffer
+ * @len: length of buffer
+ * @first_path: an absolute or relative path
+ * @ap: further path segments
+ *
+ * Fill @buf with a copy of @first_path, with subsequent path segments
+ * appended to it.
+ *
+ * Returns: The number of bytes that would have been used in buf, not
+ *  including the '\0', if there was enough space. If this is >= len,
+ *  then truncation has occurred.
+ */
+size_t
+build_filename_va (char *buf, size_t len, const char *first_path, va_list ap)
+{
+    const char *path;
+    size_t used = 0;
+    int first = 1;
+    int need_separator = 0;
+
+
+    if( len > 0 )
+        *buf = '\0';
+
+    for( path = first_path, first = 1;
+         path != NULL;
+         path = va_arg( ap, const char * ), first = 0)
+    {
+        size_t path_len;
+
+        // Collapse any leading '//' to '/'
+        while( path[0] == '/' && path[1] == '/' )
+            path++;
+
+        // If this is not the first path segment, strip any leading '/'
+        if( path[0] == '/' && !first )
+            path++;
+
+        path_len = strlen( path );
+
+        // Collapse any trailing '/' to nothing, unless this is the
+        // first path segment, in which case collapse them to just '/'
+        while( path_len > (first ? 1 : 0) && path[path_len - 1] == '/' )
+        {
+            path_len--;
+        }
+
+        // Ensure there is a '/' before we append path if necessary
+        if( need_separator )
+        {
+            used++;
+            if( used < len )
+            {
+                buf[used - 1] = '/';
+                buf[used] = '\0';
+            }
+        }
+
+        // If there's still any space left, try to append the path
+        if( used < len )
+        {
+            strncpy( buf + used, path, MIN( len - used, path_len + 1 ) );
+            buf[len - 1] = '\0';
+        }
+
+        used += path_len;
+
+        // Next time, we need to append a separator, unless this was
+        // the first path segment and it was '/'
+        need_separator = (path_len == 0 || path[path_len - 1] != '/');
+    }
+
+    va_end( ap );
+    return used;
+}
+
+/*
+ * build_filename:
+ * @buf: a buffer
+ * @len: length of buffer
+ * @first_path: an absolute or relative path
+ * @...: further path segments
+ *
+ * Fill @buf with a copy of @first_path, with subsequent path segments
+ * appended to it.
+ *
+ * Returns: The number of bytes that would have been used in buf, not
+ *  including the '\0', if there was enough space. If this is >= len,
+ *  then truncation has occurred.
+ */
+size_t
+build_filename (char *buf, size_t len, const char *first_path, ...)
+{
+    size_t used;
+    va_list ap;
+
+    va_start( ap, first_path );
+    used = build_filename_va( buf, len, first_path, ap );
+    va_end( ap );
+
+    return used;
+}
+
+/*
+ * build_filename_alloc:
+ * @first_path: an absolute or relative path
+ * @...: further path segments
+ *
+ * Allocate and return a string built from @first_path and subsequent
+ * segments. Abort if there is not enough memory.
+ *
+ * Returns: A string that can be freed with free()
+ */
+char *
+build_filename_alloc (const char *first_path, ...)
+{
+    char *buf;
+    size_t allocate;
+    size_t len;
+    va_list ap;
+
+    // Do a first pass to count how much space we're going to need
+    va_start( ap, first_path );
+    // We need an extra byte for the "\0" which isn't included in the
+    // result, consistent with strlcpy()
+    allocate = build_filename_va( NULL, 0, first_path, ap ) + 1;
+    va_end( ap );
+
+    buf = xrealloc( NULL, allocate );
+
+    // Iterate over the arguments again to fill buf
+    va_start( ap, first_path );
+    len = build_filename_va( buf, allocate, first_path, ap );
+    va_end( ap );
+
+    // build_filename_va() returns the same thing every time. In
+    // particular this means truncation did not occur, because that
+    // would be indicated by len >= allocate.
+    assert( len + 1 == allocate );
+
+    return buf;
+}
+
+ptr_list *
+ptr_list_alloc(size_t size)
+{
+    ptr_list *list = xcalloc( 1, sizeof(ptr_list) );
+
+    list->loc = xcalloc( size, sizeof(ptr_item) );
+    list->allocated = size;
+    list->next = 0;
+    return list;
+}
+
+/*
+ * ptr_list_free_to_array:
+ * @list: (transfer full): list to free
+ * @n: (out) (optional): if not %NULL, used to return the number of
+ *  items that were in @list
+ *
+ * Add an extra %NULL entry to @list, then return an array with the
+ * same contents as @list, freeing @list. If @n is not %NULL, set it
+ * to the number of elements in the returned array before the %NULL
+ * added by this function.
+ */
+void **
+ptr_list_free_to_array (ptr_list *list, size_t *n)
+{
+    void *ret;
+
+    // Without these assertions, it's unsafe to assign a ptr_item *
+    // to a void **
+    static_assert( alignof( void * ) == alignof( ptr_item ),
+                   "union {void *, ElfW(Addr)} assumed to be same size "
+                   "as void *");
+    static_assert( sizeof( void * ) == sizeof( ptr_item ),
+                   "union {void *, ElfW(Addr)} assumed to be same "
+                   "alignment as void *");
+
+    if( n != NULL )
+        *n = list->next;
+
+    ptr_list_push_ptr( list, NULL );
+    ret = list->loc;
+    list->loc = NULL;
+    ptr_list_free (list);
+    return ret;
+}
+
+void
+ptr_list_free (ptr_list *list)
+{
+    free( list->loc );
+    list->loc = NULL;
+    list->allocated = 0;
+    list->next = 0;
+    free( list );
+}
+
+void
+ptr_list_push_addr (ptr_list *list, ElfW(Addr) addr)
+{
+    if( list->next >= list->allocated )
+    {
+        list->loc = xrealloc( list->loc,
+                             (list->allocated + 16) * sizeof(ptr_item) );
+        list->allocated += 16;
+    }
+
+    list->loc[ list->next++ ].addr = addr;
+}
+
+
+void
+ptr_list_push_ptr (ptr_list *list, void *ptr)
+{
+    if( list->next >= list->allocated )
+    {
+        list->loc = realloc( list->loc,
+                             (list->allocated + 16) * sizeof(ptr_item) );
+        if( !list->loc )
+        {
+            fprintf( stderr, "failed to realloc ptr_list\n" );
+            abort();
+        }
+
+        list->allocated += 16;
+    }
+
+    list->loc[ list->next++ ].ptr = ptr;
+}
+
+int
+ptr_list_add_ptr (ptr_list *list, void *ptr, ptrcmp equals)
+{
+    for( size_t n = 0; n < list->next; n++ )
+        if( equals( list->loc[ n ].ptr, ptr ) )
+            return 0;
+
+    ptr_list_push_ptr( list, ptr );
+
+    return 1;
+}
+
+int
+ptr_list_contains (ptr_list *list, ElfW(Addr) addr)
+{
+    if( list->next == 0 )
+        return 0;
+
+    for( size_t n = 0; n < list->next; n++ )
+        if( list->loc[ n ].addr == addr )
+            return 1;
+    return 0;
+}
+
+void *
+ptr_list_nth_ptr (ptr_list *list, size_t nth)
+{
+    if( nth < list->next )
+        return list->loc[ nth ].ptr;
+
+    return NULL;
+}
+
+void
+oom( void )
+{
+    fprintf( stderr, "libcapsule: out of memory\n" );
+    abort();
+}
+
+char *
+xstrdup( const char *s )
+{
+    char *ret = strdup( s );
+
+    if (s != NULL && ret == NULL)
+        oom();
+
+    return ret;
+}
+
+void *
+xrealloc( void *ptr, size_t size )
+{
+    void *ret = realloc( ptr, size );
+
+    if( ptr != NULL && size != 0 && ret == NULL )
+        oom();
+
+    return ret;
+}
+
+void *
+xcalloc( size_t n, size_t size )
+{
+    void *ret = calloc( n, size );
+
+    if( n != 0 && size != 0 && ret == NULL )
+        oom();
+
+    return ret;
+}
+
+int
+xasprintf( char **dest, const char *format, ...)
+{
+    int ret;
+    va_list ap;
+
+    va_start( ap, format );
+    ret = vasprintf( dest, format, ap );
+    va_end( ap );
+
+    if( ret < 0 )
+        oom();
+
+    return ret;
+}
+
+/**
+ * free_strv_full:
+ * @strings_array: (nullable) (array zero-terminated=1): array of strings
+ *  to free
+ *
+ * Completely frees the given @strings_array.
+ * If @strings_array is %NULL, this function simply returns.
+ */
+void
+free_strv_full( char **strings_array )
+{
+    if( strings_array )
+    {
+        for( size_t i = 0; strings_array[i] != NULL; i++ )
+            free( strings_array[i] );
+        free( strings_array );
+    }
+}
+
+/*
+ * _capsule_set_error_literal:
+ * @code_dest: (out) (optional): used to return an errno value
+ * @message_dest: (out) (optional) (transfer full): used to return an
+ *  error message
+ * @code: an errno value
+ * @message: an error message
+ *
+ * Set an error code, like g_set_error_literal().
+ */
+void
+_capsule_set_error_literal( int *code_dest, char **message_dest,
+                            int code, const char *message )
+{
+    if( code_dest != NULL )
+        *code_dest = code;
+
+    if( message_dest != NULL )
+        *message_dest = xstrdup( message );
+}
+
+/*
+ * _capsule_set_error:
+ * @code_dest: (out) (optional): used to return an errno value
+ * @message_dest: (out) (optional) (transfer full): used to return an
+ *  error message
+ * @code: an errno value
+ * @message: an error message
+ *
+ * Set an error code, like g_set_error().
+ */
+void
+_capsule_set_error( int *code_dest, char **message_dest,
+                    int code, const char *format,
+                    ... )
+{
+    va_list ap;
+
+    if( code_dest != NULL )
+        *code_dest = code;
+
+    if( message_dest != NULL )
+    {
+        va_start( ap, format );
+
+        if( vasprintf( message_dest, format, ap ) < 0 )
+            oom();
+
+        va_end( ap );
+    }
+}
+
+/*
+ * _capsule_basename:
+ * @path: A path
+ *
+ * Return the part of @path after the last directory separator, or @path
+ * if there is no directory separator at all, or an empty string if @path
+ * ends with a directory separator.
+ *
+ * Equivalent to GNU basename(3) from string.h, but not POSIX
+ * basename(3) from libgen.h.
+ *
+ * Returns: (transfer none): A pointer into @path
+ */
+const char *
+_capsule_basename (const char *path)
+{
+  const char *ret = strrchr( path, '/' );
+
+  if( ret == NULL )
+      return path;
+
+  assert( ret[0] == '/' );
+  return ret + 1;
+}
diff --git a/subprojects/libcapsule/utils/utils.h b/subprojects/libcapsule/utils/utils.h
new file mode 100644
index 0000000000000000000000000000000000000000..66316b0cd25b7b464429adade6fa47bacb694a85
--- /dev/null
+++ b/subprojects/libcapsule/utils/utils.h
@@ -0,0 +1,238 @@
+// Copyright © 2017-2020 Collabora Ltd
+
+// This file is part of libcapsule.
+
+// libcapsule is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as
+// published by the Free Software Foundation; either version 2.1 of the
+// License, or (at your option) any later version.
+
+// libcapsule 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 Lesser General Public License for more details.
+
+// You should have received a copy of the GNU Lesser General Public
+// License along with libcapsule.  If not, see <http://www.gnu.org/licenses/>.
+
+#pragma once
+
+#include <assert.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdlib.h>
+
+#include <link.h> // for __ELF_NATIVE_CLASS
+
+#include "debug.h"
+
+#ifdef HAVE_STDALIGN_H
+#include <stdalign.h>
+#endif
+
+#define UNLIKELY(x) __builtin_expect(x, 0)
+#define LIKELY(x)   __builtin_expect(x, 1)
+
+// these macros are secretly the same for elf32 & elf64:
+#define ELFW_ST_TYPE(a)       ELF32_ST_TYPE(a)
+#define ELFW_ST_BIND(a)       ELF32_ST_BIND(a)
+#define ELFW_ST_VISIBILITY(a) ELF32_ST_VISIBILITY(a)
+
+#if __ELF_NATIVE_CLASS == 64
+#define FMT_OFF   "lu"
+#define FMT_SWORD "lu"
+#define FMT_WORD  "ld"
+#define FMT_SIZE  "lu"
+#define FMT_ADDR  "ld"
+#define FMT_XADDR "lx"
+#define FMT_XU64  "lx"
+#else
+#define FMT_OFF   "u"
+#define FMT_SWORD "u"
+#define FMT_WORD  "d"
+#define FMT_SIZE  "u"
+#define FMT_ADDR  "d"
+#define FMT_XADDR "x"
+#define FMT_XU64  "llx"
+#endif
+
+typedef union ptr_item
+{
+    ElfW(Addr) addr;
+    void *ptr;
+} ptr_item;
+
+typedef struct ptr_list
+{
+    size_t allocated;
+    size_t next;
+    ptr_item *loc;
+} ptr_list;
+
+typedef int (*ptrcmp) (const void *a, const void *b);
+
+ptr_list *ptr_list_alloc (size_t size);
+void *ptr_list_nth_ptr (ptr_list *list, size_t nth);
+void ptr_list_free (ptr_list *list);
+void **ptr_list_free_to_array (ptr_list *list, size_t *n);
+void ptr_list_push_ptr  (ptr_list *list, void *ptr);
+void ptr_list_push_addr (ptr_list *list, ElfW(Addr) addr);
+int  ptr_list_contains  (ptr_list *list, ElfW(Addr) addr);
+int  ptr_list_add_ptr   (ptr_list *list, void *ptr, ptrcmp equals);
+
+#define strstarts(str, start) \
+  (strncmp( str, start, strlen( start ) ) == 0)
+
+char *safe_strncpy (char *dest, const char *src, size_t n);
+int   resolve_link (const char *prefix, char path[PATH_MAX]);
+int soname_matches_path (const char *soname, const char *path);
+size_t build_filename_va (char *buf, size_t len, const char *first_path, va_list ap);
+size_t build_filename (char *buf, size_t len, const char *first_path, ...) __attribute__((sentinel));
+char *build_filename_alloc (const char *first_path, ...) __attribute__((sentinel));
+
+const void *      fix_addr (const void *base, ElfW(Addr) offset_or_addr);
+const ElfW(Dyn) * find_dyn (ElfW(Addr) base, void *start, int what);
+size_t            find_value (ElfW(Addr) base, void *start, int what);
+ElfW(Addr)        find_ptr (ElfW(Addr) base, void *start, int what);
+const char *      dynamic_section_find_strtab (const ElfW(Dyn) *entries, const void *base, size_t *siz);
+const ElfW(Sym) * find_symbol (int idx,
+                               const ElfW(Sym) *stab,
+                               size_t symsz,
+                               const char *str,
+                               size_t strsz,
+                               const char **name);
+
+void oom( void ) __attribute__((noreturn));
+char *xstrdup( const char *s );
+void *xrealloc( void *ptr, size_t size ) __attribute__((alloc_size(2)));
+void *xcalloc( size_t n, size_t size ) __attribute__((alloc_size(1, 2), malloc));
+int xasprintf( char **s, const char *format, ...) __attribute__((format(printf, 2, 3)));
+void free_strv_full( char **strings_array );
+
+/*
+ * _capsule_steal_pointer:
+ * @pp: A non-%NULL pointer to any pointer type (void ** or Something **)
+ *
+ * Transfer the contents of @pp to the caller, setting @pp to %NULL.
+ * This is essentially the same thing as g_steal_pointer().
+ */
+static inline void *
+_capsule_steal_pointer( void *pp )
+{
+    typedef void *__attribute__((may_alias)) voidp_alias;
+    voidp_alias *pointer_to_pointer = pp;
+    void *ret = *pointer_to_pointer;
+    *pointer_to_pointer = NULL;
+    return ret;
+}
+
+/*
+ * _capsule_propagate_error:
+ * @code_dest: (out) (optional): where to put the error code (errno), or %NULL
+ * @message_dest: (out) (transfer full) (optional) (nullable): where to
+ *  put the message, or %NULL
+ * @code_src: the error code (errno)
+ * @message_src: (nullable) (transfer full): the error message
+ *
+ * This is essentially the same thing as g_propagate_error(), and leaves
+ * message_src undefined. It should typically be used as:
+ *
+ *     _capsule_propagate_error( &code, &message, local_code,
+ *                               _capsule_steal_pointer( &local_message ) );
+ */
+static inline void
+_capsule_propagate_error( int *code_dest, char **message_dest, int code_src, char *message_src )
+{
+    if( code_dest != NULL )
+        *code_dest = code_src;
+
+    if( message_dest != NULL )
+        *message_dest = message_src;
+    else
+        free( message_src );
+}
+
+void _capsule_set_error_literal( int *code_dest, char **message_dest,
+                                 int code, const char *message );
+
+void _capsule_set_error( int *code_dest, char **message_dest,
+                         int code, const char *format, ... )
+    __attribute__((format(printf, 4, 5)));
+
+/*
+ * N_ELEMENTS:
+ * @array: A fixed-size array (not a pointer!)
+ *
+ * Same as `G_N_ELEMENTS`, `_DBUS_N_ELEMENTS`, systemd `ELEMENTSOF`, etc.
+ */
+#define N_ELEMENTS(array) ( sizeof(array) / sizeof(array[0]) )
+
+/*
+ * _capsule_clear:
+ * @pp: A pointer to a pointer that can be freed by Standard C `free()`,
+ *  i.e. type `void **`, `char **` or more rarely `something **`
+ *
+ * Free whatever object is pointed to by `*pp`, and set `*pp` to NULL.
+ */
+static inline void
+_capsule_clear( void *pp )
+{
+    free( _capsule_steal_pointer( pp ) );
+}
+
+/*
+ * _capsule_cleanup:
+ * @clear:
+ *
+ * An attribute marking a variable to be cleared by `clear(&variable)`
+ * on exit from its scope.
+ */
+#define _capsule_cleanup(clear) __attribute__((cleanup(clear)))
+
+/*
+ * _capsule_autofree:
+ *
+ * An attribute marking a variable to be freed by `free(variable)`
+ * on exit from its scope.
+ */
+#define _capsule_autofree _capsule_cleanup(_capsule_clear)
+
+/*
+ * alignof:
+ * @type: A type
+ *
+ * The same as in Standard C: return the minimal alignment of a type.
+ *
+ * Note that this is not the same as gcc __alignof__, which returns the
+ * type's *preferred* alignment, which is sometimes greater than the
+ * *minimal* alignment returned by this macro.
+ */
+#ifndef alignof
+# define alignof(type) (__builtin_offsetof(struct { char a; type b; }, b))
+#endif
+
+/*
+ * offsetof:
+ * @t: A `struct` type
+ * @m: A member
+ *
+ * The same as in Standard C: return the offset of @m within @t.
+ */
+#ifndef offsetof
+# define offsetof(t, m) (__builtin_offsetof(t, m))
+#endif
+
+/*
+ * static_assert:
+ * @expr: An expression to evaluate at compile-time
+ * @message: A diagnostic message used if @expr is not true
+ *
+ * The same as in Standard C: evaluate @expr as a compile-time constant
+ * expression, and fail to build if it is zero.
+ */
+#ifndef static_assert
+# define static_assert(expr, message) _Static_assert(expr, message)
+#endif
+
+const char *_capsule_basename (const char *path);
diff --git a/tests/pressure-vessel/containers.py b/tests/pressure-vessel/containers.py
index cc1261aec62454c12fbb792c101e9d30bc9d343a..1e25697dcb5a8fa527285bd5577a030f52e40e5a 100755
--- a/tests/pressure-vessel/containers.py
+++ b/tests/pressure-vessel/containers.py
@@ -271,35 +271,8 @@ class TestContainers(BaseTest):
                     symlinks=True,
                 )
 
-            fallback_libcapsule_tools = os.path.join(
-                cls.containers_dir,
-                'steam-runtime', 'usr', 'lib', 'libcapsule', 'relocatable',
-            )
-
-            if not os.path.isdir(fallback_libcapsule_tools):
-                fallback_libcapsule_tools = '/usr/lib/libcapsule/relocatable'
-
             for multiarch in ('i386-linux-gnu', 'x86_64-linux-gnu'):
-                for tool in ('capsule-capture-libs', 'capsule-symbols'):
-                    exe = multiarch + '-' + tool
-                    tool_path = os.path.join(cls.pv_dir, 'bin', exe)
-                    found = shutil.which(exe)
-                    relocatable = os.path.join(
-                        os.environ.get(
-                            'PRESSURE_VESSEL_LIBCAPSULE_TOOLS',
-                            fallback_libcapsule_tools,
-                        ),
-                        exe,
-                    )
-
-                    if found is not None:
-                        cls.copy2(found, tool_path)
-                    elif os.path.isfile(relocatable):
-                        cls.copy2(relocatable, tool_path)
-                    else:
-                        raise unittest.SkipTest('{} not found'.format(exe))
-
-                for tool in ('inspect-library',):
+                for tool in ('inspect-library', 'capsule-capture-libs'):
                     exe = multiarch + '-' + tool
                     tool_path = os.path.join(
                         cls.pv_dir,
diff --git a/tests/pressure-vessel/relocatable-install.py b/tests/pressure-vessel/relocatable-install.py
index cc8270b8566ca2babfa7516b719d61e63164c077..6fbe2f002b61906f1c1d6ebec2ca6a06325e11b5 100755
--- a/tests/pressure-vessel/relocatable-install.py
+++ b/tests/pressure-vessel/relocatable-install.py
@@ -84,9 +84,8 @@ EXES = [
 WRAPPED = [
     'bwrap',
 ]
-MULTIARCH = [
+HELPERS = [
     'capsule-capture-libs',
-    'capsule-symbols',
 ]
 SCRIPTS = [
     'pressure-vessel-unruntime',
@@ -200,10 +199,15 @@ def main():
         path = os.path.join(relocatable_install, 'bin', exe)
         check_dependencies(test, relocatable_install, path, is_wrapper=True)
 
-    for basename in MULTIARCH:
+    for basename in HELPERS:
         for multiarch, ld_so in LD_SO:
             exe = '{}-{}'.format(multiarch, basename)
-            path = os.path.join(relocatable_install, 'bin', exe)
+            path = os.path.join(
+                relocatable_install,
+                'libexec',
+                'steam-runtime-tools-0',
+                exe,
+            )
 
             if isexec(path):
                 test.ok('{} exists and is executable'.format(path))