#!/usr/bin/perl -- # A comment mentioning perl.
# Copyright (c) 2001 by Sun Microsystems, Inc.
# All rights reserved.
#

#
# For more info run this script with -h or look at the help text near
# the bottom of this file.
#

require 5.000;	# This script will not work with perl4 or lower.

use strict;
use Getopt::Std;
use File::Basename;

# os type and global database hashes:
my ($OS, %Inodes, %Filter, %Private, %Evolving, %Override, %Skipsym, %Info);

# regex patterns for matching and parsing output:
my ($binding_match, $filter_match, $unbound_match, $dtneeded_match);
my ($file_match, $lib_match);

my $keep_going = 0;			# -k option
my $ld_path = undef;			# -L option
my $private_match  = q/(?i)private/;	# -p option
my $evolving_match = q/(?i)evolving/;	# -e option

select(STDERR); $| = 1;
select(STDOUT); $| = 1;

#
# process cmd line args:
#
get_options();

#
# determine OS and setup OS specific things:
#
os_setup();

#
# loop over all binary objects and check each one: 
#
my $problem_count = 0;
my $file;
foreach $file (@ARGV) {

	# we handle some obvious "skip" cases here:
	if ( ! -e $file ) {
		print "$file: SKIP: $!\n";
		next;
	}
	if ( ! -f $file ) {
		print "$file: SKIP: not a regular file\n";
		next;
	}
	if ( $OS eq 'linux' && (-u $file || -g $file) ) {
		print "$file: SKIP: file is SUID or SGID\n";
		next;
	}
	if ( $file =~ /['\n]/ ) {
		#
		# these characters will likely cause our calls to the shell
		# to fail, so we skip these (oddly named) files.
		#
		my $tmp = $file;
		$tmp =~ s/\n/\\n/g;
		print "$tmp: SKIP: bad character in filename\n";
		next;
	}
	if ( ! &is_elf($file) ) {
		print "$file: SKIP: not an ELF file\n";	# must be ELF
		next;
	}
	
	check($file);
}

if ( $problem_count > 0 ) {
	exit 2;
} else {
	exit 0;
}

###########################################################################
# subroutines

#
# Wrapper for the dynamic and static checks.
#
sub check {
	my ($file) = @_;

	my (%dtneeded, $cnt);
	
	$cnt  = dynamic_check($file, \%dtneeded);
	$cnt +=  static_check($file, \%dtneeded);

	$problem_count += $cnt;	# increment by how many problems found.

	if ( ! $cnt ) {
		print "$file: OK\n";	# no problems found, indicate OK.
	}
}

#
# Extracts the command line options via getopts()
# if -f switch has been specified, reads the filelist and append filenames
# to @ARGV for processing.
#
sub get_options {
	#
	# We want the -l option to be additive. For simplicity we gather
	# all of them them up now, and just ignore $opt{l} later:
	#
	my $lib = '';
	my $i;
	for ($i=0; $i < @ARGV; $i++) {
		if ( $ARGV[$i] eq '-l' ) {
			$lib .= $ARGV[$i+1] . '|';
		}
	}
	$lib =~ s/\|+$//;		# trim trailing |
	if ( $lib ne '' ) {
		$lib_match = $lib;
	}

	# now call ordinary getopts:
	my %opt;
	if ( ! getopts('kh?f:p:e:l:L:', \%opt) ) {
		usage();
		exit 1;
	}

	if ( exists($opt{h}) || exists($opt{'?'}) ) {
		help();
		exit 1;
	}

	$keep_going = 1 if $opt{k};

	$private_match  = $opt{p} if $opt{p} ne '';
	$evolving_match = $opt{e} if $opt{e} ne '';

	$ld_path = $opt{L} if exists($opt{L});

	#
	# read in the -f list of binaries to check and append
	# them to @ARGV
	#
	if ( defined($opt{f}) ) {
		if ( $opt{f} ne '-' && ! -f $opt{f} ) {
			# '-' is STDIN, so the list can be piped in.
			die "invalid -f option: $opt{f}: $!\n";
		}

		open(LIST, "<$opt{f}") || die "cannot open: $opt{f}: $!\n";
		while (<LIST>) {
			chomp;
			push(@ARGV, $_);
		}
		close(LIST);
	}
}

#
# Determine OS, exit if not supported, adjust PATH, etc.
#
sub os_setup {

	# This path will find uname(1) and other needed utils:
	$ENV{PATH} .= ":/bin:/usr/bin";

	my ($os, $rel);
	chomp($os  = `uname -s`);
	chomp($rel = `uname -r`);

	if ( $os =~ /^SunOS/ && $rel !~ /^4\./ ) {
		# Note we cannot do SunOS 4.x (original SunOS & a.out)
		$OS = 'solaris';
		$ENV{PATH} .= ":/usr/ccs/bin";	# for dump(1)

	} elsif ( $os =~ /linux/i ) {
		#
		# We call this case "linux", though there are many distros,
		# some for which this tool may not work. Our actual
		# requirement is the GNU linker version 2.1 or higher.
		#
		$OS = 'linux';

	} else {
		die "unsupported OS: $os $rel\n";
	}

	set_matches();

	special_case_syms();
}

#
# Check a binary for any private or evolving symbol calls.
# Runs ldd -r with LD_DEBUG=files,bindings and parses the output,
# then checks the private/evolving database to see if the symbol is listed.
#
sub dynamic_check {
	my ($file, $dtneeded_ref) = @_;

	my $cnt = 0;

	# record inode for matching later on:
	my $file_inode = get_inode($file);

	#
	# LD_DEBUG output may prepend "./" to objects. We remove it
	# from everything, including the filename we profile.
	#
	my $file0 = $file;	# store original name for output.
        $file =~ s,^(\./+)*,,;

	# open a pipe to a child process that will run ldd(1):
	my $pid = open(LDD, "-|");
	die "$!\n" if ! defined($pid);	# fork failed.

	if ( ! $pid ) {
		# child here, go run ldd -r with debug env:
		exec_ldd($file);
		exit 1;
	}
	
	my ($obj, $lib, $sym, $rest, $info, $tmp, $n, %saw);
	my $bindings_cnt = 0;
	my $unbound_cnt  = 0;

	# process the LD_DEBUG output:

	my $prefix = '';
	while (<LDD>) {
		chomp;

		if ( /^[^\d]/ ) {
			# regular ldd -r output line.

			if ( /$unbound_match/o ) {	# see set_matches()
				$sym = $1;
				next if exists( $saw{"UNBOUND|$sym"} );
				$saw{"UNBOUND|$sym"} = 1;

				$unbound_cnt++;

			} elsif ( /$dtneeded_match/o ) { # see set_matches()
				$dtneeded_ref->{$1} = $2;
			}
			next;
		}

		if ( $prefix eq '' ) {
			#
			# This will detect if the "12345:" pid prefix applies
			# to the file we want checked.
			#
			
			if ( /$file_match/o ) {
				$tmp = $1;
				$obj = $2;
				my $ok = 0;
				if ( $obj eq $file ) {
					$ok = 1;
				} elsif ( defined($file_inode) &&
				    $file_inode eq get_inode($obj) ) {
					$ok = 1
				}
				if ( $ok ) {
					$prefix = $tmp;
					$n = length($prefix);
				}
			}
			next;
		} elsif ( substr($_, 0, $n) ne $prefix ) {
			next;
		}


		# leading PID: specific matches:

		if ( /$binding_match/o ) {	# see set_matches()
			$obj  = $1;
			$lib  = $2;
			$sym  = $3;
			$rest = $4;

			$obj =~ s,^(\./+)*,,;	# remove any leading ./
			$lib =~ s,^(\./+)*,,;

			my $ob = '';
			if ($obj eq $file) {
				;
			} elsif ( $lib eq $file || $obj eq $lib ) {
				next;	# ignore self and reverse bindings
			} elsif ($lib_match ne '' && $obj =~ /$lib_match/o) {
				# see get_options() for $lib_match
				$ob = basename($obj) . '->';
			} else {
				next;
			}



			if ( defined($Filter{$lib}) ) {
				# apply the alias for the filter:
				$lib = $Filter{$lib};
				next if $obj eq $lib;
			}

			# skip repeated bindings:
			next if exists( $saw{"$lib|$sym"} );
			$saw{"$lib|$sym"} = 1;

			$bindings_cnt++;

			next if $Skipsym{$sym};

			#
			# set the inode for later use (we use the inodes of
			# libraries instead of pathnames to avoid chasing
			# down symlinks, etc)
			#
			my $lib_inode = get_inode($lib);

			if ( ! defined($lib_inode) ) {
				die "invalid library: $lib, $!\n";
			}

			load_classification($lib_inode, $lib, $sym, $rest);

			if ( $Private{$lib_inode}{$sym} ) {
				$cnt++;
				$info = $Private{$lib_inode}{$sym};
				print "$file0: PRIVATE: ($ob$info) $sym\n";
			}
			if ( $Evolving{$lib_inode}{$sym} ) {
				$cnt++;
				$info = $Evolving{$lib_inode}{$sym};
				print "$file0: EVOLVING: ($ob$info) $sym\n";
			}

		} elsif ( /$filter_match/o ) {	# see set_matches()
			my $filtee = $1;
			my $filter = $2;
			$filtee =~ s,^(\./+)*,,;
			$filter =~ s,^(\./+)*,,;

			if ( ! exists($Filter{$filtee}) ) {
				load_filter($filtee, $filter);
			}
		}
	}
	undef %saw;

	close(LDD);
	if ( $? != 0 ) {
		$cnt++;
		print "$file0: LDD_ERROR";
		print " (run ldd -r on binary for more info)\n";
		exit 1 unless $keep_going;

	} elsif ( $bindings_cnt == 0 ) {
		$cnt++;
		print "$file0: NO_BINDINGS";
		print " (run ldd -r on binary for more info)\n";
		exit 1 unless $keep_going;
	}

	if ( $unbound_cnt != 0 ) {
		$cnt++;
		print "$file0: UNBOUND_SYMBOLS: $unbound_cnt";
		print " (run ldd -r on binary for more info)\n";
		exit 1 unless $keep_going;
	}

	return $cnt;
}

#
# Takes binding line information and determines if the lib:sym is private
# or evolving. Stores results in %Private and %Evolving databases.
#
sub load_classification {
    my ($inode, $lib, $sym, $rest) = @_;

    if ( ! exists $Info{extended_private_match} ) {
	#
	# for efficiency, store in the %Info hash whether we are doing
	# extended matching for private or evolving checks. These are
	# set by -p & -e with a component of the regex looking like:
	#
	#   -p 'GLIBC.*::__str'      (set syms __str* to private in GLIBC pkg)
	#   -p 'libgkt.*::__gtk_'    (set syms __gtk_* to private in libgtk)
	#
	# (those are made-up examples)
	#
	$Info{extended_private_match} = 0;
	if ( $private_match =~ /::/ ) {
		$Info{extended_private_match} = 1;
	}
	$Info{extended_evolving_match} = 0;
	if ( $evolving_match =~ /::/ ) {
		$Info{extended_evolving_match} = 1;
	}
    }

    my $base = basename($lib);

    if ( $OS eq 'solaris' ) {
	# must load whole library classification at once via pvs(1)

	# we are done if we have already processed this library:
	return if exists $Private{$inode};

	# set something so we only get in here once per lib:
	$Private{$inode}{'__LOADED_PVS__'} = 1;
	
	return if $lib =~ /['\n]/;	# bad chars for shell
	if ( ! -e $lib ) {
		# the dynamic linker found it; so should we.
		die "invalid library: $lib: $!\n";
	}

	open(PVS, "pvs -dos '$lib' 2>/dev/null|") || die "$!\n";

	while (<PVS>) {
		chomp;
		my ($l, $d, $version, $sym) = split(' ', $_);
		$version =~ s/:$//;
		$sym =~ s/;$//;

		set_db($version, $base, $sym, $inode);
	}
	close(PVS);

    } elsif ( $OS eq 'linux' ) {
	# load the classification one symbol at a time:

	# return if we already processed it: 
	return if exists $Private{$inode}{$sym};

	my $version = '';
	if ( $rest =~ /^\s*\[(.*)\]/ ) {	# e.g. [GLIBC_2.1]
		$version = $1;
	}
	#
	# N.B.: the above will only work if the application being checked
	# has been was built against a *versioned* library. Otherwise
	# the dynamic linker does not provided the [VERS] info.
	#
	# To handle this properly, we will likely have to do
	# it the Solaris way with a method similar to pvs(1).
	#

	set_db($version, $base, $sym, $inode);
    }
}
	
#
# Set the entries in the %Private and %Evolving database hashes.
# Both are referenced by the inode of the library, then the symbol name.
# The value is a little piece of information to report to user when usage
# is detected.
#
sub set_db {
	my ($version, $base, $sym, $inode) = @_;

	$Private{$inode}{$sym}  = undef;
	$Evolving{$inode}{$sym} = undef;

	if( ! $Info{extended_private_match} &&
	    ! $Info{extended_evolving_match} ) {
		return if $Override{$base}{$sym} eq 'public';
	}

	if( $Info{extended_private_match} ) {

		if ( $version ne '' && 
		    "${version}::${sym}" =~ /$private_match/o ) {

			$Private{$inode}{$sym} = "$base:$version";
			return;
				
		} elsif ( "${base}::${sym}" =~ /$private_match/o ) {
			$Private{$inode}{$sym} = "$base";
			return;
		}

	} elsif ( $Override{$base}{$sym} eq 'private' ) {

		$Private{$inode}{$sym} = "$base:$version";
		return;

	} elsif ( $version ne '' && $version =~ /$private_match/o ) {

		$Private{$inode}{$sym} = "$base:$version";
		return;
	} 

	#
	# Simple additional heuristic for GLIBC (leading underscore)
	# see special_case_syms().
	#
	if( ! $Info{extended_private_match} &&
	    ! defined( $Override{$base}{$sym} ) &&
	    $version =~ /^GLIBC_/ ) {

		if ( $sym =~ /^_/ && 'private' =~ /$private_match/o ) {
			$Private{$inode}{$sym} = "$base:$version";
			return;
		}
	}
			
	if( $Info{extended_evolving_match} ) {

		if ( $version ne '' && 
		    "${version}::${sym}" =~ /$evolving_match/o ) {

			$Evolving{$inode}{$sym} = "$base:$version";
			return;
				
		} elsif ( "${base}::${sym}" =~ /$evolving_match/o ) {
			$Evolving{$inode}{$sym} = "$base";
			return;
		}

	} elsif ( $Override{$base}{$sym} eq 'evolving' ) {

		$Evolving{$inode}{$sym} = "$base:$version";
		return;

	} elsif ( $version ne '' && $version =~ /$evolving_match/o ) {

		$Evolving{$inode}{$sym} = "$base:$version";
		return;
	}
}

#
# Record filter library "aliasing" for auxiliary filter libraries.
# The global hash %Filter will be used to apply the aliasing.
#
sub load_filter {
	my ($filtee, $filter) = @_;
	
	# set it to undef to indicate we have touched this one:
	$Filter{$filtee} = undef;

	if ( ! -e $filter ) {
		# the dynamic linker found it; so should we.
		die "invalid filter library: $filter: $!\n";
	}

	#
	# These characters would never happen under normal circumstances,
	# but could cause our shell commands to fail:
	#
	return if $filter =~ /['\n]/;

	# currently only know how to handle filters on Solaris:

	if ( $OS eq 'solaris' ) {
		my $dump = `dump -Lv '$filter' 2>/dev/null`;
		my $base = basename($filtee);
		if ($dump =~ /\bAUXILIARY\b.*\b${base}\b/ ||
		    $filter =~ /\blibdl\.so\.\d+/) {
			$Filter{$filtee} = $filter;	# set the alias.
		}
	} elsif ( $OS eq 'linux' ) {
		return;
	}
}

#
# exec ldd -r (or equivalent) on binary. Never returns, parent
# will read command output.
#
sub exec_ldd {
	my ($file) = @_;
	
	open(STDERR, ">&STDOUT");
	# need to close stdin on linux for some suid programs e.g. chsh (!)
	close(STDIN);

	if ( defined($ld_path) ) {
		$ENV{LD_LIBRARY_PATH} = $ld_path;
	}

	# currently, no difference between OSs
	$ENV{LD_DEBUG} = "files,bindings";
	exec 'ldd', '-r', $file;
	exit 1;	# exec failed
}

#
# Perform the static linking of system library archives check.
# The heuristic is simply to see if "well-known" symbols exist in the
# binary's .text area.
#
sub static_check {
	my ($file, $dtneeded_ref) = @_;

	my (%syms, %linkslib, $lib);

	#
	# N.B. technically we should use the %linkslib set below to verify
	# that the binary does not actually have the library on its DTNEEDED
	# list. Currently need to check if the above $dtneeded_ref does
	# not include extra libs from the full closure of the dynamic linking.
	#
	if ( 0 ) {
	    my $dtlib;
	    foreach $dtlib ( keys %{$dtneeded_ref} ) {
		$linkslib{libc} = 1      if $dtlib =~ m,\blibc\.so,;
		$linkslib{libsocket} = 1 if $dtlib =~ m,\blibsocket\.so,;
		$linkslib{libnsl} = 1    if $dtlib =~ m,\blibnsl\.so,;
	    }
	}

	get_syms($file, \%syms);

	my $cnt = 0;

	if ( $OS eq 'solaris' ) {
		
		# libc.a:
		if ($syms{'_exit'}) {
			$cnt++;
			print "$file: STATIC_LINK: libc.a\n";
		}

		# libsocket.a:
		if ($syms{'socket'} && $syms{'_socket'} &&
		    $syms{'bind'} && $syms{'_bind'} &&
		    $syms{'connect'} && $syms{'_connect'}) {
			print "$file: STATIC_LINK: libsocket.a\n";
			$cnt++;
		}

		# libnsl.a:
		if ($syms{'_xti_bind'} && $syms{'_xti_connect'} &&
		    $syms{'_tx_bind'} && $syms{'_tx_connect'}) {
			print "$file: STATIC_LINK: libnsl.a\n";
			$cnt++;
		}
	} elsif ( $OS eq 'linux' ) {
		
		# libc.a:
		if ($syms{'_exit'}) {
			$cnt++;
			print "$file: STATIC_LINK: libc.a\n";
		}
	}
	return $cnt;
}

#
# Extract the symbols defined in the binary's .text section.
# Even if the the binary is stripped, these will be in .dynsym because
# during dynamic linking shared objects may bind to these symbols.
#
sub get_syms {
	my ($file, $sym_ref) = @_;

	# run the respective binary utilities and extract the .text symbols

	if ( $OS eq 'solaris' ) {

		open(ELFDUMP, "elfdump -s -N .dynsym '$file' 2>/dev/null |") ||
		    die "$!\n";

		# line looks like:
		# [88]  0x00011110 0x00000974  FUNC GLOB 0   .text       main
		while (<ELFDUMP>) {
			chomp;
			if (/\s\.text\s+(\S+)$/) {
				$sym_ref->{$1} = 1;
			}
		}
		close(ELFDUMP);

	} elsif ( $OS eq 'linux' ) {
		
		open(OBJDUMP, "objdump --dynamic-syms '$file' 2>/dev/null |") ||
		    die "$!\n";
		
		# line looks like:
		# 08051660 g    DF .text  0000001a  Base        _exit
		while (<OBJDUMP>) {
			chomp;
			if (/\s\.text\b.*\s(\S+)$/) {
				$sym_ref->{$1} = 1;
			}
		}
		close(OBJDUMP);
	}
}

#
# sets and compiles the regex's for parsing the LD_DEBUG output.
#
sub set_matches {
	#
	# Set the various pattern matches for parsing the LD_DEBUG output.
	# They are similar for the OS's, but not the same. If that
	# output ever changes this script will no longer work...
	#
	# One can LD_DEBUG=files,bindings ldd -r <binary> to see what the 
	# output looks like on a given OS.
	# 
	if ( $OS eq 'solaris' ) {

		$binding_match =
		    q/binding file=(.*) to file=(.*): symbol `(\w+)'(.*)$/; 
		$filter_match  =
		    q/file=(.*);  filtered by (.*)$/;
		$unbound_match =
		    q/^\s*symbol not found:\s+(\S+)/;
		$dtneeded_match =
		    q/^\s*(\S+)\s+=>\s+(\S+)/;
		$file_match =
		    q/^(\d+:)\s+file=(.*);\s+analyzing/;

	} elsif ( $OS eq 'linux' ) {

		$binding_match =
		    q/binding file (.*) to (.*): symbol `(\w+)'(.*)$/;
		$filter_match  =
		    q/file=(.*);  filtered by (.*)$/; # XXX not checked
		$unbound_match =
		    q/^\s*undefined symbol:\s+(\S+)/;
		$dtneeded_match =
		    q/^\s*(\S+)\s+=>\s+(\S+)/;
		$file_match =
		    q/^(\d+:).*needed by (.*)$/;
		#
		# n.b. there is a PID mismatch in the GNU linker output if
		# we watch for the more sensible:
		#   q/^(\d+:)\s+file=(.*);\s+generating link map/;
		#
	}
}

#
# Determine inode of file. Returns undef if there was a problem.
# (including if it could not read the file: stat failed).
#
sub get_inode {
	my ($file) = @_;

	if ( exists($Inodes{$file}) ) {
		return $Inodes{$file};
	}
	$Inodes{$file} = (stat($file))[1];

	return $Inodes{$file};
}

#
# Determine if a file is ELF, returns 1 if so, 0 otherwise
# (including if it could not read the file)
#
sub is_elf {
	my ($file) = @_;
	open(FILE, "<$file") || return 0;

	my ($n, $buf);
	$n = read(FILE, $buf, 4);
	close(FILE);

	if ( $n != 4 || $buf ne "\177ELF" ) {
		return 0;
	}
	return 1;
}

#
# print short usage statement
#
sub usage {
	print <<"END";

abicheck [-h|-?] [-k] [-f <listfile>] [-p <pattern>] [-e <pattern>]
         [-l <library> -l ...] [-L <ldpath>] <file> ...

END

}

#
# print long description 
#
sub help {
	usage();
	print <<"END";

abicheck: check application binaries for calls to private or evolving symbols
          in libraries and check for static linking of some system libraries.


abicheck is run on application binaries and issues warnings whenever
any of the following three conditions are detected:

    Private symbol usage.  Private symbols are functions or data
    variables in a library package that are internal to that package.
    They are used by the libraries in the package for internal
    communication and are not part of the API/ABI that application
    developers should use.

    Evolving symbol usage.  Evolving symbols are functions or data
    variables in a library package that are intended for developer
    consumption, but have been marked as "evolving" or "unstable"" in
    the sense that they may become incompatible or disappear on a later
    release of the library package.

    Static linking.  Static linking of system libraries (e.g. libc.a)
    into an application is generally not a good idea because the system
    library code it "locks" into the application binary may become
    incompatible with later releases of the system.  abicheck attempts
    to detect static linking of a few system libraries.

    The default behavior is to, for each binary object checked, to
    examine direct calls from that binary object only.  The -l option
    allows the libraries the binary object brings in to have their
    calls checked as well.


Usage:	abicheck <options> <files>

<files> is a list of application binary objects to check.

<options> are:

	-k		Keep on checking binaries even if there are
			serious errors (dynamic linker reports unresolved
			symbols, ldd(1) failures, no symbols detected) 

	-h, -?		Print out long form of help.

	-f <listfile>	A file containing a list of binary objects
			to check, one per line.  This list is appended
			to any list on the cmd line via <files>. 
			If <listfile> is '-', then stdin is used.

	-l <library>	Add the basename or full path to the shared
			object library <library> to the list of objects
			to be checked for making private calls.  This
			option may occur more than once on the command
			line and is additive.

	-L <ldpath>	Set the LD_LIBRARY_PATH environment variable to
			<ldpath> before invoking dynamic linker.
			Use -L "" to unset LD_LIBRARY_PATH.

	-p <pattern>	Modify the version name pattern match labelling
			private version sets.  Default is /private/ using
			a case insensitive match.

			If a component of the regex <pattern> contains two
			colons in a row:  'patt1::patt2', then symbol level
			matching will be activated by checking whether
			"<version>::<symbol>" or "<library>::<symbol>"
			matches <pattern> (where the symbol name, version
			(if any), and library basename are substituted for
			<symbol>, <version>, and <library>).
			E.g.:
				-p 'FOO_VERS.*::_foopriv' or
				-p 'libfoo\.so.*::_foopriv'

	-e <pattern>	Same as -p but for "evolving" interfaces.


Output:
	There is one line per problem (there may be multiple problems
	per binary checked) which look like the following: 

	  If no problems were found:
	     <filename>: OK

	  If private symbol usage:
	     <filename>: PRIVATE (<library>:<private_version>) <private_sym>

	  If evolving symbol usage:
	     <filename>: EVOLVING (<library>:<evolving_vers>) <evolving_sym>

	  If file statically linked in a system archive library:
	     <filename>: STATIC_LINK (<archive>)

	  If checking of the file was skipped:
	     <filename>: SKIP (<reason>)

	The following problems will cause a fatal error unless the -k
	switch is used:

	  If the dynamic linker could not resolve N symbols when ldd -r was run:
	     <filename>: UNBOUND_SYMBOLS: <N>

	  If the dynamic linker found no dynamic bindings:
	     <filename>: NO_BINDINGS

	  If ldd(1) -r with LD_DEBUG=files,bindings failed:
	     <filename>: LDD_ERROR

	In these latter 3 cases run "ldd -r" on the binary file for
	more information on what went wrong.  (abicheck runs ldd -r with
	LD_DEBUG=files,bindings set).  On some systems the dynamic linker
	will not process SUID programs with LD_DEBUG set (this usually
	results in NO_BINDINGS in the abicheck output).

	Note that if you are running abicheck on a shared library
	(e.g. libfoo.so) that has NOT been built with -l<lib> flags
	to record its library dependencies, then the "unbound symbols"
	problem is very likely.  There is not much that can be done besides
	rebuilding the library or checking an application binary that
	uses the library and use the abicheck -l switch.


Exit status:
	0    no errors and no problems found.
	1    a fatal error occurred.
	2    no fatal errors but some binaries with problems were detected.

Notes:
	Only ELF objects are checked.

	Unless one is using the "::" custom matches supplied via the -p or
	-e flags, abicheck can only check against system libraries that
	have had symbol versioning applied to them (i.e. the private and/or
	evolving information recorded for each symbol in the library
	itself). For more info about symbol versioning, see the
	"Solaris Linker and Libraries Guide" answerbook at the URL
	http://docs.sun.com/ab2/coll.45.13 and the Commands/Version-Script
	section of the GNU linker "ld" info page.

	The default symbol version name matching patterns are case
	insensitive matches to the strings "private" and "evolving" for
	the private and evolving cases, respectively.

	Odd filenames containing the single-quote character or newline
	will be skipped; such characters interferes with calling
	commands via the shell.

	This program will not run with perl version 4 or lower.

Bugs:
	On Solaris 2.6 elfdump(1) (called by this script) will sometimes
	fail with "Segmentation Fault - core dumped" when part of
	a pipeline.

	On Linux when ldd(1) is run on a SUID binary, it (ldd and the
	dynamic-linker) will sometimes actually RUN the binary. On Linux
	SUID/SGID binaries are currently skipped.

END

}

#
# setup some special case symbols to skip the processing of.
#
sub special_case_syms {

	# skip undef items introduced by compiler via crt, etc,
	# or known private/evolving versioning errors.
	my (@skip, @public, @evolving, @private);

	if ( $OS eq 'solaris' ) {
		#
		# Some low-level symbols we are not interested in.
		#
		@skip = qw(
			_END_
			_START_
			__1cG__CrunMdo_exit_code6F_v_
			__1cG__CrunVdo_exit_code_in_range6Fpv1_v_
			__1cH__CimplKcplus_fini6F_v_
			__1cH__CimplKcplus_init6F_v_
			__fsr_init_value
			_ex_deregister
			_ex_register
			_exit
			_fini
			_get_exit_frame_monitor
			_init
			_objcInit
			atexit
			exit
			main

			_ctype
			__ctype
			_end
			_environ
			environ
			errno
			__iob
			_lib_version

			__fpstart
			__xtol
			__xtoll
		);

		#
		# These are frequently called symbols that are incorrectly
		# classified in Solaris.
		#
		@public = qw(
			libnsl.so.1:gethostname
			libc.so.1:mkstemp
			libc.so.1:thr_main

			libc.so.1:ntohl
			libc.so.1:ntohs
			libc.so.1:htonl
			libc.so.1:htons

			libXt.so.4:_XtInherit
			libXt.so.4:XtShellStrings
		);

	} elsif ( $OS eq 'linux' ) {
		#
		# Some low-level symbols we are not interested in.
		#
		@skip = qw(
			atexit
			_environ
			etext
			exit
			_fini
			_fp_hw
			_GLOBAL_OFFSET_TABLE_
			__gmon_start__
			_init
			__libc_init_first
			__libc_start_main
			main
			_mcleanup
			__monstartup
			monstartup
		);

		#
		# This big hardwired exception list is pretty gross, but 
		# without it nearly all applications use at least one of
		# the following symbols. This list was extracted from the
		# GLIBC 2.2.3 sources using the versioning scripts "Versions".
		#
		# These are the leading "_" symbol names that are nevertheless
		# part of the ABI (i.e. publically available).
		#
		@public = qw(
			libc.so.6:_IO_feof
			libc.so.6:_IO_ferror
			libc.so.6:_IO_getc
			libc.so.6:_IO_peekc_unlocked
			libc.so.6:_IO_putc
			libc.so.6:___brk_addr
			libc.so.6:__after_morecore_hook
			libc.so.6:__argz_count
			libc.so.6:__argz_next
			libc.so.6:__argz_stringify
			libc.so.6:__assert
			libc.so.6:__assert_fail
			libc.so.6:__assert_perror_fail
			libc.so.6:__bzero
			libc.so.6:__check_rhosts_file
			libc.so.6:__close
			libc.so.6:__cmsg_nxthdr
			libc.so.6:__collate_element_hash
			libc.so.6:__collate_element_strings
			libc.so.6:__collate_symbol_classes
			libc.so.6:__collate_symbol_hash
			libc.so.6:__collate_symbol_strings
			libc.so.6:__ctype32_b
			libc.so.6:__ctype32_tolower
			libc.so.6:__ctype32_toupper
			libc.so.6:__ctype_b
			libc.so.6:__ctype_get_mb_cur_max
			libc.so.6:__ctype_tolower
			libc.so.6:__ctype_toupper
			libc.so.6:__curbrk
			libc.so.6:__daylight
			libc.so.6:__dcgettext
			libc.so.6:__dcngettext
			libc.so.6:__default_morecore
			libc.so.6:__deregister_frame
			libc.so.6:__deregister_frame_info
			libc.so.6:__divdi3
			libc.so.6:__environ
			libc.so.6:__errno_location
			libc.so.6:__fcntl
			libc.so.6:__ffs
			libc.so.6:__finite
			libc.so.6:__finitef
			libc.so.6:__finitel
			libc.so.6:__frame_state_for
			libc.so.6:__free_hook
			libc.so.6:__fxstat
			libc.so.6:__fxstat64
			libc.so.6:__gconv_alias_db
			libc.so.6:__gconv_modules_db
			libc.so.6:__getdelim
			libc.so.6:__h_errno_location
			libc.so.6:__isinf
			libc.so.6:__isinff
			libc.so.6:__isinfl
			libc.so.6:__isnan
			libc.so.6:__isnanf
			libc.so.6:__isnanl
			libc.so.6:__iswctype
			libc.so.6:__ivaliduser
			libc.so.6:__libc_allocate_rtsig
			libc.so.6:__libc_current_sigrtmax
			libc.so.6:__libc_current_sigrtmin
			libc.so.6:__libc_sa_len
			libc.so.6:__line_wrap_output
			libc.so.6:__line_wrap_update
			libc.so.6:__lseek
			libc.so.6:__lxstat
			libc.so.6:__lxstat64
			libc.so.6:__malloc_hook
			libc.so.6:__malloc_initialize_hook
			libc.so.6:__malloc_initialized
			libc.so.6:__mbrlen
			libc.so.6:__mbrtowc
			libc.so.6:__memalign_hook
			libc.so.6:__memcpy_by2
			libc.so.6:__memcpy_by4
			libc.so.6:__memcpy_c
			libc.so.6:__memcpy_g
			libc.so.6:__mempcpy
			libc.so.6:__mempcpy_by2
			libc.so.6:__mempcpy_by4
			libc.so.6:__mempcpy_byn
			libc.so.6:__mempcpy_small
			libc.so.6:__memset_cc
			libc.so.6:__memset_ccn_by2
			libc.so.6:__memset_ccn_by4
			libc.so.6:__memset_cg
			libc.so.6:__memset_gcn_by2
			libc.so.6:__memset_gcn_by4
			libc.so.6:__memset_gg
			libc.so.6:__moddi3
			libc.so.6:__modify_ldt
			libc.so.6:__monstartup
			libc.so.6:__morecore
			libc.so.6:__on_exit
			libc.so.6:__open
			libc.so.6:__open_catalog
			libc.so.6:__overflow
			libc.so.6:__poll
			libc.so.6:__printf_fp
			libc.so.6:__profile_frequency
			libc.so.6:__progname
			libc.so.6:__progname_full
			libc.so.6:__rawmemchr
			libc.so.6:__rcmd_errstr
			libc.so.6:__read
			libc.so.6:__realloc_hook
			libc.so.6:__register_frame
			libc.so.6:__register_frame_info
			libc.so.6:__register_frame_info_table
			libc.so.6:__register_frame_table
			libc.so.6:__res_init
			libc.so.6:__res_nclose
			libc.so.6:__res_ninit
			libc.so.6:__res_state
			libc.so.6:__secure_getenv
			libc.so.6:__sigaddset
			libc.so.6:__sigdelset
			libc.so.6:__sigismember
			libc.so.6:__signbit
			libc.so.6:__signbitf
			libc.so.6:__signbitl
			libc.so.6:__sigpause
			libc.so.6:__sigsetjmp
			libc.so.6:__stpcpy
			libc.so.6:__stpcpy_g
			libc.so.6:__stpcpy_small
			libc.so.6:__stpncpy
			libc.so.6:__strcasecmp
			libc.so.6:__strcasestr
			libc.so.6:__strcat_c
			libc.so.6:__strcat_g
			libc.so.6:__strchr_c
			libc.so.6:__strchr_g
			libc.so.6:__strchrnul_c
			libc.so.6:__strchrnul_g
			libc.so.6:__strcmp_gg
			libc.so.6:__strcpy_g
			libc.so.6:__strcpy_small
			libc.so.6:__strcspn_c1
			libc.so.6:__strcspn_c2
			libc.so.6:__strcspn_c3
			libc.so.6:__strcspn_cg
			libc.so.6:__strcspn_g
			libc.so.6:__strdup
			libc.so.6:__strerror_r
			libc.so.6:__strlen_g
			libc.so.6:__strncat_g
			libc.so.6:__strncmp_g
			libc.so.6:__strncpy_by2
			libc.so.6:__strncpy_by4
			libc.so.6:__strncpy_byn
			libc.so.6:__strncpy_gg
			libc.so.6:__strndup
			libc.so.6:__strpbrk_c2
			libc.so.6:__strpbrk_c3
			libc.so.6:__strpbrk_cg
			libc.so.6:__strpbrk_g
			libc.so.6:__strrchr_c
			libc.so.6:__strrchr_g
			libc.so.6:__strsep_1c
			libc.so.6:__strsep_2c
			libc.so.6:__strsep_3c
			libc.so.6:__strsep_g
			libc.so.6:__strspn_c1
			libc.so.6:__strspn_c2
			libc.so.6:__strspn_c3
			libc.so.6:__strspn_cg
			libc.so.6:__strspn_g
			libc.so.6:__strstr_cg
			libc.so.6:__strstr_g
			libc.so.6:__strto*_internal
			libc.so.6:__strtok_r
			libc.so.6:__strtok_r_1c
			libc.so.6:__strverscmp
			libc.so.6:__sysconf
			libc.so.6:__sysv_signal
			libc.so.6:__timezone
			libc.so.6:__towctrans
			libc.so.6:__tzname
			libc.so.6:__udivdi3
			libc.so.6:__uflow
			libc.so.6:__umoddi3
			libc.so.6:__underflow
			libc.so.6:__vfscanf
			libc.so.6:__vsnprintf
			libc.so.6:__vsscanf
			libc.so.6:__wcsto*_internal
			libc.so.6:__write
			libc.so.6:__xmknod
			libc.so.6:__xpg_basename
			libc.so.6:__xstat
			libc.so.6:__xstat64
			libc.so.6:_authenticate
			libc.so.6:_environ
			libc.so.6:_exit
			libc.so.6:_h_errno
			libc.so.6:_inb
			libc.so.6:_inl
			libc.so.6:_inw
			libc.so.6:_itoa_lower_digits
			libc.so.6:_itoa_upper_digits
			libc.so.6:_libc_intl_domainname
			libc.so.6:_longjmp
			libc.so.6:_mcleanup
			libc.so.6:_nl_current_LC_COLLATE
			libc.so.6:_nl_current_LC_CTYPE
			libc.so.6:_nl_default_dirname
			libc.so.6:_nl_domain_bindings
			libc.so.6:_nl_msg_cat_cntr
			libc.so.6:_obstack_allocated_p
			libc.so.6:_obstack_begin
			libc.so.6:_obstack_begin_1
			libc.so.6:_obstack_free
			libc.so.6:_obstack_memory_used
			libc.so.6:_obstack_newchunk
			libc.so.6:_outb
			libc.so.6:_outl
			libc.so.6:_outw
			libc.so.6:_res
			libc.so.6:_res_hconf
			libc.so.6:_setjmp
			libc.so.6:_sys_errlist
			libc.so.6:_sys_nerr
			libc.so.6:_sys_siglist
			libc.so.6:_tolower
			libc.so.6:_toupper
			libm.so.6:_LIB_VERSION
			libm.so.6:__clog10
			libm.so.6:__clog10f
			libm.so.6:__clog10l
			libm.so.6:__expl
			libm.so.6:__expm1l
			libm.so.6:__finite
			libm.so.6:__finitef
			libm.so.6:__finitel
			libm.so.6:__fpclassify
			libm.so.6:__fpclassifyf
			libm.so.6:__fpclassifyl
			libm.so.6:__signbit
			libm.so.6:__signbitf
			libm.so.6:__signbitl
			libnsl.so.1:__yp_check
			libpthread.so.0:_IO_flockfile
			libpthread.so.0:_IO_ftrylockfile
			libpthread.so.0:_IO_funlockfile
			libpthread.so.0:__close
			libpthread.so.0:__connect
			libpthread.so.0:__errno_location
			libpthread.so.0:__fcntl
			libpthread.so.0:__fork
			libpthread.so.0:__h_errno_location
			libpthread.so.0:__lseek
			libpthread.so.0:__open
			libpthread.so.0:__open64
			libpthread.so.0:__pread64
			libpthread.so.0:__pwrite64
			libpthread.so.0:__read
			libpthread.so.0:__res_state
			libpthread.so.0:__send
			libpthread.so.0:__wait
			libpthread.so.0:__write
			libpthread.so.0:_pthread_cleanup_pop
			libpthread.so.0:_pthread_cleanup_pop_restore
			libpthread.so.0:_pthread_cleanup_push
			libpthread.so.0:_pthread_cleanup_push_defer
			libresolv.so.2:_gethtbyaddr
			libresolv.so.2:_gethtbyname
			libresolv.so.2:_gethtbyname2
			libresolv.so.2:_gethtent
			libresolv.so.2:_getlong
			libresolv.so.2:_getshort
			libresolv.so.2:_res_opcodes
			libresolv.so.2:_res_resultcodes
			libresolv.so.2:_sethtent

			libc.so.6:__strtod_internal
			libc.so.6:__strtoull_internal
			libc.so.6:__strtol_internal
			libc.so.6:__strtoll_internal
			libc.so.6:__strtoul_internal
			libc.so.6:__strtold_internal
			libc.so.6:__strtouq_internal
			libc.so.6:__strtoq_internal
			libc.so.6:__strtof_internal
			libc.so.6:__wcstold_internal
			libc.so.6:__wcstoull_internal
			libc.so.6:__wcstoll_internal
			libc.so.6:__wcstoul_internal
			libc.so.6:__wcstof_internal
			libc.so.6:__wcstol_internal
			libc.so.6:__wcstod_internal
		);
		#
		# These are  are from older libraries.
		#
		push(@public, qw(
			libdb.so.3:__bam_init_print
			libdb.so.3:__bam_pgin
			libdb.so.3:__bam_pgout
			libdb.so.3:__db_dispatch
			libdb.so.3:__db_dump
			libdb.so.3:__db_err
			libdb.so.3:__db_init_print
			libdb.so.3:__db_jump
			libdb.so.3:__db_omode
			libdb.so.3:__db_prdbt
			libdb.so.3:__ham_init_print
			libdb.so.3:__ham_pgin
			libdb.so.3:__ham_pgout
			libdb.so.3:__lock_dump_region
			libdb.so.3:__log_init_print
			libdb.so.3:__memp_dump_region
			libdb.so.3:__txn_init_print
			libresolv.so.2:__dn_skipname
			)
		);

		# These are non-leading _ that are actually private:
		@private = qw(
			libnsl.so.1:readColdStartFile
			libnsl.so.1:writeColdStartFile
		);
	}

	my ($sym, $pair);
	
	# Symbols to ignore:
	foreach $sym (@skip) {
		$Skipsym{$sym} = 1;
	}

	# Symbols to reset to public:
	foreach $pair (@public) {
		my ($lib, $sym) = split(/:/, $pair);
		$Override{$lib}{$sym} = 'public';

		$lib =~ s/\.so.*$/.so/;
		$Override{$lib}{$sym} = 'public';
	}

	# Symbols to reset to evolving:
	foreach $pair (@evolving) {
		my ($lib, $sym) = split(/:/, $pair);
		$Override{$lib}{$sym} = 'evolving';

		$lib =~ s/\.so.*$/.so/;
		$Override{$lib}{$sym} = 'evolving';
	}

	# Symbols to reset to private:
	foreach $pair (@private) {
		my ($lib, $sym) = split(/:/, $pair);
		$Override{$lib}{$sym} = 'private';

		$lib =~ s/\.so.*$/.so/;
		$Override{$lib}{$sym} = 'private';
	}
}

