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);