xref: /illumos-gate/usr/src/cmd/abi/appcert/scripts/symcheck.pl (revision 8b80e8cb6855118d46f605e91b5ed4ce83417395)
1#!/usr/perl5/bin/perl -w
2#
3# CDDL HEADER START
4#
5# The contents of this file are subject to the terms of the
6# Common Development and Distribution License, Version 1.0 only
7# (the "License").  You may not use this file except in compliance
8# with the License.
9#
10# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
11# or http://www.opensolaris.org/os/licensing.
12# See the License for the specific language governing permissions
13# and limitations under the License.
14#
15# When distributing Covered Code, include this CDDL HEADER in each
16# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
17# If applicable, add the following below this CDDL HEADER, with the
18# fields enclosed by brackets "[]" replaced with your own identifying
19# information: Portions Copyright [yyyy] [name of copyright owner]
20#
21# CDDL HEADER END
22#
23#
24# ident	"%Z%%M%	%I%	%E% SMI"
25#
26# Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
27# Use is subject to license terms.
28#
29
30#
31# This utility program loads the unstable behavior databases, then reads
32# the symprof output for each binary, and record any detected unstable
33# behavior in the binary's output directory.
34#
35
36require 5.005;
37use strict;
38use locale;
39use POSIX qw(locale_h);
40use Sun::Solaris::Utils qw(textdomain gettext);
41use File::Basename;
42use File::Path;
43
44use lib qw(/usr/lib/abi/appcert);
45use AppcertUtil;
46
47setlocale(LC_ALL, "");
48textdomain(TEXT_DOMAIN);
49
50use vars qw(
51	$LIBC
52	$tmp_check_dir
53	$binaries_checked_count
54	$misc_check_databases_loaded_ok
55	%load_model_default
56	%load_error
57	%model_loaded
58	%model
59	%is_system_library_cache
60);
61
62set_clean_up_exit_routine(\&clean_up_exit);
63
64initialize_variables();
65
66import_vars_from_environment();
67
68signals('on', \&interrupted);
69
70set_working_dir();
71
72load_model_index();
73
74check_objects();
75
76clean_up();
77
78exit 0;
79
80#
81# Set up any variables.
82#
83sub initialize_variables
84{
85	# Here is what we call libc:
86	$LIBC = '/usr/lib/libc.so.1';
87}
88
89#
90# working_dir has been imported by import_vars_from_environment()
91# A sanity check is performed here to make sure it exists.
92#
93sub set_working_dir
94{
95	if (! defined($working_dir) || ! -d $working_dir) {
96		exiter("$command_name: " . sprintf(gettext(
97		    "cannot locate working directory: %s\n"), $working_dir));
98	}
99}
100
101#
102# Called when interrupted by a signal.
103#
104sub interrupted
105{
106	$SIG{$_[0]} = 'DEFAULT';
107	signals('off');
108	clean_up_exit(1);
109}
110
111#
112# Does the cleanup then exit with return code $rc.  Note: The utility
113# routine exiter() will call this routine.
114#
115sub clean_up_exit
116{
117	my ($rc) = @_;
118	$rc = 0 unless ($rc);
119
120	clean_up();
121	exit $rc;
122}
123
124#
125# General cleanup activities are placed here.  There may not be an
126# immediate exit after this cleanup.
127#
128sub clean_up
129{
130	if (defined($tmp_check_dir) && -d $tmp_check_dir) {
131		rmtree($tmp_check_dir);
132	}
133}
134
135#
136# Top level routine to initialize databases and then loop over the
137# objects and call the checking routines on each one.
138#
139sub check_objects
140{
141	# Make a tmp dir for the checking work.
142	$tmp_check_dir = create_tmp_dir($tmp_dir);
143
144	if (! -d $tmp_check_dir) {
145		exiter(nocreatedir($tmp_check_dir, $!));
146	}
147
148	emsg("\n" . gettext(
149	    "checking binary objects for unstable practices") . " ...\n\n");
150
151	my ($dir, $path_to_object);
152
153	#
154	# Loop over each object item in the working_dir.
155	#  - $dir will be each one of these object directories.
156	#  - $path_to_object will be the corresponding actual path
157	#    to the the binary to be checked.
158	# Output will be placed down in $dir, e.g. "$dir/check.foo"
159	#
160
161	$binaries_checked_count = 0;
162
163	#
164	# We need to load the Misc Databases to get any modifications to
165	# the symbol database. E.g. gethostname should be public.
166	#
167	$misc_check_databases_loaded_ok = load_misc_check_databases();
168
169	while (defined($dir = next_dir_name())) {
170
171		# Map object output dir to actual path of the object:
172		$path_to_object = dir_name_to_path($dir);
173
174		if (! -f $path_to_object) {
175			exiter(nopathexist($path_to_object, $!));
176		}
177
178		# Check it:
179		emsg(gettext("checking: %s\n"), $path_to_object);
180
181		static_check_object($path_to_object, $dir);
182		dynamic_check_object($path_to_object, $dir);
183	}
184
185	if ($binaries_checked_count == 0) {
186		exiter("$command_name: " . gettext(
187		    "no binary objects where checked."));
188	}
189
190	# Do additional heuristic checks of unstable behavior:
191	perform_misc_checks();
192
193	clean_up();	# Remove any tmp dirs and files.
194}
195
196#
197# Reads in the static profile (i.e. the symbols exported by bin in
198# .text section) and calls the static archive checking routine.
199#
200sub static_check_object
201{
202	my ($path_to_object, $dir) = @_;
203
204	# The profile output file created by static_profile() in symprof.
205
206	my $profile_file = "$dir/profile.static";
207
208	my $err_fmt = gettext(
209	    "binary object %s has no static profile: %s: %s\n");
210	if (! -f $profile_file) {
211		emsg("$command_name: " . $err_fmt, $path_to_object,
212		    $profile_file, $!);
213		return 0;
214	}
215
216	my $profile_fh = do { local *FH; *FH };
217	if (! open($profile_fh, "<$profile_file")) {
218		exiter(nofile($profile_file, $!));
219	}
220
221	my ($profile, $lib, $lib2, $base, %libs_needed);
222
223	my $completely_statically_linked = 0;
224
225	while (<$profile_fh>) {
226		$profile .= $_;
227		if (/^\s*#dtneeded:\s*(.*)$/) {
228			#
229			# record the bare name, e.g. "libc" of the
230			# (direct) dtneededs.
231			#
232			foreach $lib (split(/\s+/, $1)) {
233				next if ($lib eq '');
234				$base = $lib;
235				# record it as libc.so.1 -> libc
236				$base =~ s/\.so\..*$//;
237				$base =~ s/\.so$//;
238				$libs_needed{$base} = $lib;
239				$libs_needed{basename($base)} = $lib;
240			}
241		} elsif (/^\s*#SKIPPED_TEST:\s+STATICALLY_LINKED/) {
242			#
243			# Record statical linking if it takes place
244			# since it indicates to skip the test.
245			#
246			$completely_statically_linked = 1;
247		}
248	}
249	close($profile_fh);
250
251	my $problems = "$dir/check.problems";
252
253	my $problems_fh = do { local *FH; *FH };
254	if (! open($problems_fh, ">>$problems")) {
255		exiter(nofile($problems, $!));
256	}
257
258	if ($completely_statically_linked) {
259		print $problems_fh "STATIC: COMPLETELY_STATIC"  . "\n";
260	}
261
262	my (%saw_lib);
263	if (! defined($profile)) {
264		close($problems_fh);
265		return;
266	}
267	foreach $lib (lib_static_check($profile)) {
268		#
269		# lib_static_check returns a list of statically linked
270		# libraries, however to be on the safe side we will skip
271		# false positives our dtneeded's show they are really
272		# dynamically linked in.
273		#
274
275		next if ($libs_needed{$lib});
276		next if ($libs_needed{basename($lib)});
277		next if ($saw_lib{basename($lib)}++);
278
279		$lib2 = $lib;
280		$lib2 =~ s/\.a$//;
281		next if ($libs_needed{$lib2});
282		next if ($libs_needed{basename($lib2)});
283
284		# Otherwise, record in the problems file:
285		print $problems_fh "STATIC: LINKED_ARCHIVE $lib" . "\n";
286	}
287	close($problems_fh);
288}
289
290#
291# Takes as input the static profile (e.g. the .text symbols) and returns
292# a list of suspected statically linked Solaris archive libraries.
293#
294sub lib_static_check
295{
296	my ($profile) = @_;
297
298	my ($line, $area, $extent, $type, $sym, $obj);
299
300	my (%symbols);
301
302	#
303	# Working on lines like:
304	# /bin/ftp|TEXT|GLOB|FUNC|glob
305	# /bin/ftp|TEXT|GLOB|FUNC|help
306	#
307
308	# First record all the symbols in the TEXT area:
309
310	foreach $line (split(/\n/, $profile)) {
311		next unless ($line =~ /\bTEXT\b/);
312		($obj, $area, $extent, $type, $sym) = split(/\|/, $line);
313
314		$symbols{$sym} = 1;
315	}
316
317	my (@static_libs);
318
319	# Next, check against the library heuristics for static linking:
320
321	# libc.a:
322
323	if (exists($symbols{'_exit'})) {
324		push(@static_libs, "/usr/lib/libc.a");
325	}
326
327	# libsocket.a:
328
329	if (exists($symbols{'socket'}) && exists($symbols{'_socket'}) &&
330	    exists($symbols{'bind'}) && exists($symbols{'_bind'}) &&
331	    exists($symbols{'connect'}) && exists($symbols{'_connect'})) {
332			push(@static_libs, "/usr/lib/libsocket.a");
333	}
334
335	# libnsl.a:
336
337	if (exists($symbols{'_xti_bind'}) && exists($symbols{'_xti_connect'}) &&
338	    exists($symbols{'_tx_bind'}) && exists($symbols{'_tx_connect'})) {
339			push(@static_libs, "/usr/lib/libnsl.a");
340	}
341
342	return @static_libs;
343}
344
345#
346# Reads in the dynamic profile from the object's output directory.
347# Loads any needed public/private Solaris library symbol models.
348# Records unstable use of any private Solaris symbols in the object's
349# output directory.
350#
351sub dynamic_check_object
352{
353	my ($path_to_object, $dir) = @_;
354
355	# Location of the dynamic profile output:
356	my $profile_file = "$dir/profile.dynamic";
357
358	my $err_fmt = gettext(
359	    "binary object %s has no dynamic profile: %s: %s\n");
360	if (! -f $profile_file) {
361		emsg("$command_name: " . $err_fmt, $path_to_object,
362		    $profile_file, $!);
363		return 0;
364	}
365
366	my $profile_fh = do { local *FH; *FH };
367	if (! open($profile_fh, "<$profile_file")) {
368		exiter(nofile($profile_file, $!));
369	}
370
371	$binaries_checked_count++;
372
373	#
374	# Variables to hold temporary items:
375	#
376	# %library_list will be a hash of "to" libraries we need to load.
377	# @symbol_list will be an array of the "lib|sym" pairs.
378	#
379
380	my (%library_list, @symbol_list, @unbound_list);
381	my ($to, $sym, $from, $binary, $line);
382
383	my ($to_is_sys_lib, $from_is_sys_lib);
384
385	#
386	# profile lines look like:
387	# /bin/ftp|*DIRECT*|libsocket.so.1|socket
388	# /bin/ftp|libnsl.so.1|libc.so.1|mutex_lock
389	#
390	# or:
391	#
392	# /bin/ftp|*DIRECT*|/usr/lib/libsocket.so.1|socket
393	# /bin/ftp|/usr/lib/libnsl.so.1|/usr/lib/libc.so.1|mutex_lock
394	#
395
396	my ($abi, $type, $wordsize, $endian, $e_machine) =
397	    bin_type($path_to_object);
398
399	#
400	# Setting abi to 'any' will allow continuation when
401	# we encounter an abi we do not recognize.
402	#
403	if (! defined($abi) || $abi eq 'unknown') {
404		$abi = 'any';
405	} else {
406		#
407		# Always try to load libc.  This will be used for symbol
408		# migration to libc checks.
409		#
410		if (! exists($load_model_default{$abi})) {
411			load_model($LIBC, $abi);
412		}
413		$load_model_default{$abi} = 1;
414	}
415
416	my $dynamic_bindings_count = 0;
417	my $no_bindings_msg;
418
419	while (<$profile_fh>) {
420		chomp;
421
422		if (/^\s*#/) {
423		    if (/NO_BINDINGS_FOUND\s*(.*)$/) {
424			my $msg = $1;
425			if ($msg =~ /^\s*$/) {
426				$no_bindings_msg = 'NO_SYMBOL_BINDINGS_FOUND';
427			} else {
428				$no_bindings_msg = $msg;
429			}
430		    }
431		    next;
432		}
433
434		($binary, $from, $to, $sym) = split(/\|/, $_, 4);
435
436		$dynamic_bindings_count++;
437
438		# Skip the checking of reverse calls:
439		next if ($from eq "*REVERSE*");
440
441		# Skip the checking of special symbols:
442		next if (exists($skip_symbols{$sym}));
443
444		# Accumulate unbounds, but otherwise skip them:
445		if ($to eq "*UNBOUND*") {
446			push(@unbound_list, "$from|$sym");
447			next;
448		}
449
450		# Record if the "to" object is a system library:
451		$to_is_sys_lib = is_system_library($to, $abi);
452
453		if ($from eq "*DIRECT*") {
454			$from_is_sys_lib = 0;
455		} else {
456			#
457			# Otherwise we may check its calls. See if it is
458			# a system lib:
459			#
460			$from_is_sys_lib = is_system_library($from, $abi);
461		}
462
463		#
464		# We will pass judgement on *DIRECT* calls and indirect
465		# calls from a library we do not recognize.
466		#
467		if ($from_is_sys_lib) {
468			next;
469		}
470		if (! $to_is_sys_lib) {
471			# Call to a middleware or supporting library.
472			next;
473		}
474
475		$library_list{$to} = 1;
476		push(@symbol_list, "$from|$to|$abi|$sym");
477	}
478
479	close($profile_fh);
480
481	my $file;
482
483	my $problems_fh = do { local *FH; *FH };
484	if ($dynamic_bindings_count == 0 && defined($no_bindings_msg)) {
485		$file = "$dir/check.problems";
486
487		if (! open($problems_fh, ">>$file")) {
488			exiter(nofile($file, $!));
489		}
490
491		print $problems_fh "DYNAMIC: NO_DYNAMIC_BINDINGS_FOUND" .
492		    " $no_bindings_msg\n";
493		close($problems_fh);
494		return;
495	}
496
497	my ($lib, $str);
498
499	$file = "$dir/check.dynamic.abi_models";
500	my $model_info_fh = do { local *FH; *FH };
501	if (! open($model_info_fh, ">$file")) {
502		exiter(nofile($file, $!));
503	}
504
505	# Load all the needed library models:
506	my ($s1, $s2);
507	$s1 = ",NO_PUBLIC/PRIVATE_MODEL_FOUND-SKIPPING_CHECK_FOR_THIS_LIBRARY";
508	$s2 = "ERROR_LOADING_PUBLIC/PRIVATE_MODEL";
509
510	foreach $lib (keys %library_list) {
511		if (! load_model($lib, $abi) && ! $load_error{"$lib|$abi"}) {
512			$load_error{"$lib|$abi"} = 1;
513		}
514		$str = $model_loaded{"$lib|$abi"};
515		if ($str eq '__FAILED__') {
516			$str .= $s1;
517		} elsif (! $str) {
518			$str = $s2;
519		}
520		print $model_info_fh "$lib:$str\n";
521	}
522	close($model_info_fh);
523
524	my ($lib_abi_sym, $class, %result_list);
525
526	my ($l, $a, $s);
527	foreach $lib_abi_sym (@symbol_list) {
528
529		($from, $lib_abi_sym) = split(/\|/, $lib_abi_sym, 2);
530
531		($l, $a, $s) = split(/\|/, $lib_abi_sym);
532
533		if (! exists($model{$lib_abi_sym})) {
534			#
535			# Check the library.  If it is not in
536			# model_loaded, then we claim it is not a
537			# library we are interested in.
538			#
539			next if (! exists($model_loaded{"$l|$a"}));
540			next if ($model_loaded{"$l|$a"} eq '__FAILED__');
541
542			# it is an unrecognized symbol:
543			$result_list{'unrecognized'} .=
544			    "$from|$lib_abi_sym" . "\n";
545			next;
546		}
547
548		# N.B. $lib_abi_sym and $l may have been altered above.
549		$class = $model{$lib_abi_sym};
550		$line = "$path_to_object|$a|$from|$l|$class|$s" . "\n";
551
552		if ($class !~ /^(public|private|unclassified)$/) {
553			exiter("$command_name" . sprintf(gettext(
554			    "unrecognized symbol class: %s"), $class));
555		}
556
557		$result_list{$class} .= $line;
558	}
559
560	if (@unbound_list) {
561		my $ldd_file = "$dir/profile.dynamic.ldd";
562		my $tmp;
563		if (-f $ldd_file) {
564			my $ldd_info_fh = do { local *FH; *FH };
565			if (! open($ldd_info_fh, "<$ldd_file")) {
566				exiter(nofile($ldd_file, $!));
567			}
568			while (<$ldd_info_fh>) {
569				$tmp .= '# ' . $_ if (/not\s+found/);
570			}
571			close($ldd_info_fh);
572		}
573		if (defined($tmp)) {
574			$result_list{'unbound'} = $tmp;
575		}
576		$result_list{'unbound'} .= join("\n", @unbound_list) . "\n";
577	}
578
579	my $count;
580
581	my @classes = qw(private public unbound unclassified unrecognized);
582
583	foreach $class (@classes) {
584
585		next if (! exists($result_list{$class}));
586
587		$file = "$dir/check.dynamic.$class";
588
589		my $outfile_fh = do { local *FH; *FH };
590		if (! open($outfile_fh, ">$file")) {
591			exiter(nofile($file, $!));
592		}
593		if ($class eq 'private') {
594			print $outfile_fh
595			    $text{'Message_Private_Symbols_Check_Outfile'};
596		} elsif ($class eq 'public') {
597			print $outfile_fh
598			    $text{'Message_Public_Symbols_Check_Outfile'};
599		}
600		print $outfile_fh $result_list{$class};
601		close($outfile_fh);
602	}
603
604	$file = "$dir/check.problems";
605
606	if (! open($problems_fh, ">>$file")) {
607		exiter(nofile($file, $!));
608	}
609
610	if (exists($result_list{'private'})) {
611		$count = scalar(my @a = split(/\n/, $result_list{'private'}));
612		print $problems_fh "DYNAMIC: PRIVATE_SYMBOL_USE $count\n";
613	}
614	if (exists($result_list{'unbound'})) {
615		$count = scalar(@unbound_list);
616		print $problems_fh "DYNAMIC: UNBOUND_SYMBOL_USE $count\n";
617	}
618	if (exists($result_list{'unrecognized'})) {
619		$count =
620		    scalar(my @a = split(/\n/, $result_list{'unrecognized'}));
621		print $problems_fh "DYNAMIC: UNRECOGNIZED_SYMBOL_USE $count\n";
622	}
623
624	close($problems_fh);
625}
626
627#
628# Loads a system model for a library on demand.
629#
630# Input is a library to load and the architecture ABI.
631#
632# On successful completion, 1 is returned and the associative array:
633#
634#	%model{"$library|$abi|$symbol"} = {public,private}
635#
636#	is set.
637#
638sub load_model
639{
640	#
641	# Returns 1 if the model was successfully loaded, or returns 0
642	# if it was not.
643	#
644
645	my ($library, $abi) = @_;
646
647	#
648	# This %model_loaded hash records the following states:
649	#    <string>	Method by which successfully loaded.
650	#    __FAILED__	Failed to loaded successfully.
651	#    undef      Have not tried to load yet.
652	#
653	if (exists($model_loaded{"$library|$abi"})) {
654		if ($model_loaded{"$library|$abi"} eq '__FAILED__') {
655			return 0;
656		} elsif ($model_loaded{"$library|$abi"}) {
657			return 1;
658		}
659	}
660
661	my ($loaded, $ok);
662
663	$loaded = 1 if (load_model_versioned_lib($library, $abi));
664
665	# Record the result so we do not have to repeat the above:
666
667	if ($loaded) {
668		$ok = "OK";
669		my $tweaks = load_tweaks($library, $abi);
670		$ok .= ",Model_Tweaks\[$tweaks\]" if ($tweaks);
671		$model_loaded{"$library|$abi"} = $ok;
672	} else {
673		$model_loaded{"$library|$abi"} = '__FAILED__';
674	}
675
676	return $loaded;
677}
678
679#
680# Routine to load into %model any special modifications to the Solaris
681# symbol Models kept in the etc.* file.
682#
683sub load_tweaks
684{
685	my ($lib, $abi) = @_;
686
687	my $key;
688	if (exists($model_tweak{$lib}) && $model_tweak{$lib}) {
689		$key = $lib;
690	} else {
691		#
692		# check device/inode record so as to not get tricked by
693		# symlinks.
694		#
695		my ($device, $inode) = (stat($lib))[0,1];
696		if (! defined($device) || ! defined($inode)) {
697			return 0;
698		}
699		$key = "$device/$inode";
700		if (! exists($model_tweak{$key}) || ! $model_tweak{$key}) {
701			return 0;
702		}
703		#
704		# device/inode $key is recorded, so continue along
705		# using it below in the model_tweak lookup.
706		#
707	}
708
709	#
710	# etc line looks like:
711	# MODEL_TWEAK|/usr/lib/libnsl.so.1|sparc,i386|gethostname|public
712	# value looks like:
713	# gethostname|sparc,i386|public
714	#
715
716	my ($case, $abis, $sym, $class, $count);
717
718	$count = 0;
719	foreach $case (split(/,/, $model_tweak{$key})) {
720		($sym, $abis, $class) = split(/\|/, $case);
721		if ($abis eq '*' || ($abis =~ /\b${abi}\b/)) {
722			$model{"$lib|$abi|$sym"} = $class;
723			$count++;
724		}
725	}
726
727	return $count;
728}
729
730#
731# Determine the public/private symbol model for a versioned Solaris
732# library. Returns 0 if no model could be extracted, otherwise returns a
733# string detailing the model loading.
734#
735sub load_model_versioned_lib
736{
737	my ($library, $abi) = @_;
738
739	#
740	# This subroutine runs pvs -dos directly on the Solaris shared
741	# object, and parses data that looks like this:
742	#
743	# % pvs -dos /usr/lib/libsocket.so.1
744	# ...
745	# /usr/lib/libsocket.so.1 -       SUNW_1.1: __xnet_sendmsg;
746	# ...
747	# /usr/lib/libsocket.so.1 -       SISCD_2.3: connect;
748	# ...
749	# /usr/lib/libsocket.so.1 -       SUNWprivate_1.2: getnetmaskbyaddr;
750	#
751	# Note that data types look like:
752	# /usr/lib/libc.so.1 -    SUNWprivate_1.1: __environ_lock (24);
753	#
754	# we discard the size.
755	#
756	# On successful completion 1, is returned and the hash:
757	#
758	#	%model{"$library|$abi|$symbol"} = {public,private}
759	#
760	# is set.
761	#
762
763	# library must be a full path and exist:
764	if (! -f $library) {
765		return 0;
766	}
767
768	my ($rc, $output, $output_syslib);
769
770	#
771	# quote character should never happen in normal use, but if it
772	# did it will foul up the pvs commands below, so we return
773	# early.
774	#
775	if ($library =~ /'/) {
776		return 0;
777	}
778
779	#
780	# Get the entire list of symbols:
781	# note that $library does not contain a single-quote.
782	#
783	c_locale(1);
784	$output = `$cmd_pvs -dos '$library' 2>/dev/null`;
785	$rc = $?;
786	c_locale(0);
787
788	if ($rc != 0 || ($output =~ /^[\s\n]*$/)) {
789		# It is not versioned, so get out.
790		return 0;
791	}
792
793	# Library is versioned with something from this point on.
794
795	my ($line, $libtmp, $j1, $j2, $version, $symbol, $class);
796	my ($count, $public_count, $private_count);
797	my (%versions);
798	my $libbase = basename($library);
799
800	$count = 0;
801	$public_count = 0;
802	$private_count = 0;
803
804	my $is_system_lib = is_system_library($library, $abi);
805
806	my (@defs, $def);
807	if (defined($is_system_lib)) {
808		foreach $def (split(/:/, $is_system_lib)) {
809			next if ($def =~ /^FILE=/);
810			next if ($def =~ /^-$/);
811			push(@defs, $def);
812		}
813		if (@defs == 1) {
814			$is_system_lib = $defs[0];
815		}
816	}
817
818	my (@version_heads, $vers, $default_class);
819	if (defined($is_system_lib) && $is_system_lib ne 'NO_PUBLIC_SYMS') {
820		#
821		# It is a versioned system library.  Extract the public
822		# symbols version head end(s)
823		#
824		if ($is_system_lib =~ /^PUBLIC=(.*)$/) {
825			@version_heads = split(/,/, $1);
826		} else {
827			push(@version_heads, $is_system_lib);
828		}
829
830		#
831		# Rerun pvs again to extract the symbols associated with
832		# the *public* inheritance chain(s).
833		#
834		c_locale(1);
835		foreach $vers (@version_heads) {
836			# something is wrong if $vers has a quote
837			$vers =~ s/'//g;
838
839			# $library has been screened for single quotes earlier.
840			$output_syslib .=
841			    `$cmd_pvs -dos -N '$vers' '$library' 2>/dev/null`;
842		}
843		c_locale(0);
844	}
845
846
847	if (defined($output_syslib) && ($output_syslib !~ /^[\s\n]*$/)) {
848		#
849		# If non-empty there are some public symbols sets.
850		# First, mark everything private:
851		#
852		$output = "DEFAULT_CLASS=private\n" . $output;
853		# then overwrite the public ones:
854		$output .= "DEFAULT_CLASS=public\n"  . $output_syslib;
855	} elsif (defined($is_system_lib) &&
856	    $is_system_lib eq 'NO_PUBLIC_SYMS') {
857		# Everything is private:
858		$output = "DEFAULT_CLASS=private\n" . $output;
859	} else {
860		#
861		# assume public, the override will occur when version
862		# string matches /private/i. This is for 3rd party
863		# libraries.
864		#
865		$output = "DEFAULT_CLASS=public\n" . $output;
866	}
867
868	foreach $line (split(/\n/, $output)) {
869		$line = trim($line);
870		if ($line =~ /^DEFAULT_CLASS=(.*)$/) {
871			$default_class = $1;
872			next;
873		}
874		($libtmp, $j1, $version, $symbol, $j2) = split(/\s+/, $line);
875
876		$symbol  =~ s/;*$//;
877		$version =~ s/:*$//;
878
879		next if ($symbol =~ /^\s*$/);
880		next if ($version eq $libbase);	# see example output above
881
882		$versions{$version}++;
883
884		$class = $default_class;
885
886		if (! $output_syslib && ($version =~ /private/i)) {
887			$class = 'private';
888		}
889
890		if ($class eq 'private') {
891			$private_count++;
892		} else {
893			if ($output_syslib) {
894				# remove the double counting of this version:
895				$versions{$version}--;
896				$private_count--;
897				$count--;
898			}
899			$public_count++;
900		}
901
902		$model{"$library|$abi|$symbol"} = $class;
903		$count++;
904	}
905
906	if (! $count) {
907		return 0;
908	}
909
910	# Construct the info string:
911	$libtmp = "load_model_versioned_lib,$library,$abi:";
912	foreach $version (sort(keys(%versions))) {
913		$libtmp .= "$version\[$versions{$version}\],";
914	}
915	$libtmp .=
916	    "\[${count}symbols=${public_count}public+${private_count}private\]";
917	return $libtmp;
918}
919
920#
921# Returns a non-empty string if the $path_to_library is recognized as a
922# System (i.e. Solaris) ABI library for given abi.  Returns undef
923# otherwise.  The returned string will either be the public symbol version
924# name(s), "NO_PUBLIC_SYMS" if all symbols are private, or "-" if there
925# is no versioning at all.
926#
927sub is_system_library
928{
929	my ($path_to_library, $abi) = @_;
930
931	if (exists($is_system_library_cache{"$path_to_library|$abi"})) {
932		return $is_system_library_cache{"$path_to_library|$abi"};
933	}
934
935	my ($dir, $def, $key);
936
937	my ($device, $inode) = (stat($path_to_library))[0,1];
938	foreach $dir (@lib_index_loaded) {
939
940		$key = "$dir|$path_to_library|$abi";
941		$def = $lib_index_definition{$key};
942		if (defined($device) && defined($inode) && ! defined($def)) {
943			# try inode lookup (chases down unexpected symlinks)
944			$key = "$dir|$device/$inode|$abi";
945			$def = $lib_index_definition{$key};
946		}
947		last if (defined($def));
948	}
949	if (!defined($def) && $path_to_library !~ /'/) {
950		#
951		# we skip the case $path_to_library containing
952		# a single quote, so the cmd argument is protected.
953		#
954		my $tmp = `$cmd_pvs -dn '$path_to_library' 2>/dev/null`;
955		if ($tmp =~ /\b(SUNW[^;]*);/) {
956			$def = $1;
957		}
958	}
959
960	$is_system_library_cache{"$path_to_library|$abi"} = $def;
961
962	return $def;
963}
964
965#
966# Loop over each object item in the working_dir.
967#  - $dir will be each one of these object directories.
968#  - $path_to_object will be the corresponding actual path
969#    to the the binary to be checked.
970# Output will usually be placed down in $dir, e.g. "$dir/check.foo"
971#
972sub perform_misc_checks
973{
974	my ($dir, $path_to_object);
975
976	if (! $misc_check_databases_loaded_ok) {
977		#
978		# The load was attempted in check_objects() There is no
979		# point in continuing if that loading failed.
980		#
981		return;
982	}
983
984	emsg("\n" . gettext(
985	    "performing miscellaneous checks") . " ...\n\n");
986
987	while (defined($dir = next_dir_name())) {
988
989		# Map object output dir to actual path of the object:
990		$path_to_object = dir_name_to_path($dir);
991
992		if (! -f $path_to_object) {
993			exiter(nopathexist($path_to_object, $!));
994		}
995
996		# Check it:
997		misc_check($path_to_object, $dir);
998	}
999}
1000
1001#
1002# Routine to perform the misc. checks on a given binary object.  Records
1003# the findings in object's output directory.
1004#
1005sub misc_check
1006{
1007	my ($path_to_object, $dir) = @_;
1008
1009	# Load the entire dynamic profile for this object:
1010
1011	my (@profile, @profile_short, %direct_syms, $file);
1012	my $tmp;
1013
1014	$file = "$dir/profile.dynamic";
1015
1016	my ($app, $caller, $lib, $base, $sym);
1017	my ($libsymcaller, $cnt, %sawlib);
1018	if (-f $file) {
1019		my $prof_fh = do { local *FH; *FH };
1020		if (! open($prof_fh, "<$file")) {
1021			exiter(nofile($file, $!));
1022		}
1023		$cnt = 0;
1024		while (<$prof_fh>) {
1025			next if (/^\s*#/);
1026			next if (/^\s*$/);
1027			chomp;
1028			($app, $caller, $lib, $sym) = split(/\|/, $_, 4);
1029
1030			# Skip the checking of special symbols:
1031			next if (exists($skip_symbols{$sym}));
1032
1033			push(@profile, "$lib|$sym|$caller");
1034
1035			#
1036			# We collect in @profile_short up to 10
1037			# lib-sym-caller triples where all the libs are
1038			# distinct.  This is used to speed up some
1039			# loops over the profile below: when we catch
1040			# the lib-matching checks early in the loop we
1041			# can exclude those checks from the remainder
1042			# of the loop.  Since a profile may involve
1043			# 1000's of symbols this can be a savings.
1044			#
1045			if ($cnt < 10 && ! exists($sawlib{$lib})) {
1046				push(@profile_short, "$lib|$sym|$caller");
1047				$sawlib{$lib} = 1;
1048				$cnt++;
1049			}
1050		}
1051		close($prof_fh);
1052	}
1053	#
1054	# Misc Check #1:
1055	# Go through dynamic profile looking for scoped local symbols:
1056	#
1057
1058	my (%all_neededs, %lib_not_found);
1059	my ($scoped_list, $scoped_msg);
1060	my ($sc_rel, $sc_lib, $sc_sym, $sc_val);
1061
1062	%all_neededs = all_ldd_neededs($path_to_object);
1063	my ($key, $key_trim);
1064	foreach $key (keys(%all_neededs)) {
1065		$key_trim = basename($key);
1066		$all_neededs{$key_trim} = $all_neededs{$key};
1067		if ($all_neededs{$key} =~ /file not found/) {
1068			# %lib_not_found will be used below in check #2
1069			$lib_not_found{$key}++;
1070			$lib_not_found{$key_trim}++;
1071		}
1072	}
1073
1074	# We will need the abi of the object:
1075	my $abi;
1076	($abi) = bin_type($path_to_object);
1077	if ($abi eq '' || ($abi =~ /^(unknown|any)$/)) {
1078		if ($uname_p =~ /sparc/i) {
1079			$abi = 'sparc';
1080		} else {
1081			$abi = 'i386';
1082		}
1083	}
1084
1085	foreach $libsymcaller (@profile) {
1086		next unless ($libsymcaller =~ /\*DIRECT\*$/);
1087
1088		($lib, $sym, $caller) = split(/\|/, $libsymcaller, 3);
1089
1090		#
1091		# Record direct symbols to improve our %wskip list used
1092		# to speed up loops over symbols.
1093		#
1094		$direct_syms{$sym} = 1;
1095		next unless (exists($scoped_symbol_all{$sym}));
1096
1097		$base = basename($lib);
1098
1099		#
1100		# We only worry if a scoped call is a direct one.  This
1101		# assumes the user's support shared objects are also
1102		# checked by appcert.
1103		#
1104
1105		if (exists($scoped_symbol{"$lib|$sym"}) ||
1106		    exists($scoped_symbol{"$base|$sym"})) {
1107			#
1108			# This one is for checking on releases BEFORE
1109			# the scoping actually occurred.
1110			#
1111			$scoped_msg  .= "$base:$sym ";
1112			$scoped_list .= "$path_to_object|$caller|$lib|$sym\n";
1113
1114		} elsif ($lib eq '*UNBOUND*' &&
1115		    exists($scoped_symbol_all{$sym})) {
1116			#
1117			# This one is for checking on releases AFTER the
1118			# scoping.
1119			#
1120			# Assume these type of unbounds are deprecated
1121			# if found in scoped_symbol_all. Double check it
1122			# is in the all-needed-libs though:
1123			#
1124
1125			if (defined($sc_sym) &&
1126			    exists($model{"$LIBC|$abi|$sc_sym"})) {
1127				next;
1128			}
1129
1130			foreach $sc_val (split(/,/, $scoped_symbol_all{$sym})) {
1131				($sc_rel, $sc_lib, $sc_sym) =
1132				    split(/\|/, $sc_val);
1133
1134				#
1135				# The general scoping that occurred for
1136				# ld.so.1 makes the current heuristic
1137				# somewhat less accurate. Unboundedness
1138				# from other means has too good a chance
1139				# of overlapping with the ld.so.1
1140				# scoping. Note that the app likely does
1141				# NOT have ld.so.1 in its neededs, but
1142				# we still skip this case.
1143				#
1144
1145				next if ($sc_lib eq 'ld.so.1');
1146
1147				if ($all_neededs{$sc_lib}) {
1148					# note that $lib is '*UNBOUND*'
1149					$scoped_msg  .= "<unbound>:$sym ";
1150					$scoped_list .=
1151					"$path_to_object|$caller|$lib|$sym\n";
1152				}
1153			}
1154		}
1155	}
1156
1157	if (defined($scoped_msg)) {
1158		my $problems = "$dir/check.problems";
1159
1160		# problems will be appended to the file:
1161		my $problems_fh = do { local *FH; *FH };
1162		if (! open($problems_fh, ">>$problems")) {
1163			exiter(nofile($problems, $!));
1164		}
1165		print $problems_fh
1166		    "MISC: REMOVED_SCOPED_SYMBOLS: $scoped_msg\n";
1167		close($problems_fh);
1168
1169		$problems = "$dir/check.demoted_symbols";
1170
1171		# problems will be appended to the file:
1172		my $check_fh = do { local *FH; *FH };
1173		if (! open($check_fh, ">>$problems")) {
1174			exiter(nofile($problems, $!));
1175		}
1176		print $check_fh $scoped_list;
1177		close($check_fh);
1178	}
1179
1180	#
1181	# Misc Check #2
1182	# Go through dynamic profile looking for special warnings.
1183	#
1184
1185	my (%warnings, %wskip);
1186	my (%lib_star, %sym_star, %caller_star);
1187	my ($tag, $tag0, $sub, $res);
1188
1189	while (($tag, $sub) = each(%warnings_match)) {
1190		next if (! $sub);
1191
1192		$res = &{$sub}($path_to_object);
1193		$warnings{$tag} = 1 if ($res);
1194	}
1195
1196	my $warnings_bind_has_non_direct = 0;
1197
1198	while (($tag0, $tmp) =  each(%warnings_bind)) {
1199		($lib, $sym, $caller) = split(/\|/, $tmp, 3);
1200		$lib_star{$tag0}	= 1 if ($lib eq '*');
1201		$sym_star{$tag0}	= 1 if ($sym eq '*');
1202		$caller_star{$tag0}	= 1 if ($caller eq '*');
1203		if ($lib ne '*' && $lib !~ m,/, && ! $all_neededs{$lib}) {
1204			# it can never match:
1205			$wskip{$tag0} = 1;
1206		}
1207		if ($caller ne '*DIRECT*') {
1208			# this will be used to speed up the *DIRECT* only case:
1209			$warnings_bind_has_non_direct = 1;
1210		} elsif ($sym ne '*' && ! $direct_syms{$sym}) {
1211			# it can never match:
1212			$wskip{$tag0} = 1;
1213		}
1214	}
1215
1216	foreach $lib (keys(%lib_not_found)) {
1217		#
1218		# add a placeholder symbol in %profile to indicate
1219		# $lib is on dtneeded, but wasn't found. This will
1220		# match a $sym = '*' warnings_bind misc check:
1221		#
1222		push(@profile,
1223		    "$lib|__ldd_indicated_file_not_found__|*DIRECT*");
1224	}
1225
1226	my ($l_t, $s_t, $c_t, $match_t);
1227
1228	my (@tag_list, @tag_list2, $new_tags);
1229	#
1230	# create a list of tags excluding the ones we know will be
1231	# skipped in the $libsymcaller loop below.
1232	#
1233	foreach $tag0 (keys(%warnings_bind)) {
1234		next if ($wskip{$tag0});
1235		push(@tag_list, $tag0);
1236	}
1237
1238	#
1239	# we loop over @profile_short first, these will give us up to
1240	# 10 different libraries early to help us shrink @tag_list
1241	# as we go through the profile.
1242	#
1243	foreach $libsymcaller (@profile_short, @profile) {
1244		@tag_list = @tag_list2 if ($new_tags);
1245		last if (! @tag_list);
1246
1247		($lib, $sym, $caller) = split(/\|/, $libsymcaller, 3);
1248
1249		if (! $warnings_bind_has_non_direct && $caller ne '*DIRECT*') {
1250			next;
1251		}
1252
1253		$base = basename($lib);
1254		$new_tags = 0;
1255
1256		foreach $tag0 (@tag_list) {
1257
1258			# try to get out early:
1259			next if ($wskip{$tag0});
1260
1261			($tag, $tmp) = split(/\|/, $tag0, 2);
1262			# try to get out early:
1263			next if ($warnings{$tag});
1264
1265			$match_t = $warnings_bind{$tag0};
1266
1267			$l_t = $lib;
1268			$s_t = $sym;
1269			$c_t = $caller;
1270
1271			$l_t = '*' if ($lib_star{$tag0});
1272			$s_t = '*' if ($sym_star{$tag0});
1273			$c_t = '*' if ($caller_star{$tag0});
1274
1275			if ("$l_t|$s_t|$c_t" eq $match_t ||
1276			    "$base|$s_t|$c_t" eq $match_t) {
1277				$warnings{$tag} = 1;
1278				$wskip{$tag0} = 1;
1279
1280				# shorten tag list:
1281				my (@t, $tg, $tg2, $tp);
1282				foreach $tg (@tag_list) {
1283					next if ($tg eq $tag0);
1284					($tg2, $tp) = split(/\|/, $tg, 2);
1285					next if ($tg2 eq $tag);
1286					push(@t, $tg);
1287				}
1288				@tag_list2 = @t;
1289				$new_tags = 1;
1290			}
1291		}
1292	}
1293
1294	if (%warnings) {
1295		my $problems = "$dir/check.problems";
1296
1297		# append problems to the file:
1298		my $problems_fh = do { local *FH; *FH };
1299		if (! open($problems_fh, ">>$problems")) {
1300			exiter(nofile($problems, $!));
1301		}
1302
1303		my $tag;
1304		foreach $tag (keys(%warnings)) {
1305			print $problems_fh "MISC: WARNING: $tag\n";
1306		}
1307		close($problems_fh);
1308	}
1309}
1310