1 #! /usr/bin/env perl 2 # -*- mode: Perl -*- 3 # Copyright 2016-2023 The OpenSSL Project Authors. All Rights Reserved. 4 # 5 # Licensed under the Apache License 2.0 (the "License"). You may not use 6 # this file except in compliance with the License. You can obtain a copy 7 # in the file LICENSE in the source distribution or at 8 # https://www.openssl.org/source/license.html 9 10 use strict; 11 use File::Spec::Functions qw(devnull); 12 use IPC::Cmd; 13 use OpenSSL::Test qw(:DEFAULT srctop_file srctop_dir bldtop_dir bldtop_file); 14 use OpenSSL::Test::Utils; 15 16 BEGIN { 17 setup("test_symbol_presence"); 18 } 19 20 use lib srctop_dir('Configurations'); 21 use lib bldtop_dir('.'); 22 use platform; 23 24 plan skip_all => "Test is disabled on NonStop" if config('target') =~ m|^nonstop|; 25 # MacOS arranges symbol names differently 26 plan skip_all => "Test is disabled on MacOS" if config('target') =~ m|^darwin|; 27 # AIX reports symbol names differently 28 plan skip_all => "Test is disabled on AIX" if config('target') =~ m|^aix|; 29 plan skip_all => "This is unsupported on platforms that don't have 'nm'" 30 unless IPC::Cmd::can_run('nm'); 31 32 note 33 "NOTE: developer test! It's possible that it won't run on your\n", 34 "platform, and that's perfectly fine. This is mainly for developers\n", 35 "on Unix to check that our shared libraries are consistent with the\n", 36 "ordinals (util/*.num in the source tree), and that our static libraries\n", 37 "don't share symbols, something that should be a good enough check for\n", 38 "the other platforms as well.\n"; 39 40 my %stlibname; 41 my %shlibname; 42 my %stlibpath; 43 my %shlibpath; 44 my %defpath; 45 foreach (qw(crypto ssl)) { 46 $stlibname{$_} = platform->staticlib("lib$_"); 47 $stlibpath{$_} = bldtop_file($stlibname{$_}); 48 $shlibname{$_} = platform->sharedlib("lib$_") unless disabled('shared'); 49 $shlibpath{$_} = bldtop_file($shlibname{$_}) unless disabled('shared'); 50 } 51 52 my $testcount 53 = 1 # Check for static library symbols duplicates 54 ; 55 $testcount 56 += (scalar keys %shlibpath) # Check for missing symbols in shared lib 57 unless disabled('shared'); 58 59 plan tests => $testcount; 60 61 ###################################################################### 62 # Collect symbols 63 # [3 tests per library] 64 65 my %stsymbols; # Static library symbols 66 my %shsymbols; # Shared library symbols 67 my %defsymbols; # Symbols taken from ordinals 68 foreach (sort keys %stlibname) { 69 my $stlib_cmd = "nm -Pg $stlibpath{$_} 2> /dev/null"; 70 my $shlib_cmd = "nm -DPg $shlibpath{$_} 2> /dev/null"; 71 my @stlib_lines; 72 my @shlib_lines; 73 *OSTDERR = *STDERR; 74 *OSTDOUT = *STDOUT; 75 open STDERR, ">", devnull(); 76 open STDOUT, ">", devnull(); 77 @stlib_lines = map { s|\R$||; $_ } `$stlib_cmd`; 78 if ($? != 0) { 79 note "running '$stlib_cmd' => $?"; 80 @stlib_lines = (); 81 } 82 unless (disabled('shared')) { 83 @shlib_lines = map { s|\R$||; $_ } `$shlib_cmd`; 84 if ($? != 0) { 85 note "running '$shlib_cmd' => $?"; 86 @shlib_lines = (); 87 } 88 } 89 close STDERR; 90 close STDOUT; 91 *STDERR = *OSTDERR; 92 *STDOUT = *OSTDOUT; 93 94 my $bldtop = bldtop_dir(); 95 my @def_lines; 96 unless (disabled('shared')) { 97 indir $bldtop => sub { 98 my $mkdefpath = srctop_file("util", "mkdef.pl"); 99 my $def_path = srctop_file("util", "lib$_.num"); 100 my $def_cmd = "$^X $mkdefpath --ordinals $def_path --name $_ --OS linux 2> /dev/null"; 101 @def_lines = map { s|\R$||; $_ } `$def_cmd`; 102 if ($? != 0) { 103 note "running 'cd $bldtop; $def_cmd' => $?"; 104 @def_lines = (); 105 } 106 }, create => 0, cleanup => 0; 107 } 108 109 note "Number of lines in \@stlib_lines before massaging: ", scalar @stlib_lines; 110 unless (disabled('shared')) { 111 note "Number of lines in \@shlib_lines before massaging: ", scalar @shlib_lines; 112 note "Number of lines in \@def_lines before massaging: ", scalar @def_lines; 113 } 114 115 # Massage the nm output to only contain defined symbols 116 my @arrays = ( \@stlib_lines ); 117 push @arrays, \@shlib_lines unless disabled('shared'); 118 foreach (@arrays) { 119 my %commons; 120 foreach (@$_) { 121 if (m|^(.*) C .*|) { 122 $commons{$1}++; 123 } 124 } 125 foreach (sort keys %commons) { 126 note "Common symbol: $_"; 127 } 128 129 @$_ = 130 sort 131 ( map { 132 # Drop the first space and everything following it 133 s| .*||; 134 # Drop OpenSSL dynamic version information if there is any 135 s|\@\@.+$||; 136 # Return the result 137 $_ 138 } 139 # Drop any symbol starting with a double underscore, they 140 # are reserved for the compiler / system ABI and are none 141 # of our business 142 grep !m|^__|, 143 # Only look at external definitions 144 grep m|.* [BDST] .*|, 145 @$_ ), 146 keys %commons; 147 } 148 149 # Massage the mkdef.pl output to only contain global symbols 150 # The output we got is in Unix .map format, which has a global 151 # and a local section. We're only interested in the global 152 # section. 153 my $in_global = 0; 154 unless (disabled('shared')) { 155 @def_lines = 156 sort 157 map { s|;||; s|\s+||g; $_ } 158 grep { $in_global = 1 if m|global:|; 159 $in_global = 0 if m|local:|; 160 $in_global = 0 if m|\}|; 161 $in_global && m|;|; } @def_lines; 162 } 163 164 note "Number of lines in \@stlib_lines after massaging: ", scalar @stlib_lines; 165 unless (disabled('shared')) { 166 167 note "Number of lines in \@shlib_lines after massaging: ", scalar @shlib_lines; 168 note "Number of lines in \@def_lines after massaging: ", scalar @def_lines; 169 } 170 171 $stsymbols{$_} = [ @stlib_lines ]; 172 unless (disabled('shared')) { 173 $shsymbols{$_} = [ @shlib_lines ]; 174 $defsymbols{$_} = [ @def_lines ]; 175 } 176 } 177 178 ###################################################################### 179 # Check that there are no duplicate symbols in all our static libraries 180 # combined 181 # [1 test] 182 183 my %symbols; 184 foreach (sort keys %stlibname) { 185 foreach (@{$stsymbols{$_}}) { 186 $symbols{$_}++; 187 } 188 } 189 my @duplicates = sort grep { $symbols{$_} > 1 } keys %symbols; 190 if (@duplicates) { 191 note "Duplicates:"; 192 note join('\n', @duplicates); 193 } 194 ok(scalar @duplicates == 0, "checking no duplicate symbols in static libraries"); 195 196 ###################################################################### 197 # Check that the exported symbols in our shared libraries are consistent 198 # with our ordinals files. 199 # [1 test per library] 200 201 unless (disabled('shared')) { 202 foreach (sort keys %stlibname) { 203 # Maintain lists of symbols that are missing in the shared library, 204 # or that are extra. 205 my @missing = (); 206 my @extra = (); 207 208 my @sh_symbols = ( @{$shsymbols{$_}} ); 209 my @def_symbols = ( @{$defsymbols{$_}} ); 210 211 while (scalar @sh_symbols || scalar @def_symbols) { 212 my $sh_first = $sh_symbols[0]; 213 my $def_first = $def_symbols[0]; 214 215 if (!defined($sh_first)) { 216 push @missing, shift @def_symbols; 217 } elsif (!defined($def_first)) { 218 push @extra, shift @sh_symbols; 219 } elsif ($sh_first gt $def_first) { 220 push @missing, shift @def_symbols; 221 } elsif ($sh_first lt $def_first) { 222 push @extra, shift @sh_symbols; 223 } else { 224 shift @def_symbols; 225 shift @sh_symbols; 226 } 227 } 228 229 if (scalar @missing) { 230 note "The following symbols are missing in $_:"; 231 foreach (@missing) { 232 note " $_"; 233 } 234 } 235 if (scalar @extra) { 236 note "The following symbols are extra in $_:"; 237 foreach (@extra) { 238 note " $_"; 239 } 240 } 241 ok(scalar @missing == 0, 242 "check that there are no missing symbols in $_"); 243 } 244 } 245