From 00e16f2ff5012c6337986adcbdf5fde4deb54e9c Mon Sep 17 00:00:00 2001
From: Simon McVittie <smcv@collabora.com>
Date: Fri, 10 Nov 2017 13:06:08 +0000
Subject: [PATCH] tests: use nm -B (like libtool does) to enumerate symbols

This protects us from potential bugs in capsule-symbols(1) by
having something else to cross-check our results against.
It also means we can inspect the generated shims for libraries that
we do not actually have and hence cannot load, such as libGL.so.1 on
autobuilders that do not have a graphics stack installed.

Signed-off-by: Simon McVittie <smcv@collabora.com>
---
 Makefile.am              |  4 +-
 debian/copyright         | 24 ++++++++++++
 tests/CapsuleTest.pm     | 57 ++++++++++++++++++++++++++++
 tests/CapsuleTestDpkg.pm | 81 ++++++++++++++++++++++++++++++++++++++++
 tests/gl-shim.pl         | 38 ++++++++++++-------
 tests/init-project.pl    | 11 +++++-
 6 files changed, 199 insertions(+), 16 deletions(-)
 create mode 100644 tests/CapsuleTestDpkg.pm

diff --git a/Makefile.am b/Makefile.am
index 67f6c0ed6..8107c736e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -152,7 +152,8 @@ nobase_insttests_LTLIBRARIES         = tests/red/libhelper.la                  \
                                        tests/lib/libnotgles.la                 \
                                        tests/shim/libnotgl.la                  \
                                        tests/shim/libnotgles.la
-nobase_dist_insttests_DATA           = tests/CapsuleTest.pm
+nobase_dist_insttests_DATA           = tests/CapsuleTest.pm                    \
+                                       tests/CapsuleTestDpkg.pm
 # Install the source code so we can rebuild it inside a container if we want to
 nobase_dist_insttests_DATA          += examples/shim/libGL.so.1.excluded       \
                                        examples/shim/libGL.so.1.shared         \
@@ -295,6 +296,7 @@ AM_TESTS_ENVIRONMENT                 = export CAPSULE_CFLAGS="-I$(abs_top_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";
 test_extra_programs                  = tests/notgl-user                        \
                                        tests/notgl-helper-user                 \
diff --git a/debian/copyright b/debian/copyright
index 9a8ec1d83..4c25d9d7e 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -10,6 +10,13 @@ Copyright:
  © 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:
@@ -40,6 +47,23 @@ 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
diff --git a/tests/CapsuleTest.pm b/tests/CapsuleTest.pm
index 572fca880..1097239fe 100644
--- a/tests/CapsuleTest.pm
+++ b/tests/CapsuleTest.pm
@@ -14,6 +14,8 @@
 #
 # 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;
 
@@ -28,12 +30,14 @@ use Test::More;
 
 our @EXPORT = qw(
     diag_multiline
+    get_symbols_with_nm
     run_ok
     run_verbose
     skip_all_unless_bwrap
     $CAPSULE_INIT_PROJECT_TOOL
     $CAPSULE_SYMBOLS_TOOL
     $CAPSULE_VERSION_TOOL
+    $NM
     $PKG_CONFIG
     $builddir
     $srcdir
@@ -99,6 +103,18 @@ unless (defined $CAPSULE_VERSION_TOOL) {
     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
@@ -192,6 +208,43 @@ sub skip_all_unless_bwrap {
     }
 }
 
+=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;
+            require CapsuleTestDpkg;
+            next if CapsuleTestDpkg::symbol_is_blacklisted($symbol);
+            next if "\@\@$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;
+}
+
 =back
 
 =head1 ENVIRONMENT
@@ -221,6 +274,10 @@ if (length $ENV{CAPSULE_TESTS_KEEP_TEMP}) {
 
 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)
diff --git a/tests/CapsuleTestDpkg.pm b/tests/CapsuleTestDpkg.pm
new file mode 100644
index 000000000..dd23d788d
--- /dev/null
+++ b/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/tests/gl-shim.pl b/tests/gl-shim.pl
index 4c92d9c6f..8db5fc17f 100755
--- a/tests/gl-shim.pl
+++ b/tests/gl-shim.pl
@@ -44,31 +44,41 @@ if (length $ENV{CAPSULE_TESTS_UNINSTALLED}) {
 
 my $examples = "$srcdir/examples";
 
+my @sonames = qw(libGL.so.1 libX11.so.6 libXext.so.6 libxcb-dri2.so.0
+    libxcb-glx.so.0 libxcb-present.so.0 libxcb-sync.so.1 libxcb.so.1);
+
 run_ok([$CAPSULE_INIT_PROJECT_TOOL,
         '--runtime-tree=/run/host',
         '--set-version=1.0.0',
         "--symbols-from=$examples/shim",
-        'libGL.so.1',
-        'libX11.so.6',
-        'libXext.so.6',
-        'libxcb-dri2.so.0',
-        'libxcb-glx.so.0',
-        'libxcb-present.so.0',
-        'libxcb-sync.so.1',
-        'libxcb.so.1',
+        @sonames,
     ]);
 run_ok([
         'sh', '-euc', 'cd "$1"; shift; ./configure "$@"',
         'sh', "$test_tempdir/libGL-proxy",
     ], '>&2');
 run_ok(['make', '-C', "$test_tempdir/libGL-proxy", 'V=1'], '>&2');
-ok(-e "$test_tempdir/libGL-proxy/libGL.la");
-ok(-e "$test_tempdir/libGL-proxy/.libs/libGL.so");
-ok(-e "$test_tempdir/libGL-proxy/.libs/libGL.so.1");
-ok(-e "$test_tempdir/libGL-proxy/.libs/libGL.so.1.0.0");
 
-# TODO: I can't run capsule-symbols on the generated proxy on Debian
-# unstable, possibly caused by glvnd libGL?
+foreach my $soname (@sonames) {
+    my $basename = $soname;
+    $basename =~ s/\.so\..*$//;
+
+    ok(-e "$test_tempdir/libGL-proxy/$basename.la");
+    ok(-e "$test_tempdir/libGL-proxy/.libs/$basename.so");
+    ok(-e "$test_tempdir/libGL-proxy/.libs/$soname");
+    ok(-e "$test_tempdir/libGL-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/libGL-proxy/.libs/$soname");
+    is_deeply \@symbols_wanted, [grep {! m/^capsule_meta $/} @symbols_produced];
+}
 
 chdir '/';
 done_testing;
diff --git a/tests/init-project.pl b/tests/init-project.pl
index afa9d2934..36b6a75a7 100755
--- a/tests/init-project.pl
+++ b/tests/init-project.pl
@@ -104,13 +104,18 @@ sub uniq {
     return @unique;
 }
 
+my @symbols_produced =
+    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);
-my @symbols_produced = grep { !/capsule_meta\b/ } sort(split /\n/, $output);
+@symbols_produced = grep { !/capsule_meta\b/ } sort(split /\n/, $output);
 foreach my $sym (@symbols_produced) {
     diag "- $sym";
 }
@@ -190,6 +195,10 @@ run_ok(['make', '-C', "$test_tempdir/libz-proxy", 'V=1',
 }
 run_ok(['make', '-C', "$test_tempdir/libz-proxy", 'V=1'], '>&2');
 
+@symbols_produced =
+    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);
-- 
GitLab