#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License, Version 1.0 only
# (the "License"). You may not use this file except in compliance
# with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
# ident "%Z%%M% %I% %E% SMI"
#
# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
#
# This utility program loads the unstable behavior databases, then reads
# the symprof output for each binary, and record any detected unstable
# behavior in the binary's output directory.
#
require 5.005;
use strict;
use locale;
use AppcertUtil;
use vars qw(
$LIBC
);
clean_up();
exit 0;
#
# Set up any variables.
#
{
# Here is what we call libc:
}
#
# working_dir has been imported by import_vars_from_environment()
# A sanity check is performed here to make sure it exists.
#
sub set_working_dir
{
if (! defined($working_dir) || ! -d $working_dir) {
"cannot locate working directory: %s\n"), $working_dir));
}
}
#
# Called when interrupted by a signal.
#
sub interrupted
{
signals('off');
clean_up_exit(1);
}
#
# Does the cleanup then exit with return code $rc. Note: The utility
# routine exiter() will call this routine.
#
sub clean_up_exit
{
my ($rc) = @_;
clean_up();
exit $rc;
}
#
# General cleanup activities are placed here. There may not be an
# immediate exit after this cleanup.
#
sub clean_up
{
if (defined($tmp_check_dir) && -d $tmp_check_dir) {
}
}
#
# Top level routine to initialize databases and then loop over the
# objects and call the checking routines on each one.
#
sub check_objects
{
# Make a tmp dir for the checking work.
if (! -d $tmp_check_dir) {
}
"checking binary objects for unstable practices") . " ...\n\n");
my ($dir, $path_to_object);
#
# Loop over each object item in the working_dir.
# - $dir will be each one of these object directories.
# - $path_to_object will be the corresponding actual path
# to the the binary to be checked.
#
$binaries_checked_count = 0;
#
# We need to load the Misc Databases to get any modifications to
# the symbol database. E.g. gethostname should be public.
#
while (defined($dir = next_dir_name())) {
# Map object output dir to actual path of the object:
if (! -f $path_to_object) {
}
# Check it:
}
if ($binaries_checked_count == 0) {
"no binary objects where checked."));
}
# Do additional heuristic checks of unstable behavior:
clean_up(); # Remove any tmp dirs and files.
}
#
# Reads in the static profile (i.e. the symbols exported by bin in
# .text section) and calls the static archive checking routine.
#
{
my ($path_to_object, $dir) = @_;
# The profile output file created by static_profile() in symprof.
my $profile_file = "$dir/profile.static";
"binary object %s has no static profile: %s: %s\n");
if (! -f $profile_file) {
$profile_file, $!);
return 0;
}
if (! open($profile_fh, "<$profile_file")) {
}
my $completely_statically_linked = 0;
while (<$profile_fh>) {
$profile .= $_;
if (/^\s*#dtneeded:\s*(.*)$/) {
#
# record the bare name, e.g. "libc" of the
# (direct) dtneededs.
#
foreach $lib (split(/\s+/, $1)) {
next if ($lib eq '');
# record it as libc.so.1 -> libc
}
} elsif (/^\s*#SKIPPED_TEST:\s+STATICALLY_LINKED/) {
#
# Record statical linking if it takes place
# since it indicates to skip the test.
#
}
}
close($profile_fh);
my $problems = "$dir/check.problems";
if (! open($problems_fh, ">>$problems")) {
}
if ($completely_statically_linked) {
}
my (%saw_lib);
if (! defined($profile)) {
close($problems_fh);
return;
}
#
# lib_static_check returns a list of statically linked
# libraries, however to be on the safe side we will skip
# false positives our dtneeded's show they are really
# dynamically linked in.
#
next if ($libs_needed{$lib});
$lib2 =~ s/\.a$//;
next if ($libs_needed{$lib2});
# Otherwise, record in the problems file:
}
close($problems_fh);
}
#
# Takes as input the static profile (e.g. the .text symbols) and returns
# a list of suspected statically linked Solaris archive libraries.
#
sub lib_static_check
{
my ($profile) = @_;
my (%symbols);
#
# Working on lines like:
#
# First record all the symbols in the TEXT area:
}
my (@static_libs);
# Next, check against the library heuristics for static linking:
# libc.a:
if (exists($symbols{'_exit'})) {
push(@static_libs, "/usr/lib/libc.a");
}
# libsocket.a:
push(@static_libs, "/usr/lib/libsocket.a");
}
# libnsl.a:
push(@static_libs, "/usr/lib/libnsl.a");
}
return @static_libs;
}
#
# Reads in the dynamic profile from the object's output directory.
# Records unstable use of any private Solaris symbols in the object's
# output directory.
#
{
my ($path_to_object, $dir) = @_;
# Location of the dynamic profile output:
my $profile_file = "$dir/profile.dynamic";
"binary object %s has no dynamic profile: %s: %s\n");
if (! -f $profile_file) {
$profile_file, $!);
return 0;
}
if (! open($profile_fh, "<$profile_file")) {
}
#
# Variables to hold temporary items:
#
# %library_list will be a hash of "to" libraries we need to load.
# @symbol_list will be an array of the "lib|sym" pairs.
#
my ($to_is_sys_lib, $from_is_sys_lib);
#
# profile lines look like:
#
# or:
#
# /bin/ftp|*DIRECT*|/usr/lib/libsocket.so.1|socket
#
#
# Setting abi to 'any' will allow continuation when
# we encounter an abi we do not recognize.
#
$abi = 'any';
} else {
#
# Always try to load libc. This will be used for symbol
# migration to libc checks.
#
if (! exists($load_model_default{$abi})) {
}
}
my $dynamic_bindings_count = 0;
my $no_bindings_msg;
while (<$profile_fh>) {
chomp;
if (/^\s*#/) {
if (/NO_BINDINGS_FOUND\s*(.*)$/) {
my $msg = $1;
if ($msg =~ /^\s*$/) {
$no_bindings_msg = 'NO_SYMBOL_BINDINGS_FOUND';
} else {
$no_bindings_msg = $msg;
}
}
next;
}
# Skip the checking of reverse calls:
next if ($from eq "*REVERSE*");
# Skip the checking of special symbols:
next if (exists($skip_symbols{$sym}));
# Accumulate unbounds, but otherwise skip them:
if ($to eq "*UNBOUND*") {
push(@unbound_list, "$from|$sym");
next;
}
# Record if the "to" object is a system library:
if ($from eq "*DIRECT*") {
$from_is_sys_lib = 0;
} else {
#
# Otherwise we may check its calls. See if it is
# a system lib:
#
}
#
# We will pass judgement on *DIRECT* calls and indirect
# calls from a library we do not recognize.
#
if ($from_is_sys_lib) {
next;
}
if (! $to_is_sys_lib) {
# Call to a middleware or supporting library.
next;
}
push(@symbol_list, "$from|$to|$abi|$sym");
}
close($profile_fh);
my $file;
$file = "$dir/check.problems";
if (! open($problems_fh, ">>$file")) {
}
print $problems_fh "DYNAMIC: NO_DYNAMIC_BINDINGS_FOUND" .
" $no_bindings_msg\n";
close($problems_fh);
return;
}
$file = "$dir/check.dynamic.abi_models";
if (! open($model_info_fh, ">$file")) {
}
# Load all the needed library models:
foreach $lib (keys %library_list) {
}
if ($str eq '__FAILED__') {
} elsif (! $str) {
}
print $model_info_fh "$lib:$str\n";
}
close($model_info_fh);
my ($l, $a, $s);
foreach $lib_abi_sym (@symbol_list) {
($l, $a, $s) = split(/\|/, $lib_abi_sym);
if (! exists($model{$lib_abi_sym})) {
#
# Check the library. If it is not in
# model_loaded, then we claim it is not a
# library we are interested in.
#
next if (! exists($model_loaded{"$l|$a"}));
# it is an unrecognized symbol:
$result_list{'unrecognized'} .=
"$from|$lib_abi_sym" . "\n";
next;
}
# N.B. $lib_abi_sym and $l may have been altered above.
"unrecognized symbol class: %s"), $class));
}
}
if (@unbound_list) {
my $ldd_file = "$dir/profile.dynamic.ldd";
my $tmp;
if (-f $ldd_file) {
if (! open($ldd_info_fh, "<$ldd_file")) {
}
while (<$ldd_info_fh>) {
}
close($ldd_info_fh);
}
if (defined($tmp)) {
}
}
my $count;
next if (! exists($result_list{$class}));
$file = "$dir/check.dynamic.$class";
if (! open($outfile_fh, ">$file")) {
}
if ($class eq 'private') {
print $outfile_fh
$text{'Message_Private_Symbols_Check_Outfile'};
} elsif ($class eq 'public') {
print $outfile_fh
$text{'Message_Public_Symbols_Check_Outfile'};
}
close($outfile_fh);
}
$file = "$dir/check.problems";
if (! open($problems_fh, ">>$file")) {
}
if (exists($result_list{'private'})) {
print $problems_fh "DYNAMIC: PRIVATE_SYMBOL_USE $count\n";
}
if (exists($result_list{'unbound'})) {
$count = scalar(@unbound_list);
print $problems_fh "DYNAMIC: UNBOUND_SYMBOL_USE $count\n";
}
if (exists($result_list{'unrecognized'})) {
$count =
scalar(my @a = split(/\n/, $result_list{'unrecognized'}));
print $problems_fh "DYNAMIC: UNRECOGNIZED_SYMBOL_USE $count\n";
}
close($problems_fh);
}
#
# Loads a system model for a library on demand.
#
# Input is a library to load and the architecture ABI.
#
# On successful completion, 1 is returned and the associative array:
#
# %model{"$library|$abi|$symbol"} = {public,private}
#
# is set.
#
sub load_model
{
#
# Returns 1 if the model was successfully loaded, or returns 0
# if it was not.
#
#
# This %model_loaded hash records the following states:
# <string> Method by which successfully loaded.
# __FAILED__ Failed to loaded successfully.
# undef Have not tried to load yet.
#
if (exists($model_loaded{"$library|$abi"})) {
return 0;
} elsif ($model_loaded{"$library|$abi"}) {
return 1;
}
}
# Record the result so we do not have to repeat the above:
if ($loaded) {
$ok = "OK";
} else {
}
return $loaded;
}
#
# Routine to load into %model any special modifications to the Solaris
# symbol Models kept in the etc.* file.
#
sub load_tweaks
{
my $key;
} else {
#
# symlinks.
#
return 0;
}
$key = "$device/$inode";
return 0;
}
#
# using it below in the model_tweak lookup.
#
}
#
# etc line looks like:
# MODEL_TWEAK|/usr/lib/libnsl.so.1|sparc,i386|gethostname|public
# value looks like:
# gethostname|sparc,i386|public
#
$count = 0;
$count++;
}
}
return $count;
}
#
# library. Returns 0 if no model could be extracted, otherwise returns a
# string detailing the model loading.
#
{
#
# This subroutine runs pvs -dos directly on the Solaris shared
# object, and parses data that looks like this:
#
# % pvs -dos /usr/lib/libsocket.so.1
# ...
# /usr/lib/libsocket.so.1 - SUNW_1.1: __xnet_sendmsg;
# ...
# /usr/lib/libsocket.so.1 - SISCD_2.3: connect;
# ...
# /usr/lib/libsocket.so.1 - SUNWprivate_1.2: getnetmaskbyaddr;
#
# Note that data types look like:
#
# we discard the size.
#
# On successful completion 1, is returned and the hash:
#
# %model{"$library|$abi|$symbol"} = {public,private}
#
# is set.
#
# library must be a full path and exist:
if (! -f $library) {
return 0;
}
#
# quote character should never happen in normal use, but if it
# did it will foul up the pvs commands below, so we return
# early.
#
if ($library =~ /'/) {
return 0;
}
#
# Get the entire list of symbols:
# note that $library does not contain a single-quote.
#
c_locale(1);
$rc = $?;
c_locale(0);
# It is not versioned, so get out.
return 0;
}
# Library is versioned with something from this point on.
my (%versions);
$count = 0;
$public_count = 0;
$private_count = 0;
if (defined($is_system_lib)) {
foreach $def (split(/:/, $is_system_lib)) {
next if ($def =~ /^-$/);
}
if (@defs == 1) {
}
}
#
# It is a versioned system library. Extract the public
# symbols version head end(s)
#
if ($is_system_lib =~ /^PUBLIC=(.*)$/) {
@version_heads = split(/,/, $1);
} else {
push(@version_heads, $is_system_lib);
}
#
# Rerun pvs again to extract the symbols associated with
# the *public* inheritance chain(s).
#
c_locale(1);
foreach $vers (@version_heads) {
# something is wrong if $vers has a quote
$vers =~ s/'//g;
# $library has been screened for single quotes earlier.
$output_syslib .=
}
c_locale(0);
}
if (defined($output_syslib) && ($output_syslib !~ /^[\s\n]*$/)) {
#
# If non-empty there are some public symbols sets.
# First, mark everything private:
#
# then overwrite the public ones:
} elsif (defined($is_system_lib) &&
$is_system_lib eq 'NO_PUBLIC_SYMS') {
# Everything is private:
} else {
#
# assume public, the override will occur when version
# string matches /private/i. This is for 3rd party
# libraries.
#
}
if ($line =~ /^DEFAULT_CLASS=(.*)$/) {
$default_class = $1;
next;
}
$symbol =~ s/;*$//;
$version =~ s/:*$//;
next if ($symbol =~ /^\s*$/);
$class = $default_class;
$class = 'private';
}
if ($class eq 'private') {
$private_count++;
} else {
if ($output_syslib) {
# remove the double counting of this version:
$private_count--;
$count--;
}
$public_count++;
}
$count++;
}
if (! $count) {
return 0;
}
# Construct the info string:
$libtmp = "load_model_versioned_lib,$library,$abi:";
$libtmp .= "$version\[$versions{$version}\],";
}
$libtmp .=
"\[${count}symbols=${public_count}public+${private_count}private\]";
return $libtmp;
}
#
# Returns a non-empty string if the $path_to_library is recognized as a
# System (i.e. Solaris) ABI library for given abi. Returns undef
# otherwise. The returned string will either be the public symbol version
# name(s), "NO_PUBLIC_SYMS" if all symbols are private, or "-" if there
# is no versioning at all.
#
{
my ($path_to_library, $abi) = @_;
if (exists($is_system_library_cache{"$path_to_library|$abi"})) {
return $is_system_library_cache{"$path_to_library|$abi"};
}
foreach $dir (@lib_index_loaded) {
$key = "$dir|$path_to_library|$abi";
# try inode lookup (chases down unexpected symlinks)
$key = "$dir|$device/$inode|$abi";
}
last if (defined($def));
}
#
# we skip the case $path_to_library containing
# a single quote, so the cmd argument is protected.
#
$def = $1;
}
}
return $def;
}
#
# Loop over each object item in the working_dir.
# - $dir will be each one of these object directories.
# - $path_to_object will be the corresponding actual path
# to the the binary to be checked.
#
{
my ($dir, $path_to_object);
if (! $misc_check_databases_loaded_ok) {
#
# The load was attempted in check_objects() There is no
# point in continuing if that loading failed.
#
return;
}
"performing miscellaneous checks") . " ...\n\n");
while (defined($dir = next_dir_name())) {
# Map object output dir to actual path of the object:
if (! -f $path_to_object) {
}
# Check it:
}
}
#
# Routine to perform the misc. checks on a given binary object. Records
# the findings in object's output directory.
#
sub misc_check
{
my ($path_to_object, $dir) = @_;
# Load the entire dynamic profile for this object:
my $tmp;
$file = "$dir/profile.dynamic";
if (-f $file) {
if (! open($prof_fh, "<$file")) {
}
$cnt = 0;
while (<$prof_fh>) {
next if (/^\s*#/);
next if (/^\s*$/);
chomp;
# Skip the checking of special symbols:
next if (exists($skip_symbols{$sym}));
push(@profile, "$lib|$sym|$caller");
#
# We collect in @profile_short up to 10
# lib-sym-caller triples where all the libs are
# distinct. This is used to speed up some
# loops over the profile below: when we catch
# the lib-matching checks early in the loop we
# can exclude those checks from the remainder
# of the loop. Since a profile may involve
# 1000's of symbols this can be a savings.
#
push(@profile_short, "$lib|$sym|$caller");
$cnt++;
}
}
close($prof_fh);
}
#
# Misc Check #1:
# Go through dynamic profile looking for scoped local symbols:
#
my (%all_neededs, %lib_not_found);
my ($scoped_list, $scoped_msg);
foreach $key (keys(%all_neededs)) {
# %lib_not_found will be used below in check #2
$lib_not_found{$key}++;
$lib_not_found{$key_trim}++;
}
}
# We will need the abi of the object:
my $abi;
$abi = 'sparc';
} else {
$abi = 'i386';
}
}
foreach $libsymcaller (@profile) {
next unless ($libsymcaller =~ /\*DIRECT\*$/);
#
# Record direct symbols to improve our %wskip list used
# to speed up loops over symbols.
#
next unless (exists($scoped_symbol_all{$sym}));
#
# We only worry if a scoped call is a direct one. This
# assumes the user's support shared objects are also
# checked by appcert.
#
if (exists($scoped_symbol{"$lib|$sym"}) ||
exists($scoped_symbol{"$base|$sym"})) {
#
# This one is for checking on releases BEFORE
# the scoping actually occurred.
#
$scoped_msg .= "$base:$sym ";
$scoped_list .= "$path_to_object|$caller|$lib|$sym\n";
} elsif ($lib eq '*UNBOUND*' &&
exists($scoped_symbol_all{$sym})) {
#
# This one is for checking on releases AFTER the
# scoping.
#
# Assume these type of unbounds are deprecated
# if found in scoped_symbol_all. Double check it
# is in the all-needed-libs though:
#
if (defined($sc_sym) &&
exists($model{"$LIBC|$abi|$sc_sym"})) {
next;
}
split(/\|/, $sc_val);
#
# The general scoping that occurred for
# ld.so.1 makes the current heuristic
# somewhat less accurate. Unboundedness
# from other means has too good a chance
# of overlapping with the ld.so.1
# scoping. Note that the app likely does
# NOT have ld.so.1 in its neededs, but
# we still skip this case.
#
next if ($sc_lib eq 'ld.so.1');
if ($all_neededs{$sc_lib}) {
# note that $lib is '*UNBOUND*'
$scoped_msg .= "<unbound>:$sym ";
$scoped_list .=
"$path_to_object|$caller|$lib|$sym\n";
}
}
}
}
if (defined($scoped_msg)) {
my $problems = "$dir/check.problems";
# problems will be appended to the file:
if (! open($problems_fh, ">>$problems")) {
}
print $problems_fh
"MISC: REMOVED_SCOPED_SYMBOLS: $scoped_msg\n";
close($problems_fh);
$problems = "$dir/check.demoted_symbols";
# problems will be appended to the file:
if (! open($check_fh, ">>$problems")) {
}
print $check_fh $scoped_list;
close($check_fh);
}
#
# Misc Check #2
# Go through dynamic profile looking for special warnings.
#
next if (! $sub);
}
my $warnings_bind_has_non_direct = 0;
# it can never match:
}
if ($caller ne '*DIRECT*') {
# this will be used to speed up the *DIRECT* only case:
# it can never match:
}
}
foreach $lib (keys(%lib_not_found)) {
#
# add a placeholder symbol in %profile to indicate
# $lib is on dtneeded, but wasn't found. This will
# match a $sym = '*' warnings_bind misc check:
#
push(@profile,
"$lib|__ldd_indicated_file_not_found__|*DIRECT*");
}
#
# create a list of tags excluding the ones we know will be
# skipped in the $libsymcaller loop below.
#
foreach $tag0 (keys(%warnings_bind)) {
}
#
# we loop over @profile_short first, these will give us up to
# 10 different libraries early to help us shrink @tag_list
# as we go through the profile.
#
last if (! @tag_list);
next;
}
$new_tags = 0;
# try to get out early:
# try to get out early:
if ("$l_t|$s_t|$c_t" eq $match_t ||
"$base|$s_t|$c_t" eq $match_t) {
# shorten tag list:
push(@t, $tg);
}
@tag_list2 = @t;
$new_tags = 1;
}
}
}
if (%warnings) {
my $problems = "$dir/check.problems";
# append problems to the file:
if (! open($problems_fh, ">>$problems")) {
}
my $tag;
print $problems_fh "MISC: WARNING: $tag\n";
}
close($problems_fh);
}
}