1#!/usr/bin/env perl 2 3# make_sunver.pl 4# 5# Copyright (C) 2010, 2011, 2012, 2013 6# Free Software Foundation, Inc. 7# 8# This file is free software; you can redistribute it and/or modify it 9# under the terms of the GNU General Public License as published by 10# the Free Software Foundation; either version 3 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, but 14# WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16# General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program; see the file COPYING.GPLv3. If not see 20# <http://www.gnu.org/licenses/>. 21 22# This script takes at least two arguments, a GNU style version script and 23# a list of object and archive files, and generates a corresponding Sun 24# style version script as follows: 25# 26# Each glob pattern, C++ mangled pattern or literal in the input script is 27# matched against all global symbols in the input objects, emitting those 28# that matched (or nothing if no match was found). 29# A comment with the original pattern and its type is left in the output 30# file to make it easy to understand the matches. 31# 32# It uses elfdump when present (native), GNU readelf otherwise. 33# It depends on the GNU version of c++filt, since it must understand the 34# GNU mangling style. 35 36use FileHandle; 37use IPC::Open2; 38 39# Enforce C locale. 40$ENV{'LC_ALL'} = "C"; 41$ENV{'LANG'} = "C"; 42 43# Input version script, GNU style. 44my $symvers = shift; 45 46########## 47# Get all the symbols from the library, match them, and add them to a hash. 48 49my %sym_hash = (); 50 51# List of objects and archives to process. 52my @OBJECTS = (); 53 54# List of shared objects to omit from processing. 55my @SHAREDOBJS = (); 56 57# Filter out those input archives that have corresponding shared objects to 58# avoid adding all symbols matched in the archive to the output map. 59foreach $file (@ARGV) { 60 if (($so = $file) =~ s/\.a$/.so/ && -e $so) { 61 printf STDERR "omitted $file -> $so\n"; 62 push (@SHAREDOBJS, $so); 63 } else { 64 push (@OBJECTS, $file); 65 } 66} 67 68# We need to detect and ignore hidden symbols. Solaris nm can only detect 69# this in the harder to parse default output format, and GNU nm not at all, 70# so use elfdump -s in the native case and GNU readelf -s otherwise. 71# GNU objdump -t cannot be used since it produces a variable number of 72# columns. 73 74# The path to elfdump. 75my $elfdump = "/usr/ccs/bin/elfdump"; 76 77if (-f $elfdump) { 78 open ELFDUMP,$elfdump.' -s '.(join ' ',@OBJECTS).'|' or die $!; 79 my $skip_arsym = 0; 80 81 while (<ELFDUMP>) { 82 chomp; 83 84 # Ignore empty lines. 85 if (/^$/) { 86 # End of archive symbol table, stop skipping. 87 $skip_arsym = 0 if $skip_arsym; 88 next; 89 } 90 91 # Keep skipping until end of archive symbol table. 92 next if ($skip_arsym); 93 94 # Ignore object name header for individual objects and archives. 95 next if (/:$/); 96 97 # Ignore table header lines. 98 next if (/^Symbol Table Section:/); 99 next if (/index.*value.*size/); 100 101 # Start of archive symbol table: start skipping. 102 if (/^Symbol Table: \(archive/) { 103 $skip_arsym = 1; 104 next; 105 } 106 107 # Split table. 108 (undef, undef, undef, undef, $bind, $oth, undef, $shndx, $name) = split; 109 110 # Error out for unknown input. 111 die "unknown input line:\n$_" unless defined($bind); 112 113 # Ignore local symbols. 114 next if ($bind eq "LOCL"); 115 # Ignore hidden symbols. 116 next if ($oth eq "H"); 117 # Ignore undefined symbols. 118 next if ($shndx eq "UNDEF"); 119 # Error out for unhandled cases. 120 if ($bind !~ /^(GLOB|WEAK)/ or $oth ne "D") { 121 die "unhandled symbol:\n$_"; 122 } 123 124 # Remember symbol. 125 $sym_hash{$name}++; 126 } 127 close ELFDUMP or die "$elfdump error"; 128} else { 129 open READELF, 'readelf -s -W '.(join ' ',@OBJECTS).'|' or die $!; 130 # Process each symbol. 131 while (<READELF>) { 132 chomp; 133 134 # Ignore empty lines. 135 next if (/^$/); 136 137 # Ignore object name header. 138 next if (/^File: .*$/); 139 140 # Ignore table header lines. 141 next if (/^Symbol table.*contains.*:/); 142 next if (/Num:.*Value.*Size/); 143 144 # Split table. 145 (undef, undef, undef, undef, $bind, $vis, $ndx, $name) = split; 146 147 # Error out for unknown input. 148 die "unknown input line:\n$_" unless defined($bind); 149 150 # Ignore local symbols. 151 next if ($bind eq "LOCAL"); 152 # Ignore hidden symbols. 153 next if ($vis eq "HIDDEN"); 154 # Ignore undefined symbols. 155 next if ($ndx eq "UND"); 156 # Error out for unhandled cases. 157 if ($bind !~ /^(GLOBAL|WEAK)/ or $vis ne "DEFAULT") { 158 die "unhandled symbol:\n$_"; 159 } 160 161 # Remember symbol. 162 $sym_hash{$name}++; 163 } 164 close READELF or die "readelf error"; 165} 166 167########## 168# The various types of glob patterns. 169# 170# A glob pattern that is to be applied to the demangled name: 'cxx'. 171# A glob patterns that applies directly to the name in the .o files: 'glob'. 172# This pattern is ignored; used for local variables (usually just '*'): 'ign'. 173 174# The type of the current pattern. 175my $glob = 'glob'; 176 177# We're currently inside `extern "C++"', which Sun ld doesn't understand. 178my $in_extern = 0; 179 180# The c++filt command to use. This *must* be GNU c++filt; the Sun Studio 181# c++filt doesn't handle the GNU mangling style. 182my $cxxfilt = $ENV{'CXXFILT'} || "c++filt"; 183 184# The current version name. 185my $current_version = ""; 186 187# Was there any attempt to match a symbol to this version? 188my $matches_attempted; 189 190# The number of versions which matched this symbol. 191my $matched_symbols; 192 193open F,$symvers or die $!; 194 195# Print information about generating this file 196print "# This file was generated by make_sunver.pl. DO NOT EDIT!\n"; 197print "# It was generated by:\n"; 198printf "# %s %s %s\n", $0, $symvers, (join ' ',@ARGV); 199printf "# Omitted archives with corresponding shared libraries: %s\n", 200 (join ' ', @SHAREDOBJS) if $#SHAREDOBJS >= 0; 201print "#\n\n"; 202 203print "\$mapfile_version 2\n"; 204 205while (<F>) { 206 # Lines of the form '};' 207 if (/^([ \t]*)(\}[ \t]*;[ \t]*)$/) { 208 $glob = 'glob'; 209 if ($in_extern) { 210 $in_extern--; 211 print "$1##$2\n"; 212 } else { 213 print; 214 } 215 next; 216 } 217 218 # Lines of the form '} SOME_VERSION_NAME_1.0;' 219 if (/^[ \t]*\}[ \tA-Z0-9_.a-z]+;[ \t]*$/) { 220 $glob = 'glob'; 221 # We tried to match symbols agains this version, but none matched. 222 # Emit dummy hidden symbol to avoid marking this version WEAK. 223 if ($matches_attempted && $matched_symbols == 0) { 224 print " hidden:\n"; 225 print " .force_WEAK_off_$current_version = DATA S0x0 V0x0;\n"; 226 } 227 print; next; 228 } 229 230 # Comment and blank lines 231 if (/^[ \t]*\#/) { print; next; } 232 if (/^[ \t]*$/) { print; next; } 233 234 # Lines of the form '{' 235 if (/^([ \t]*){$/) { 236 if ($in_extern) { 237 print "$1##{\n"; 238 } else { 239 print; 240 } 241 next; 242 } 243 244 # Lines of the form 'SOME_VERSION_NAME_1.1 {' 245 if (/^([A-Z0-9_.]+)[ \t]+{$/) { 246 # Record version name. 247 $current_version = $1; 248 # Reset match attempts, #matched symbols for this version. 249 $matches_attempted = 0; 250 $matched_symbols = 0; 251 print "SYMBOL_VERSION $1 {\n"; 252 next; 253 } 254 255 # Ignore 'global:' 256 if (/^[ \t]*global:$/) { print; next; } 257 258 # After 'local:', globs should be ignored, they won't be exported. 259 if (/^[ \t]*local:$/) { 260 $glob = 'ign'; 261 print; 262 next; 263 } 264 265 # After 'extern "C++"', globs are C++ patterns 266 if (/^([ \t]*)(extern \"C\+\+\"[ \t]*)$/) { 267 $in_extern++; 268 $glob = 'cxx'; 269 # Need to comment, Sun ld cannot handle this. 270 print "$1##$2\n"; next; 271 } 272 273 # Chomp newline now we're done with passing through the input file. 274 chomp; 275 276 # Catch globs. Note that '{}' is not allowed in globs by this script, 277 # so only '*' and '[]' are available. 278 if (/^([ \t]*)([^ \t;{}#]+);?[ \t]*$/) { 279 my $ws = $1; 280 my $ptn = $2; 281 # Turn the glob into a regex by replacing '*' with '.*', '?' with '.'. 282 # Keep $ptn so we can still print the original form. 283 ($pattern = $ptn) =~ s/\*/\.\*/g; 284 $pattern =~ s/\?/\./g; 285 286 if ($glob eq 'ign') { 287 # We're in a local: * section; just continue. 288 print "$_\n"; 289 next; 290 } 291 292 # Print the glob commented for human readers. 293 print "$ws##$ptn ($glob)\n"; 294 # We tried to match a symbol to this version. 295 $matches_attempted++; 296 297 if ($glob eq 'glob') { 298 my %ptn_syms = (); 299 300 # Match ptn against symbols in %sym_hash. 301 foreach my $sym (keys %sym_hash) { 302 # Maybe it matches one of the patterns based on the symbol in 303 # the .o file. 304 $ptn_syms{$sym}++ if ($sym =~ /^$pattern$/); 305 } 306 307 foreach my $sym (sort keys(%ptn_syms)) { 308 $matched_symbols++; 309 print "$ws$sym;\n"; 310 } 311 } elsif ($glob eq 'cxx') { 312 my %dem_syms = (); 313 314 # Verify that we're actually using GNU c++filt. Other versions 315 # most likely cannot handle GNU style symbol mangling. 316 my $cxxout = `$cxxfilt --version 2>&1`; 317 $cxxout =~ m/GNU/ or die "$0 requires GNU c++filt to function"; 318 319 # Talk to c++filt through a pair of file descriptors. 320 # Need to start a fresh instance per pattern, otherwise the 321 # process grows to 500+ MB. 322 my $pid = open2(*FILTIN, *FILTOUT, $cxxfilt) or die $!; 323 324 # Match ptn against symbols in %sym_hash. 325 foreach my $sym (keys %sym_hash) { 326 # No? Well, maybe its demangled form matches one of those 327 # patterns. 328 printf FILTOUT "%s\n",$sym; 329 my $dem = <FILTIN>; 330 chomp $dem; 331 $dem_syms{$sym}++ if ($dem =~ /^$pattern$/); 332 } 333 334 close FILTOUT or die "c++filt error"; 335 close FILTIN or die "c++filt error"; 336 # Need to wait for the c++filt process to avoid lots of zombies. 337 waitpid $pid, 0; 338 339 foreach my $sym (sort keys(%dem_syms)) { 340 $matched_symbols++; 341 print "$ws$sym;\n"; 342 } 343 } else { 344 # No? Well, then ignore it. 345 } 346 next; 347 } 348 # Important sanity check. This script can't handle lots of formats 349 # that GNU ld can, so be sure to error out if one is seen! 350 die "strange line `$_'"; 351} 352close F; 353