#!/usr/perl5/bin/perl -w # # 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 # or http://www.opensolaris.org/os/licensing. # 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 # # # Copyright (c) 1996-2000 by Sun Microsystems, Inc. # All rights reserved. # #ident "%Z%%M% %I% %E% SMI" # # # This utility program reads the symcheck output of each binary and # creates additional output for then and an overall report. # require 5.005; use strict; use locale; use POSIX qw(locale_h); use Sun::Solaris::Utils qw(textdomain gettext); use File::Basename; use File::Path; use lib qw(/usr/lib/abi/appcert); use AppcertUtil; setlocale(LC_ALL, ""); textdomain(TEXT_DOMAIN); use vars qw( $tmp_report_dir $misc_check_databases_loaded_ok %result_list_hash %result_msg %warnings_found ); set_clean_up_exit_routine(\&clean_up_exit); import_vars_from_environment(); signals('on', \&interrupted); set_working_dir(); generate_reports(); clean_up(); exit 0; # # 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) { exiter("$command_name: " . sprintf(gettext( "cannot locate working directory: %s\n"), $working_dir)); } } # # Called when interrupted by user. # sub interrupted { $SIG{$_[0]} = 'DEFAULT'; signals('off'); clean_up_exit(1); } # # Does the cleanup and then exit with return code $rc. Note: The # utility routine exiter() will call this routine. # sub clean_up_exit { my ($rc) = @_; $rc = 0 unless ($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_report_dir) && -d $tmp_report_dir) { rmtree($tmp_report_dir); } } # # Top level routine for generating the additional reports. # sub generate_reports { # Make a tmp dir for the reporting work. $tmp_report_dir = create_tmp_dir($tmp_dir); if (! -d $tmp_report_dir) { exiter(nocreatedir($tmp_report_dir, $!)); } pmsg("\n"); print_line(); 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 profiled. # Output will be placed down in $dir, e.g. "$dir/report" # while (defined($dir = next_dir_name())) { # Map object output dir to actual path of the object: $path_to_object = dir_name_to_path($dir); # Make a report for it: report_object($path_to_object, $dir); } my $type; foreach $type (keys(%result_list_hash)) { $result_list_hash{$type} =~ s/\|+$//; } print_report(); my $tout; $tout = gettext( "Additional output regarding private symbols usage and other\n" . "data is in the directory:\n"); $tout .= "\n $working_dir\n\n"; $tout .= gettext( "see the appcert documentation for more information.\n"); pmsg("%s", $tout); clean_up(); # Remove any tmp directories and files. } # # Examines the symcheck output for a given binary object recording and # reporting and problems found. Generates additional reports and # summaries. # sub report_object { my ($object, $dir) = @_; my (%problems); my $problems_file = "$dir/check.problems"; my $problems_fh = do { local *FH; *FH }; open($problems_fh, "<$problems_file") || exiter(nofile($problems_file, $!)); # We need the "warning" msgs and text from the Misc Checks loaded: if (! defined($misc_check_databases_loaded_ok)) { $misc_check_databases_loaded_ok = load_misc_check_databases(); } my ($prob, $incomp, $c, $w); my $problem_count = 0; my $incomplete_count = 0; my $line_count = 0; while (<$problems_fh>) { chomp; $prob = 1; $incomp = 0; $line_count++; if (/^DYNAMIC: PRIVATE_SYMBOL_USE\s+(\d*)/) { $problems{'private_syms'} += $1; } elsif (/^DYNAMIC: UNBOUND_SYMBOL_USE\s+(\d*)/) { $problems{'unbound_syms'} += $1; $incomp = 1; } elsif (/^DYNAMIC: UNRECOGNIZED_SYMBOL_USE\s+(\d*)/) { $problems{'unrecognized_syms'} += $1; $incomp = 1; } elsif (/^DYNAMIC: NO_DYNAMIC_BINDINGS_FOUND\s*(.*)$/) { $problems{'no_dynamic_bindings'} .= "$1, "; $incomp = 1; } elsif (/^STATIC: LINKED_ARCHIVE\s+(.*)$/) { $problems{'static_linking'} .= "$1, "; } elsif (/^STATIC: COMPLETELY_STATIC/) { $problems{'completely_static'}++; } elsif (/^MISC: REMOVED_SCOPED_SYMBOLS:\s+(.*)$/) { $problems{'scoped_symbols'} .= "$1, "; } elsif (/^MISC: WARNING:\s+(INCOMPLETE\S+)/) { $problems{'warnings'} .= "$1|"; $incomp = 1; } elsif (/^MISC: WARNING:\s+(.*)$/) { $problems{'warnings'} .= "$1|"; } else { $prob = 0; } $problem_count += $prob; $incomplete_count += $incomp; } close($problems_fh); if ($line_count == 0) { # No problems at all, leave a comment message: open($problems_fh, ">$problems_file") || exiter(nofile($problems_file, $!)); print $problems_fh "# NO_PROBLEMS_DETECTED\n"; close($problems_fh); } if ($problem_count == 0) { $result_list_hash{'passed'} .= "$object|"; return; } if ($incomplete_count == $problem_count) { $result_list_hash{'incomplete'} .= "$object|"; } else { $result_list_hash{'failed'} .= "$object|"; } my $m; if ($m = $problems{'private_syms'}) { $result_list_hash{'private_syms'} .= "$object|"; $result_msg{$object} .= "$m " . gettext("private symbols") . "; "; } if ($m = $problems{'unbound_syms'}) { $result_list_hash{'unbound_syms'} .= "$object|"; $result_msg{$object} .= "$m " . gettext("unbound symbols") . "; "; # add this case to the warnings output at end of report. my $tag = 'unbound symbols'; $warnings_found{$tag} .= "$object|"; if (! exists($warnings_desc{$tag})) { my $desc = gettext("unbound symbols"); $warnings_desc{$tag} = $desc; } } if ($m = $problems{'unrecognized_syms'}) { $result_list_hash{'unrecognized_syms'} .= "$object|"; $result_msg{$object} .= "$m " . gettext("unrecognized symbols") . "; "; # Add this case to the warnings output at end of report. my $tag = 'unrecognized symbols'; $warnings_found{$tag} .= "$object|"; if (! exists($warnings_desc{$tag})) { my $desc = gettext("unrecognized symbols"); $warnings_desc{$tag} = $desc; } } if ($m = $problems{'static_linking'}) { $result_list_hash{'static_linking'} .= "$object|"; $m =~ s/,\s*$//; $result_msg{$object} .= sprintf(gettext( "statically linked with %s"), $m) . "; "; # Add this case to the warnings output at end of report. my $tag = 'statically linked'; $warnings_found{$tag} .= "$object|"; if (! exists($warnings_desc{$tag})) { my $desc = gettext("static linking of Solaris libraries"); $warnings_desc{$tag} = $desc; } } if ($problems{'completely_static'}) { $result_list_hash{'completely_static'} .= "$object|"; $result_msg{$object} .= gettext("completely statically linked") . "; "; # Add this case to the warnings output. my $tag = gettext("completely statically linked"); $warnings_found{$tag} .= "$object|"; my $desc = gettext("complete static linking of Solaris libraries"); if (! exists($warnings_desc{$tag})) { $warnings_desc{$tag} = $desc; } } elsif ($m = $problems{'no_dynamic_bindings'}) { # # Note we skip this error if it is completely static. # The app could technically be SUID as well. # $result_list_hash{'no_dynamic_bindings'} .= "$object|"; $m =~ s/,\s*$//; $m = " : $m"; $m =~ s/ : NO_SYMBOL_BINDINGS_FOUND//; $m =~ s/^ :/:/; $result_msg{$object} .= gettext("no bindings found") . "$m; "; } if ($m = $problems{'scoped_symbols'}) { $m =~ s/[,\s]*$//; $result_list_hash{'scoped_symbols'} .= "$object|"; $c = scalar(my @a = split(' ', $m)); $result_msg{$object} .= "$c " . gettext("demoted (removed) private symbols") . ": $m; "; # Add this case to the warnings output. my $tag = 'scoped symbols'; $warnings_found{$tag} .= "$object|"; my $desc = gettext( "dependency on demoted (removed) private Solaris symbols"); if (! exists($warnings_desc{$tag})) { $warnings_desc{$tag} = $desc; } } if ($m = $problems{'warnings'}) { foreach $w (split(/\|/, $m)) { next if ($w =~ /^\s*$/); $c = $w; if (defined($warnings_desc{$c})) { $c = $warnings_desc{$c}; $c = gettext($c); } $c =~ s/;//g; $result_msg{$object} .= "$c; "; $warnings_found{$w} .= "$object|"; } } $result_msg{$object} =~ s/;\s+$//; } # # Create the top level roll-up report. # sub print_report { # Count the number of passed, failed and total binary objects: my(@a); my($r_passed, $r_incomp, $r_failed); if (exists($result_list_hash{'passed'})) { $r_passed = $result_list_hash{'passed'}; } else { $r_passed = ''; } if (exists($result_list_hash{'incomplete'})) { $r_incomp = $result_list_hash{'incomplete'}; } else { $r_incomp = ''; } if (exists($result_list_hash{'failed'})) { $r_failed = $result_list_hash{'failed'}; } else { $r_failed = ''; } my $n_passed = scalar(@a = split(/\|/, $r_passed)); my $n_incomp = scalar(@a = split(/\|/, $r_incomp)); my $n_failed = scalar(@a = split(/\|/, $r_failed)); my $n_checked = $n_passed + $n_incomp + $n_failed; my ($summary_result, $msg, $output, $object); if ($n_checked == 0) { $summary_result = $text{'Summary_Result_None_Checked'}; } elsif ($n_failed > 0) { $summary_result = $text{'Summary_Result_Some_Failed'}; } elsif ($n_incomp > 0) { $summary_result = $text{'Summary_Result_Some_Incomplete'}; } else { $summary_result = $text{'Summary_Result_All_Passed'}; } # place the info in problem count file: my $cnt_file = "$working_dir/ProblemCount"; my $pcount_fh = do { local *FH; *FH }; if (! open($pcount_fh, ">$cnt_file")) { exiter(nofile($cnt_file, $!)); } print $pcount_fh "$n_failed / $n_checked binary_objects_had_problems\n"; print $pcount_fh "$n_incomp / $n_checked could_not_be_completely_checked\n"; print $pcount_fh "NO_PROBLEMS_LIST: $r_passed\n"; print $pcount_fh "INCOMPLETE_LIST: $r_incomp\n"; print $pcount_fh "PROBLEMS_LIST: $r_failed\n"; close($pcount_fh); # # Set the overall result code. # This is used to communicate back to the appcert script to # indicate how it should exit(). The string must start with the # exit number, after which a message may follow. # if ($n_checked == 0) { overall_result_code("3 => nothing_checked"); } elsif ($n_failed > 0) { overall_result_code("2 => some_problems_detected($n_failed)"); } elsif ($n_incomp > 0) { overall_result_code("1 => " . "some_binaries_incompletely_checked($n_incomp)"); } else { overall_result_code("0 => no_problems_detected"); } my ($sp0, $sp, $sf, $si); # PASS & FAIL spacing tags. $sp0 = ' '; if ($batch_report) { $sp = 'PASS '; $sf = 'FAIL '; $si = 'INC '; } else { $sp = $sp0; $sf = $sp0; $si = $sp0; } $msg = sprintf(gettext("Summary: %s"), $summary_result) . "\n\n"; my $format = gettext("A total of %d binary objects were examined."); $msg .= sprintf($format, $n_checked) . "\n\n\n"; $output .= $msg; my $fmt1 = gettext( "The following (%d of %d) components had no problems detected:"); if ($n_passed > 0) { $output .= sprintf($fmt1, $n_passed, $n_checked); $output .= "\n\n"; foreach $object (split(/\|/, $r_passed)) { $output .= "${sp}$object\n"; } $output .= "\n"; } my $fmt2 = gettext( "The following (%d of %d) components had no problems detected,\n" . " but could not be completely checked:"); if ($n_incomp > 0) { $output .= sprintf($fmt2, $n_incomp, $n_checked); $output .= "\n\n"; foreach $object (split(/\|/, $r_incomp)) { $msg = $result_msg{$object}; $output .= "${si}$object\t($msg)\n"; } $output .= "\n"; } my $fmt3 = gettext( "The following (%d of %d) components have potential " . "stability problems:"); if ($n_failed > 0) { $output .= sprintf($fmt3, $n_failed, $n_checked); $output .= "\n\n"; foreach $object (split(/\|/, $r_failed)) { $msg = $result_msg{$object}; $output .= "${sf}$object\t($msg)\n"; } $output .= "\n"; } $output .= "\n" . get_summary(); $output .= "\n" . get_warnings(); my $report_file = "$working_dir/Report"; my $report_fh = do { local *FH; *FH }; open($report_fh, ">$report_file") || exiter(nofile($report_file, $!)); print $report_fh $output; close($report_fh); system($cmd_more, $report_file); } # # Collects all of the warnings issued for the binaries that were # checked. Returns the warning text that will go into the roll-up # report. # sub get_warnings { my ($w, $c, $output, $count); if (! %warnings_found) { return ''; # appends null string to output text } $output = gettext("Summary of various warnings:") . "\n\n"; my(@a); foreach $w (keys(%warnings_found)) { $warnings_found{$w} =~ s/\|+$//; $count = scalar(@a = split(/\|/, $warnings_found{$w})); $c = $w; if (defined($warnings_desc{$c})) { $c = $warnings_desc{$c}; } $c = gettext($c); $output .= " - $c " . sprintf(gettext( "(%d binaries)\n"), $count); $output .= "\n"; } $output .= "\n"; return $output; } # # Computes the summary information for each binary object that was # checked. Returns the text that will go into the roll-up report. # sub get_summary { my ($dir, $file); my (%lib_private, %libsym_private); my (%libapp, %libapp_private); my ($bin, $arch, $direct, $lib, $class, $sym); while (defined($dir = next_dir_name())) { # This is where the public symbol list is: $file = "$dir/check.dynamic.public"; my %app_public; my %app_sym_public; my %app_private; my %app_sym_private; if (-s $file) { my $publics_fh = do { local *FH; *FH }; open($publics_fh, "<$file") || exiter(nofile($file, $!)); while (<$publics_fh>) { next if (/^\s*#/); chomp; ($bin, $arch, $direct, $lib, $class, $sym) = split(/\|/, $_); $libapp{"$lib|$bin"}++; $app_public{$lib}++; $app_sym_public{"$lib|$sym"}++; } close($publics_fh); } # This is where the private symbol list is: $file = "$dir/check.dynamic.private"; if (-s $file) { my $privates_fh = do { local *FH; *FH }; open($privates_fh, "<$file") || exiter(nofile($file, $!)); while (<$privates_fh>) { next if (/^\s*#/); chomp; ($bin, $arch, $direct, $lib, $class, $sym) = split(/\|/, $_); $lib_private{$lib}++; $libsym_private{"$lib|$sym"}++; $libapp_private{"$lib|$bin"}++; $libapp{"$lib|$bin"}++; $app_private{$lib}++; $app_sym_private{"$lib|$sym"}++; } close($privates_fh); } write_app_summary($dir, \%app_public, \%app_sym_public, \%app_private, \%app_sym_private); } my ($app_total, $app_private_total); my ($key, $lib2, $app2, $sym2); my $val; my $text; foreach $lib (sort(keys(%lib_private))) { $app_total = 0; foreach $key (keys(%libapp)) { ($lib2, $app2) = split(/\|/, $key); $app_total++ if ($lib eq $lib2); } $app_private_total = 0; foreach $key (keys(%libapp_private)) { ($lib2, $app2) = split(/\|/, $key); $app_private_total++ if ($lib eq $lib2); } my @list; while (($key, $val) = each(%libsym_private)) { ($lib2, $sym2) = split(/\|/, $key); next unless ($lib eq $lib2); push(@list, "$sym2 $val"); } $text .= private_format($lib, $app_total, $app_private_total, @list); } if (! defined($text)) { return ''; # appends null string to output report. } return $text; } # # Given the symbols and counts of private symbols used by all binaries # that were checked, returns a pretty-printed format table of the # symbols. This text goes into the roll-up report and the summary.dynamic # file. # sub private_format { my ($lib, $tot, $priv, @list) = @_; my (@sorted) = sort_on_count(@list); my $formatted = list_format(' ', @sorted); my $text; my $libbase = basename($lib); $text = sprintf(gettext( "Summary of Private symbol use in %s\n"), $lib); my $fmt = gettext("%d binaries used %s, %d of these used private symbols"); $text .= sprintf($fmt, $tot, $libbase, $priv); $text .= "\n\n$formatted\n"; return $text; } # # Given the public/private symbol and library usage information for a # binary object, creates an output file with this information formatted # in tables. # sub write_app_summary { my ($dir, $public, $sym_public, $private, $sym_private) = @_; my $outfile = "$dir/summary.dynamic"; my $summary_fh = do { local *FH; *FH }; open($summary_fh, ">$outfile") || exiter(nofile($outfile, $!)); my $path_to_object = dir_name_to_path($dir); my ($tmp1, $tmp2, $tmp3); $tmp1 = gettext("ABI SYMBOL USAGE SUMMARY REPORT"); $tmp2 = '*' x length($tmp1); print $summary_fh "$tmp2\n$tmp1\n$tmp2\n\n"; print $summary_fh " ", sprintf(gettext( "Binary Object: %s\n"), $path_to_object); my $uname_a = `$cmd_uname -a`; print $summary_fh " ", sprintf(gettext("System: %s\n"), $uname_a); $tmp1 = gettext("References to shared objects in the Solaris ABI"); $tmp2 = '*' x length($tmp1); print $summary_fh "$tmp2\n$tmp1\n$tmp2\n\n"; my (%libs, $lib, $maxlen, $len); $maxlen = 0; foreach $lib (keys(%$public), keys(%$private)) { $len = length($lib); $maxlen = $len if ($len > $maxlen); $libs{$lib} = 1; } if (! %libs) { my $str = gettext( " NONE FOUND. Possible explanations:\n" . " - the dynamic profiling failed, see ldd(1), ld.so.1(1)\n" . " - the object is SUID or SGID\n" . " - the object is completely statically linked.\n" ); print $summary_fh $str, "\n"; close($summary_fh); return; } foreach $lib (sort(keys(%libs))) { print $summary_fh " $lib\n"; } print $summary_fh "\n"; my ($len1, $len2, $len3); my $heading = ' ' . gettext("Library"); $heading .= ' ' x ($maxlen + 6 - length($heading)); $len1 = length($heading) - 2; my $public_str = gettext("Public"); $len2 = length($public_str); my $private_str = gettext("Private"); $len3 = length(" $private_str"); $heading .= "$public_str $private_str"; $tmp3 = $heading; $tmp3 =~ s/\S/-/g; $tmp1 = gettext("Symbol usage statistics (summary by shared object)"); $tmp2 = '*' x length($tmp1); print $summary_fh "$tmp2\n$tmp1\n$tmp2\n\n"; print $summary_fh "$heading\n"; print $summary_fh "$tmp3\n"; my ($pub, $priv, $str); foreach $lib (sort(keys(%libs))) { $pub = $public->{$lib}; $priv = $private->{$lib}; $pub = 0 if (! defined($pub)); $priv = 0 if (! defined($priv)); $str = ' '; $str .= sprintf("%-${len1}s", $lib); $str .= sprintf("%${len2}s", $pub); $str .= sprintf("%${len3}s", $priv); print $summary_fh $str, "\n"; } print $summary_fh "\n"; $tmp1 = gettext("Symbol usage (detailed inventory by shared object)"); $tmp2 = '*' x length($tmp1); print $summary_fh "$tmp2\n$tmp1\n$tmp2\n\n"; my (@pub, @priv, $lib2, $sym2, $text, $key); foreach $lib (sort(keys(%libs))) { @pub = (); @priv = (); foreach $key (keys(%$sym_public)) { next unless (index($key, $lib) == 0); ($lib2, $sym2) = split(/\|/, $key, 2); next unless ($lib2 eq $lib); push(@pub, $sym2); } foreach $key (keys(%$sym_private)) { next unless (index($key, $lib) == 0); ($lib2, $sym2) = split(/\|/, $key, 2); next unless ($lib2 eq $lib); push(@priv, $sym2); } next if (! @pub && ! @priv); my $fmt = gettext("Symbols in %s Directly Referenced"); $text = sprintf($fmt, $lib); if (@pub) { $lib2 = scalar(@pub); $text .= sprintf(gettext( " %d public symbols are used:\n"), $lib2); $text .= list_format(' ', sort(@pub)); $text .= "\n"; } if (@priv) { $lib2 = scalar(@priv); $text .= sprintf(gettext( " %d private symbols are used:\n"), $lib2); $text .= list_format(' ', sort(@priv)); $text .= "\n"; } print $summary_fh $text; } close($summary_fh); }