xref: /illumos-gate/usr/src/tools/scripts/check_rtime.pl (revision a92282e44f968185a6bba094d1e5fece2da819cf)
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 (the "License").
7# You may not use this file except in compliance with the License.
8#
9# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10# or http://www.opensolaris.org/os/licensing.
11# See the License for the specific language governing permissions
12# and limitations under the License.
13#
14# When distributing Covered Code, include this CDDL HEADER in each
15# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16# If applicable, add the following below this CDDL HEADER, with the
17# fields enclosed by brackets "[]" replaced with your own identifying
18# information: Portions Copyright [yyyy] [name of copyright owner]
19#
20# CDDL HEADER END
21#
22
23#
24# Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
25# Copyright 2020 Oxide Computer Company
26#
27
28#
29# Check ELF information.
30#
31# This script descends a directory hierarchy inspecting ELF dynamic executables
32# and shared objects.  The general theme is to verify that common Makefile rules
33# have been used to build these objects.  Typical failures occur when Makefile
34# rules are re-invented rather than being inherited from "cmd/lib" Makefiles.
35#
36# As always, a number of components don't follow the rules, and these are
37# excluded to reduce this scripts output.
38#
39# By default any file that has conditions that should be reported is first
40# listed and then each condition follows.  The -o (one-line) option produces a
41# more terse output which is better for sorting/diffing with "nightly".
42#
43# NOTE: missing dependencies, symbols or versions are reported by running the
44# file through ldd(1).  As objects within a proto area are built to exist in a
45# base system, standard use of ldd(1) will bind any objects to dependencies
46# that exist in the base system.  It is frequently the case that newer objects
47# exist in the proto area that are required to satisfy other objects
48# dependencies, and without using these newer objects an ldd(1) will produce
49# misleading error messages.  To compensate for this, the -D/-d options, or the
50# existence of the CODEMSG_WS/ROOT environment variables, cause the creation of
51# alternative dependency mappings via crle(1) configuration files that establish
52# any proto shared objects as alternatives to their base system location.  Thus
53# ldd(1) can be executed against these configuration files so that objects in a
54# proto area bind to their dependencies in the same proto area.
55
56
57# Define all global variables (required for strict)
58use vars  qw($Prog $Env $Ena64 $Tmpdir);
59use vars  qw($LddNoU $Conf32 $Conf64);
60use vars  qw(%opt);
61use vars  qw($ErrFH $ErrTtl $InfoFH $InfoTtl $OutCnt1 $OutCnt2);
62
63# An exception file is used to specify regular expressions to match
64# objects. These directives specify special attributes of the object.
65# The regular expressions are read from the file and compiled into the
66# regular expression variables.
67#
68# The name of each regular expression variable is of the form
69#
70#	$EXRE_xxx
71#
72# where xxx is the name of the exception in lower case. For example,
73# the regular expression variable for EXEC_STACK is $EXRE_exec_stack.
74#
75# onbld_elfmod::LoadExceptionsToEXRE() depends on this naming convention
76# to initialize the regular expression variables, and to detect invalid
77# exception names.
78#
79# If a given exception is not used in the exception file, its regular
80# expression variable will be undefined. Users of these variables must
81# test the variable with defined() prior to use:
82#
83#	defined($EXRE_exec_stack) && ($foo =~ $EXRE_exec_stack)
84#
85# or if the test is to make sure the item is not specified:
86#
87#	!defined($EXRE_exec_stack) || ($foo !~ $EXRE_exec_stack)
88#
89# ----
90#
91# The exceptions are:
92#
93#   EXEC_DATA
94#	Objects that are not required to have non-executable writable
95#	data segments.
96#
97#   EXEC_STACK
98#	Objects that are not required to have a non-executable stack
99#
100#   FORBIDDEN_DEP
101#	Objects allowed to link to 'forbidden' objects
102#
103#   FORBIDDEN
104#	Objects to which nobody not excepted with FORBIDDEN_DEP may link
105#
106#   NOCRLEALT
107#	Objects that should be skipped by AltObjectConfig() when building
108#	the crle script that maps objects to the proto area.
109#
110#    NODIRECT
111#	Objects that are not required to use direct bindings
112#
113#    NOSYMSORT
114#	Objects we should not check for duplicate addresses in
115#	the symbol sort sections.
116#
117#    OLDDEP
118#	Objects that are no longer needed because their functionalty
119#	has migrated elsewhere. These are usually pure filters that
120#	point at libc.
121#
122#    SKIP
123#	Files and directories that should be excluded from analysis.
124#
125#    STAB
126#	Objects that are allowed to contain stab debugging sections
127#
128#    TEXTREL
129#	Object for which relocations are allowed to the text segment
130#
131#    UNDEF_REF
132#	Objects that are allowed undefined references
133#
134#    UNREF_OBJ
135#	"unreferenced object=" ldd(1) diagnostics.
136#
137#    UNUSED_DEPS
138#	Objects that are allowed to have unused dependencies
139#
140#    UNUSED_OBJ
141#	Objects that are allowed to be unused dependencies
142#
143#    UNUSED_RPATH
144#	Objects with unused runpaths
145#
146
147use vars  qw($EXRE_exec_data $EXRE_exec_stack $EXRE_nocrlealt);
148use vars  qw($EXRE_nodirect $EXRE_nosymsort $EXRE_forbidden_dep $EXRE_forbidden);
149use vars  qw($EXRE_olddep $EXRE_skip $EXRE_stab $EXRE_textrel $EXRE_undef_ref);
150use vars  qw($EXRE_unref_obj $EXRE_unused_deps $EXRE_unused_obj);
151use vars  qw($EXRE_unused_rpath $EXRE_no_comment);
152
153use strict;
154use Getopt::Std;
155use File::Basename;
156
157
158# Reliably compare two OS revisions.  Arguments are <ver1> <op> <ver2>.
159# <op> is the string form of a normal numeric comparison operator.
160sub cmp_os_ver {
161	my @ver1 = split(/\./, $_[0]);
162	my $op = $_[1];
163	my @ver2 = split(/\./, $_[2]);
164
165	push @ver2, ("0") x $#ver1 - $#ver2;
166	push @ver1, ("0") x $#ver2 - $#ver1;
167
168	my $diff = 0;
169	while (@ver1 || @ver2) {
170		if (($diff = shift(@ver1) - shift(@ver2)) != 0) {
171			last;
172		}
173	}
174	return (eval "$diff $op 0" ? 1 : 0);
175}
176
177## ProcFile(FullPath, RelPath, File, Class, Type, Verdef)
178#
179# Determine whether this a ELF dynamic object and if so investigate its runtime
180# attributes.
181#
182sub ProcFile {
183	my($FullPath, $RelPath, $Class, $Type, $Verdef) = @_;
184	my(@Elf, @Ldd, $Dyn, $Sym, $Stack);
185	my($Sun, $Relsz, $Pltsz, $Tex, $Stab, $Strip, $Lddopt, $SymSort);
186	my($Val, $Header, $IsX86, $RWX, $UnDep);
187	my($HasDirectBinding);
188
189	# Only look at executables and sharable objects
190	return if ($Type ne 'EXEC') && ($Type ne 'DYN');
191
192	# Ignore symbolic links
193	return if -l $FullPath;
194
195	# Is this an object or directory hierarchy we don't care about?
196	return if (defined($EXRE_skip) && ($RelPath =~ $EXRE_skip));
197
198	# Bail if we can't stat the file. Otherwise, note if it is SUID/SGID.
199	return if !stat($FullPath);
200	my $Secure = (-u _ || -g _) ? 1 : 0;
201
202	# Reset output message counts for new input file
203	$$ErrTtl = $$InfoTtl = 0;
204
205	@Ldd = 0;
206
207	# Determine whether we have access to inspect the file.
208	if (!(-r $FullPath)) {
209		onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
210		    "unable to inspect file: permission denied");
211		return;
212	}
213
214	# Determine whether we have a executable (static or dynamic) or a
215	# shared object.
216	@Elf = split(/\n/, `elfdump -epdcy $FullPath 2>&1`);
217
218	$Dyn = $Stack = $IsX86 = $RWX = 0;
219	$Header = 'None';
220	foreach my $Line (@Elf) {
221		# If we have an invalid file type (which we can tell from the
222		# first line), or we're processing an archive, bail.
223		if ($Header eq 'None') {
224			if (($Line =~ /invalid file/) ||
225			    ($Line =~ /\Q$FullPath\E(.*):/)) {
226				return;
227			}
228		}
229
230		if ($Line =~ /^ELF Header/) {
231			$Header = 'Ehdr';
232			next;
233		}
234
235		if ($Line =~ /^Program Header/) {
236			$Header = 'Phdr';
237			$RWX = 0;
238			next;
239		}
240
241		if ($Line =~ /^Dynamic Section/) {
242			# A dynamic section indicates we're a dynamic object
243			# (this makes sure we don't check static executables).
244			$Dyn = 1;
245			next;
246		}
247
248		if (($Header eq 'Ehdr') && ($Line =~ /e_machine:/)) {
249			# If it's a X86 object, we need to enforce RW- data.
250			$IsX86 = 1 if $Line =~ /(EM_AMD64|EM_386)/;
251			next;
252		}
253
254		if (($Header eq 'Phdr') &&
255		    ($Line =~ /\[ PF_X\s+PF_W\s+PF_R \]/)) {
256			# RWX segment seen.
257			$RWX = 1;
258			next;
259		}
260
261		if (($Header eq 'Phdr') &&
262		    ($Line =~ /\[ PT_LOAD \]/ && $RWX && $IsX86)) {
263			# Seen an RWX PT_LOAD segment.
264			if (!defined($EXRE_exec_data) ||
265			    ($RelPath !~ $EXRE_exec_data)) {
266				onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
267				    "application requires non-executable " .
268				    "data\t<no -Mmapfile_noexdata?>");
269			}
270			next;
271		}
272
273		if (($Header eq 'Phdr') && ($Line =~ /\[ PT_SUNWSTACK \]/)) {
274			# This object defines a non-executable stack.
275			$Stack = 1;
276			next;
277		}
278	}
279
280	# Determine whether this ELF executable or shared object has a
281	# conforming mcs(1) comment section.  If the correct $(POST_PROCESS)
282	# macros are used, only a 3 or 4 line .comment section should exist
283	# containing one or two "@(#)illumos" identifying comments (one comment
284	# for a non-debug build, and two for a debug build). The results of
285	# the following split should be three or four lines, the last empty
286	# line being discarded by the split.
287	if ($opt{m} &&
288	    (!defined($EXRE_no_comment) || ($RelPath !~ $EXRE_no_comment))) {
289		my(@Mcs, $Con, $Dev);
290
291		@Mcs = split(/\n/, `mcs -p $FullPath 2>&1`);
292
293		$Con = $Dev = $Val = 0;
294		foreach my $Line (@Mcs) {
295			$Val++;
296
297			if (($Val == 3) && ($Line !~ /^@\(#\)illumos/)) {
298				$Con = 1;
299				last;
300			}
301			if (($Val == 4) && ($Line =~ /^@\(#\)illumos/)) {
302				$Dev = 1;
303				next;
304			}
305			if (($Dev == 0) && ($Val == 4)) {
306				$Con = 1;
307				last;
308			}
309			if (($Dev == 1) && ($Val == 5)) {
310				$Con = 1;
311				last;
312			}
313		}
314		if ($opt{m} && ($Con == 1)) {
315			onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
316		    "non-conforming mcs(1) comment\t<no \$(POST_PROCESS)?>");
317		}
318	}
319
320	# Applications should contain a non-executable stack definition.
321	if (($Type eq 'EXEC') && ($Stack == 0) &&
322	    (!defined($EXRE_exec_stack) || ($RelPath !~ $EXRE_exec_stack))) {
323		onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
324		    "non-executable stack required\t<no -Mmapfile_noexstk?>");
325	}
326
327	# Having caught any static executables in the mcs(1) check and non-
328	# executable stack definition check, continue with dynamic objects
329	# from now on.
330	if ($Dyn eq 0) {
331		return;
332	}
333
334	# Use ldd unless its a 64-bit object and we lack the hardware.
335	if (($Class == 32) || $Ena64) {
336		my $LDDFullPath = $FullPath;
337
338		if ($Secure) {
339			# The execution of a secure application over an nfs file
340			# system mounted nosuid will result in warning messages
341			# being sent to /var/adm/messages.  As this type of
342			# environment can occur with root builds, move the file
343			# being investigated to a safe place first.  In addition
344			# remove its secure permission so that it can be
345			# influenced by any alternative dependency mappings.
346
347			my $File = $RelPath;
348			$File =~ s!^.*/!!;      # basename
349
350			my($TmpPath) = "$Tmpdir/$File";
351
352			system('cp', $LDDFullPath, $TmpPath);
353			chmod 0777, $TmpPath;
354			$LDDFullPath = $TmpPath;
355		}
356
357		# Use ldd(1) to determine the objects relocatability and use.
358		# By default look for all unreferenced dependencies.  However,
359		# some objects have legitimate dependencies that they do not
360		# reference.
361		if ($LddNoU) {
362			$Lddopt = "-ru";
363		} else {
364			$Lddopt = "-rU";
365		}
366		@Ldd = split(/\n/, `ldd $Lddopt $Env $LDDFullPath 2>&1`);
367		if ($Secure) {
368			unlink $LDDFullPath;
369		}
370	}
371
372	$Val = 0;
373	$Sym = 5;
374	$UnDep = 1;
375
376	foreach my $Line (@Ldd) {
377
378		if ($Val == 0) {
379			$Val = 1;
380			# Make sure ldd(1) worked.  One possible failure is that
381			# this is an old ldd(1) prior to -e addition (4390308).
382			if ($Line =~ /usage:/) {
383				$Line =~ s/$/\t<old ldd(1)?>/;
384				onbld_elfmod::OutMsg($ErrFH, $ErrTtl,
385				    $RelPath, $Line);
386				last;
387			} elsif ($Line =~ /execution failed/) {
388				onbld_elfmod::OutMsg($ErrFH, $ErrTtl,
389				    $RelPath, $Line);
390				last;
391			}
392
393			# It's possible this binary can't be executed, ie. we've
394			# found a sparc binary while running on an intel system,
395			# or a sparcv9 binary on a sparcv7/8 system.
396			if ($Line =~ /wrong class/) {
397				onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
398				    "has wrong class or data encoding");
399				next;
400			}
401
402			# Historically, ldd(1) likes executable objects to have
403			# their execute bit set.
404			if ($Line =~ /not executable/) {
405				onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
406				    "is not executable");
407				next;
408			}
409		}
410
411		# Look for "file" or "versions" that aren't found.  Note that
412		# these lines will occur before we find any symbol referencing
413		# errors.
414		if (($Sym == 5) && ($Line =~ /not found\)/)) {
415			if ($Line =~ /file not found\)/) {
416				$Line =~ s/$/\t<no -zdefs?>/;
417			}
418			onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath, $Line);
419			next;
420		}
421		# Look for relocations whose symbols can't be found.  Note, we
422		# only print out the first 5 relocations for any file as this
423		# output can be excessive.
424		if ($Sym && ($Line =~ /symbol not found/)) {
425			# Determine if this file is allowed undefined
426			# references.
427			if (($Sym == 5) && defined($EXRE_undef_ref) &&
428			    ($RelPath =~ $EXRE_undef_ref)) {
429				$Sym = 0;
430				next;
431			}
432			if ($Sym-- == 1) {
433				onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
434				    "continued ...") if !$opt{o};
435				next;
436			}
437			# Just print the symbol name.
438			$Line =~ s/$/\t<no -zdefs?>/;
439			onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath, $Line);
440			next;
441		}
442		# Look for any unused search paths.
443		if ($Line =~ /unused search path=/) {
444			next if defined($EXRE_unused_rpath) &&
445			    ($Line =~ $EXRE_unused_rpath);
446
447			if ($Secure) {
448				$Line =~ s!$Tmpdir/!!;
449			}
450			$Line =~ s/^[ \t]*(.*)/\t$1\t<remove search path?>/;
451			onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath, $Line);
452			next;
453		}
454
455		# Look for unreferenced dependencies.  Note, if any unreferenced
456		# objects are ignored, then set $UnDep so as to suppress any
457		# associated unused-object messages.
458		if ($Line =~ /unreferenced object=/) {
459			if (defined($EXRE_unref_obj) &&
460			    ($Line =~ $EXRE_unref_obj)) {
461				$UnDep = 0;
462				next;
463			}
464			if ($Secure) {
465				$Line =~ s!$Tmpdir/!!;
466			}
467			$Line =~ s/^[ \t]*(.*)/$1\t<remove lib or -zignore?>/;
468			onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath, $Line);
469			next;
470		}
471		# Look for any unused dependencies.
472		if ($UnDep && ($Line =~ /unused/)) {
473			# Skip if object is allowed to have unused dependencies
474			next if defined($EXRE_unused_deps) &&
475			    ($RelPath =~ $EXRE_unused_deps);
476
477			# Skip if dependency is always allowed to be unused
478			next if defined($EXRE_unused_obj) &&
479			    ($Line =~ $EXRE_unused_obj);
480
481			$Line =~ s!$Tmpdir/!! if $Secure;
482			$Line =~ s/^[ \t]*(.*)/$1\t<remove lib or -zignore?>/;
483			onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath, $Line);
484			next;
485		}
486	}
487
488	# Reuse the elfdump(1) data to investigate additional dynamic linking
489	# information.
490
491	$Sun = $Relsz = $Pltsz = $Dyn = $Stab = $SymSort = 0;
492	$Tex = $Strip = 1;
493	$HasDirectBinding = 0;
494
495	$Header = 'None';
496ELF:	foreach my $Line (@Elf) {
497		# We're only interested in the section headers and the dynamic
498		# section.
499		if ($Line =~ /^Section Header/) {
500			$Header = 'Shdr';
501
502			if (($Sun == 0) && ($Line =~ /\.SUNW_reloc/)) {
503				# This object has a combined relocation section.
504				$Sun = 1;
505
506			} elsif (($Stab == 0) && ($Line =~ /\.stab/)) {
507				# This object contain .stabs sections
508				$Stab = 1;
509			} elsif (($SymSort == 0) &&
510				 ($Line =~ /\.SUNW_dyn(sym)|(tls)sort/)) {
511				# This object contains a symbol sort section
512				$SymSort = 1;
513			}
514
515			if (($Strip == 1) && ($Line =~ /\.symtab/)) {
516				# This object contains a complete symbol table.
517				$Strip = 0;
518			}
519			next;
520
521		} elsif ($Line =~ /^Dynamic Section/) {
522			$Header = 'Dyn';
523			next;
524		} elsif ($Line =~ /^Syminfo Section/) {
525			$Header = 'Syminfo';
526			next;
527		} elsif (($Header ne 'Dyn') && ($Header ne 'Syminfo')) {
528			next;
529		}
530
531		# Look into the Syminfo section.
532		# Does this object have at least one Directly Bound symbol?
533		if (($Header eq 'Syminfo')) {
534			my(@Symword);
535
536			if ($HasDirectBinding == 1) {
537				next;
538			}
539
540			@Symword = split(' ', $Line);
541
542			if (!defined($Symword[1])) {
543				next;
544			}
545			if ($Symword[1] =~ /B/) {
546				$HasDirectBinding = 1;
547			}
548			next;
549		}
550
551		# Does this object contain text relocations.
552		if ($Tex && ($Line =~ /TEXTREL/)) {
553			# Determine if this file is allowed text relocations.
554			if (defined($EXRE_textrel) &&
555			    ($RelPath =~ $EXRE_textrel)) {
556				$Tex = 0;
557				next ELF;
558			}
559			onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
560			    "TEXTREL .dynamic tag\t\t\t<no -Kpic?>");
561			$Tex = 0;
562			next;
563		}
564
565		# Does this file have any relocation sections (there are a few
566		# psr libraries with no relocations at all, thus a .SUNW_reloc
567		# section won't exist either).
568		if (($Relsz == 0) && ($Line =~ / RELA?SZ/)) {
569			$Relsz = hex((split(' ', $Line))[2]);
570			next;
571		}
572
573		# Does this file have any plt relocations.  If the plt size is
574		# equivalent to the total relocation size then we don't have
575		# any relocations suitable for combining into a .SUNW_reloc
576		# section.
577		if (($Pltsz == 0) && ($Line =~ / PLTRELSZ/)) {
578			$Pltsz = hex((split(' ', $Line))[2]);
579			next;
580		}
581
582		# Does this object have any dependencies.
583		if ($Line =~ /NEEDED/) {
584			my($Need) = (split(' ', $Line))[3];
585
586			if (defined($EXRE_olddep) && ($Need =~ $EXRE_olddep)) {
587				# Catch any old (unnecessary) dependencies.
588				onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
589				    "NEEDED=$Need\t<dependency no " .
590				    "longer necessary>");
591			} elsif ((defined($EXRE_forbidden) &&
592                                  ($Need =~ $EXRE_forbidden)) &&
593                                 (!defined($EXRE_forbidden_dep) ||
594                                  ($FullPath !~ $EXRE_forbidden_dep))) {
595				onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
596				    "NEEDED=$Need\t<forbidden dependency, " .
597				    "missing -nodefaultlibs?>");
598			} elsif ($opt{i}) {
599				# Under the -i (information) option print out
600				# any useful dynamic entries.
601				onbld_elfmod::OutMsg($InfoFH, $InfoTtl, $RelPath,
602				    "NEEDED=$Need");
603                	}
604			next;
605		}
606
607		# Is this object built with -B direct flag on?
608		if ($Line =~ / DIRECT /) {
609			$HasDirectBinding = 1;
610		}
611
612		# Does this object specify a runpath.
613		if ($opt{i} && ($Line =~ /RPATH/)) {
614			my($Rpath) = (split(' ', $Line))[3];
615			onbld_elfmod::OutMsg($InfoFH, $InfoTtl,
616			    $RelPath, "RPATH=$Rpath");
617			next;
618		}
619	}
620
621	# A shared object, that contains non-plt relocations, should have a
622	# combined relocation section indicating it was built with -z combreloc.
623	if (($Type eq 'DYN') && $Relsz && ($Relsz != $Pltsz) && ($Sun == 0)) {
624		onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
625		    ".SUNW_reloc section missing\t\t<no -zcombreloc?>");
626	}
627
628	# No objects released to a customer should have any .stabs sections
629	# remaining, they should be stripped.
630	if ($opt{s} && $Stab) {
631		goto DONESTAB if defined($EXRE_stab) && ($RelPath =~ $EXRE_stab);
632
633		onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
634		    "debugging sections should be deleted\t<no strip -x?>");
635	}
636
637	# Identify an object that is not built with either -B direct or
638	# -z direct.
639	goto DONESTAB
640	    if (defined($EXRE_nodirect) && ($RelPath =~ $EXRE_nodirect));
641
642	if ($Relsz && ($HasDirectBinding == 0)) {
643		onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
644		 "object has no direct bindings\t<no -B direct or -z direct?>");
645	}
646
647DONESTAB:
648
649	# All objects should have a full symbol table to provide complete
650	# debugging stack traces.
651	onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
652	    "symbol table should not be stripped\t<remove -s?>") if $Strip;
653
654	# If there are symbol sort sections in this object, report on
655	# any that have duplicate addresses.
656	ProcSymSort($FullPath, $RelPath) if $SymSort;
657
658	# If -v was specified, and the object has a version definition
659	# section, generate output showing each public symbol and the
660	# version it belongs to.
661	ProcVerdef($FullPath, $RelPath)
662	    if ($Verdef eq 'VERDEF') && $opt{v};
663}
664
665
666## ProcSymSortOutMsg(RelPath, secname, addr, names...)
667#
668# Call onbld_elfmod::OutMsg for a duplicate address error in a symbol sort
669# section
670#
671sub ProcSymSortOutMsg {
672	my($RelPath, $secname, $addr, @names) = @_;
673
674	onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
675	    "$secname: duplicate $addr: ". join(', ', @names));
676}
677
678
679## ProcSymSort(FullPath, RelPath)
680#
681# Examine the symbol sort sections for the given object and report
682# on any duplicate addresses found.  Ideally, mapfile directives
683# should be used when building objects that have multiple symbols
684# with the same address so that only one of them appears in the sort
685# section. This saves space, reduces user confusion, and ensures that
686# libproc and debuggers always display public names instead of symbols
687# that are merely implementation details.
688#
689sub ProcSymSort {
690
691	my($FullPath, $RelPath) = @_;
692
693	# If this object is exempt from checking, return quietly
694	return if defined($EXRE_nosymsort) && ($FullPath =~ $EXRE_nosymsort);
695
696
697	open(SORT, "elfdump -S $FullPath|") ||
698	    die "$Prog: Unable to execute elfdump (symbol sort sections)\n";
699
700	my $line;
701	my $last_addr;
702	my @dups = ();
703	my $secname;
704	while ($line = <SORT>) {
705		chomp $line;
706
707		next if ($line eq '');
708
709		# If this is a header line, pick up the section name
710		if ($line =~ /^Symbol Sort Section:\s+([^\s]+)\s+/) {
711			$secname = $1;
712
713			# Every new section is followed by a column header line
714			$line = <SORT>;		# Toss header line
715
716			# Flush anything left from previous section
717			ProcSymSortOutMsg($RelPath, $secname, $last_addr, @dups)
718			    if (scalar(@dups) > 1);
719
720			# Reset variables for new sort section
721			$last_addr = '';
722			@dups = ();
723
724			next;
725		}
726
727		# Process symbol line
728		my @fields = split /\s+/, $line;
729		my $new_addr = $fields[2];
730		my $new_type = $fields[8];
731		my $new_name = $fields[9];
732
733		if ($new_type eq 'UNDEF') {
734			onbld_elfmod::OutMsg($ErrFH, $ErrTtl, $RelPath,
735			    "$secname: unexpected UNDEF symbol " .
736			    "(link-editor error): $new_name");
737			next;
738		}
739
740		if ($new_addr eq $last_addr) {
741			push @dups, $new_name;
742		} else {
743			ProcSymSortOutMsg($RelPath, $secname,
744			    $last_addr, @dups) if (scalar(@dups) > 1);
745			@dups = ( $new_name );
746			$last_addr = $new_addr;
747		}
748	}
749
750	ProcSymSortOutMsg($RelPath, $secname, $last_addr, @dups)
751		if (scalar(@dups) > 1);
752
753	close SORT;
754}
755
756
757## ProcVerdef(FullPath, RelPath)
758#
759# Examine the version definition section for the given object and report
760# each public symbol along with the version it belongs to.
761#
762sub ProcVerdef {
763
764	my($FullPath, $RelPath) = @_;
765	my $line;
766	my $cur_ver = '';
767	my $tab = $opt{o} ? '' : "\t";
768
769	# pvs -dov provides information about the versioning hierarchy
770	# in the file. Lines are of the format:
771	#	path - version[XXX];
772	# where [XXX] indicates optional information, such as flags
773	# or inherited versions.
774	#
775	# Private versions are allowed to change freely, so ignore them.
776	open(PVS, "pvs -dov $FullPath|") ||
777	    die "$Prog: Unable to execute pvs (version definition section)\n";
778
779	while ($line = <PVS>) {
780		chomp $line;
781
782		if ($line =~ /^[^\s]+\s+-\s+([^;]+)/) {
783			my $ver = $1;
784
785			next if $ver =~ /private/i;
786			onbld_elfmod::OutMsg($InfoFH, $InfoTtl, $RelPath,
787			    "${tab}VERDEF=$ver");
788		}
789	}
790	close PVS;
791
792	# pvs -dos lists the symbols assigned to each version definition.
793	# Lines are of the format:
794	#	path - version: symbol;
795	#	path - version: symbol (size);
796	# where the (size) is added to data items, but not for functions.
797	# We strip off the size, if present.
798
799	open(PVS, "pvs -dos $FullPath|") ||
800	    die "$Prog: Unable to execute pvs (version definition section)\n";
801	while ($line = <PVS>) {
802		chomp $line;
803		if ($line =~ /^[^\s]+\s+-\s+([^:]+):\s*([^\s;]+)/) {
804		    my $ver = $1;
805		    my $sym = $2;
806
807		    next if $ver =~ /private/i;
808
809		    if ($opt{o}) {
810			onbld_elfmod::OutMsg($InfoFH, $InfoTtl, $RelPath,
811			    "VERSION=$ver, SYMBOL=$sym");
812		    } else {
813			if ($cur_ver ne $ver) {
814			    onbld_elfmod::OutMsg($InfoFH, $InfoTtl,
815			        $RelPath, "VERSION=$ver");
816			    $cur_ver = $ver;
817			}
818			onbld_elfmod::OutMsg($InfoFH, $InfoTtl,
819			    $RelPath, "SYMBOL=$sym");
820		    }
821		}
822	}
823
824	close PVS;
825}
826
827
828## OpenFindElf(file, FileHandleRef, LineNumRef)
829#
830# Open file in 'find_elf -r' format, and return the value of
831# the opening PREFIX line.
832#
833# entry:
834#	file - file, or find_elf child process, to open
835#	FileHandleRef - Reference to file handle to open
836#	LineNumRef - Reference to integer to increment as lines are input
837#
838# exit:
839#	This routine issues a fatal error and does not return on error.
840#	Otherwise, the value of PREFIX is returned.
841#
842sub OpenFindElf {
843	my ($file, $fh, $LineNum) = @_;
844	my $line;
845	my $prefix;
846
847	open($fh, $file) || die "$Prog: Unable to open: $file";
848	$$LineNum = 0;
849
850	# This script requires relative paths as created by 'find_elf -r'.
851	# When this is done, the first non-comment line will always
852	# be PREFIX. Obtain that line, or issue a fatal error.
853	while ($line = onbld_elfmod::GetLine($fh, $LineNum)) {
854		if ($line =~ /^PREFIX\s+(.*)$/i) {
855			$prefix = $1;
856			last;
857		}
858
859		die "$Prog: No PREFIX line seen on line $$LineNum: $file";
860	}
861
862	$prefix;
863}
864
865
866## ProcFindElf(file)
867#
868# Open the specified file, which must be produced by "find_elf -r",
869# and process the files it describes.
870#
871sub ProcFindElf {
872	my $file = $_[0];
873	my $line;
874	my $LineNum;
875
876	my $prefix = OpenFindElf($file, \*FIND_ELF, \$LineNum);
877
878	while ($line = onbld_elfmod::GetLine(\*FIND_ELF, \$LineNum)) {
879		next if !($line =~ /^OBJECT\s/i);
880
881		my ($item, $class, $type, $verdef, $obj) =
882		    split(/\s+/, $line, 5);
883
884		ProcFile("$prefix/$obj", $obj, $class, $type, $verdef);
885	}
886
887	close FIND_ELF;
888}
889
890
891## AltObjectConfig(file)
892#
893# Recurse through a directory hierarchy looking for appropriate dependencies
894# to map from their standard system locations to the proto area via a crle
895# config file.
896#
897# entry:
898#	file - File of ELF objects, in 'find_elf -r' format, to examine.
899#
900# exit:
901#	Scripts are generated for the 32 and 64-bit cases to run crle
902#	and create runtime configuration files that will establish
903#	alternative dependency mappings for the objects identified.
904#
905#	$Env - Set to environment variable definitions that will cause
906#		the config files generated by this routine to be used
907#		by ldd.
908#	$Conf32, $Conf64 - Undefined, or set to the config files generated
909#		by this routine. If defined, the caller is responsible for
910#		unlinking the files before exiting.
911#
912sub AltObjectConfig {
913	my $file = $_[0];
914	my ($Crle32, $Crle64);
915	my $line;
916	my $LineNum;
917	my $obj_path;
918	my $obj_active = 0;
919	my $obj_class;
920
921	my $prefix = OpenFindElf($file, \*FIND_ELF);
922
923LINE:
924	while ($line = onbld_elfmod::GetLine(\*FIND_ELF, \$LineNum)) {
925	      ITEM: {
926
927			if ($line =~ /^OBJECT\s/i) {
928				my ($item, $class, $type, $verdef, $obj) =
929				    split(/\s+/, $line, 5);
930
931				if ($type eq 'DYN') {
932					$obj_active = 1;
933					$obj_path = $obj;
934					$obj_class = $class;
935				} else {
936					# Only want sharable objects
937					$obj_active = 0;
938				}
939				last ITEM;
940			}
941
942			# We need to follow links to sharable objects so
943			# that any dependencies are expressed in all their
944			# available forms. We depend on ALIAS lines directly
945			# following the object they alias, so if we have
946			# a current object, this alias belongs to it.
947			if ($obj_active && ($line =~ /^ALIAS\s/i)) {
948				my ($item, $real_obj, $obj) =
949				    split(/\s+/, $line, 3);
950				$obj_path = $obj;
951				last ITEM;
952			}
953
954			# Skip unrecognized item
955			next LINE;
956		}
957
958		next if !$obj_active;
959
960		my $full = "$prefix/$obj_path";
961
962		next if defined($EXRE_nocrlealt) &&
963		    ($obj_path =~ $EXRE_nocrlealt);
964
965		my $Dir = $full;
966		$Dir =~ s/^(.*)\/.*$/$1/;
967
968		# Create a crle(1) script for the dependency we've found.
969		# We build separate scripts for the 32 and 64-bit cases.
970		# We create and initialize each script when we encounter
971		# the first object that needs it.
972		if ($obj_class == 32) {
973			if (!$Crle32) {
974				$Crle32 = "$Tmpdir/$Prog.crle32.$$";
975				open(CRLE32, "> $Crle32") ||
976				    die "$Prog: open failed: $Crle32: $!";
977				print CRLE32 "#!/bin/sh\ncrle \\\n";
978			}
979			print CRLE32 "\t-o $Dir -a /$obj_path \\\n";
980		} elsif ($Ena64) {
981			if (!$Crle64) {
982				$Crle64 = "$Tmpdir/$Prog.crle64.$$";
983				open(CRLE64, "> $Crle64") ||
984				    die "$Prog: open failed: $Crle64: $!";
985				print CRLE64 "#!/bin/sh\ncrle -64\\\n";
986			}
987			print CRLE64 "\t-o $Dir -a /$obj_path \\\n";
988		}
989	}
990
991	close FIND_ELF;
992
993
994	# Now that the config scripts are complete, use them to generate
995	# runtime linker config files.
996	if ($Crle64) {
997		$Conf64 = "$Tmpdir/$Prog.conf64.$$";
998		print CRLE64 "\t-c $Conf64\n";
999
1000		chmod 0755, $Crle64;
1001		close CRLE64;
1002
1003		undef $Conf64 if system($Crle64);
1004
1005		# Done with the script
1006		unlink $Crle64;
1007	}
1008	if ($Crle32) {
1009		$Conf32 = "$Tmpdir/$Prog.conf32.$$";
1010		print CRLE32 "\t-c $Conf32\n";
1011
1012		chmod 0755, $Crle32;
1013		close CRLE32;
1014
1015		undef $Conf32 if system($Crle32);
1016
1017		# Done with the script
1018		unlink $Crle32;
1019	}
1020
1021	# Set $Env so that we will use the config files generated above
1022	# when we run ldd.
1023	if ($Crle64 && $Conf64 && $Crle32 && $Conf32) {
1024		$Env = "-e LD_FLAGS=config_64=$Conf64,config_32=$Conf32";
1025	} elsif ($Crle64 && $Conf64) {
1026		$Env = "-e LD_FLAGS=config_64=$Conf64";
1027	} elsif ($Crle32 && $Conf32) {
1028		$Env = "-e LD_FLAGS=config_32=$Conf32";
1029	}
1030}
1031
1032# -----------------------------------------------------------------------------
1033
1034# This script relies on ldd returning output reflecting only the binary
1035# contents.  But if LD_PRELOAD* environment variables are present, libraries
1036# named by them will also appear in the output, disrupting our analysis.
1037# So, before we get too far, scrub the environment.
1038
1039delete($ENV{LD_PRELOAD});
1040delete($ENV{LD_PRELOAD_32});
1041delete($ENV{LD_PRELOAD_64});
1042
1043# Establish a program name for any error diagnostics.
1044chomp($Prog = `basename $0`);
1045
1046# The onbld_elfmod package is maintained in the same directory as this
1047# script, and is installed in ../lib/perl. Use the local one if present,
1048# and the installed one otherwise.
1049my $moddir = dirname($0);
1050$moddir = "$moddir/../lib/perl" if ! -f "$moddir/onbld_elfmod.pm";
1051require "$moddir/onbld_elfmod.pm";
1052
1053# Determine what machinery is available.
1054my $Mach = `uname -p`;
1055my$Isalist = `isalist`;
1056if ($Mach =~ /sparc/) {
1057	if ($Isalist =~ /sparcv9/) {
1058		$Ena64 = "ok";
1059	}
1060} elsif ($Mach =~ /i386/) {
1061	if ($Isalist =~ /amd64/) {
1062		$Ena64 = "ok";
1063	}
1064}
1065
1066# $Env is used with all calls to ldd. It is set by AltObjectConfig to
1067# cause an alternate object mapping runtime config file to be used.
1068$Env = '';
1069
1070# Check that we have arguments.
1071if ((getopts('D:d:E:e:f:I:imosvw:', \%opt) == 0) ||
1072    (!$opt{f} && ($#ARGV == -1))) {
1073	print "usage: $Prog [-imosv] [-D depfile | -d depdir] [-E errfile]\n";
1074	print "\t\t[-e exfile] [-f listfile] [-I infofile] [-w outdir]\n";
1075	print "\t\t[file | dir]...\n";
1076	print "\n";
1077	print "\t[-D depfile]\testablish dependencies from 'find_elf -r' file list\n";
1078	print "\t[-d depdir]\testablish dependencies from under directory\n";
1079	print "\t[-E errfile]\tdirect error output to file\n";
1080	print "\t[-e exfile]\texceptions file\n";
1081	print "\t[-f listfile]\tuse file list produced by find_elf -r\n";
1082	print "\t[-I infofile]\tdirect informational output (-i, -v) to file\n";
1083	print "\t[-i]\t\tproduce dynamic table entry information\n";
1084	print "\t[-m]\t\tprocess mcs(1) comments\n";
1085	print "\t[-o]\t\tproduce one-liner output (prefixed with pathname)\n";
1086	print "\t[-s]\t\tprocess .stab and .symtab entries\n";
1087	print "\t[-v]\t\tprocess version definition entries\n";
1088	print "\t[-w outdir]\tinterpret all files relative to given directory\n";
1089	exit 1;
1090}
1091
1092die "$Prog: -D and -d options are mutually exclusive\n" if ($opt{D} && $opt{d});
1093
1094$Tmpdir = "/tmp" if (!($Tmpdir = $ENV{TMPDIR}) || (! -d $Tmpdir));
1095
1096# If -w, change working directory to given location
1097!$opt{w} || chdir($opt{w}) || die "$Prog: can't cd to $opt{w}";
1098
1099# Locate and process the exceptions file
1100onbld_elfmod::LoadExceptionsToEXRE('check_rtime');
1101
1102# Is there a proto area available, either via the -d option, or because
1103# we are part of an activated workspace?
1104my $Proto;
1105if ($opt{d}) {
1106	# User specified dependency directory - make sure it exists.
1107	-d $opt{d} || die "$Prog: $opt{d} is not a directory\n";
1108	$Proto = $opt{d};
1109} elsif ($ENV{CODEMGR_WS}) {
1110	my $Root;
1111
1112	# Without a user specified dependency directory see if we're
1113	# part of a codemanager workspace and if a proto area exists.
1114	$Proto = $Root if ($Root = $ENV{ROOT}) && (-d $Root);
1115}
1116
1117# If we are basing this analysis off the sharable objects found in
1118# a proto area, then gather dependencies and construct an alternative
1119# dependency mapping via a crle(1) configuration file.
1120#
1121# To support alternative dependency mapping we'll need ldd(1)'s
1122# -e option.  This is relatively new (s81_30), so make sure
1123# ldd(1) is capable before gathering any dependency information.
1124if ($opt{D} || $Proto) {
1125	if (system('ldd -e /usr/lib/lddstub 2> /dev/null')) {
1126		print "ldd: does not support -e, unable to ";
1127		print "create alternative dependency mappingings.\n";
1128		print "ldd: option added under 4390308 (s81_30).\n\n";
1129	} else {
1130		# If -D was specified, it supplies a list of files in
1131		# 'find_elf -r' format, and can use it directly. Otherwise,
1132		# we will run find_elf as a child process to find the
1133		# sharable objects found under $Proto.
1134		AltObjectConfig($opt{D} ? $opt{D} : "find_elf -frs $Proto|");
1135	}
1136}
1137
1138# To support unreferenced dependency detection we'll need ldd(1)'s -U
1139# option.  This is relatively new (4638070), and if not available we
1140# can still fall back to -u.  Even with this option, don't use -U with
1141# releases prior to 5.10 as the cleanup for -U use only got integrated
1142# into 5.10 under 4642023.  Note, that nightly doesn't typically set a
1143# RELEASE from the standard <env> files.  Users who wish to disable use
1144# of ldd(1)'s -U should set (or uncomment) RELEASE in their <env> file
1145# if using nightly, or otherwise establish it in their environment.
1146if (system('ldd -U /usr/lib/lddstub 2> /dev/null')) {
1147	$LddNoU = 1;
1148} else {
1149	my($Release);
1150
1151	if (($Release = $ENV{RELEASE}) && (cmp_os_ver($Release, "<", "5.10"))) {
1152		$LddNoU = 1;
1153	} else {
1154		$LddNoU = 0;
1155	}
1156}
1157
1158# Set up variables used to handle output files:
1159#
1160# Error messages go to stdout unless -E is specified. $ErrFH is a
1161# file handle reference that points at the file handle where error messages
1162# are sent, and $ErrTtl is a reference that points at an integer used
1163# to count how many lines have been sent there.
1164#
1165# Informational messages go to stdout unless -I is specified. $InfoFH is a
1166# file handle reference that points at the file handle where info messages
1167# are sent, and $InfoTtl is a reference that points at an integer used
1168# to count how many lines have been sent there.
1169#
1170if ($opt{E}) {
1171	open(ERROR, ">$opt{E}") || die "$Prog: open failed: $opt{E}";
1172	$ErrFH = \*ERROR;
1173} else {
1174	$ErrFH = \*STDOUT;
1175}
1176
1177if ($opt{I}) {
1178	open(INFO, ">$opt{I}") || die "$Prog: open failed: $opt{I}";
1179	$InfoFH = \*INFO;
1180} else {
1181	$InfoFH = \*STDOUT;
1182}
1183my ($err_dev, $err_ino) = stat($ErrFH);
1184my ($info_dev, $info_ino) = stat($InfoFH);
1185$ErrTtl = \$OutCnt1;
1186$InfoTtl = (($err_dev == $info_dev) && ($err_ino == $info_ino)) ?
1187    \$OutCnt1 : \$OutCnt2;
1188
1189
1190# If we were given a list of objects in 'find_elf -r' format, then
1191# process it.
1192ProcFindElf($opt{f}) if $opt{f};
1193
1194# Process each argument
1195foreach my $Arg (@ARGV) {
1196	# Run find_elf to find the files given by $Arg and process them
1197	ProcFindElf("find_elf -fr $Arg|");
1198}
1199
1200# Cleanup output files
1201unlink $Conf64 if $Conf64;
1202unlink $Conf32 if $Conf32;
1203close ERROR if $opt{E};
1204close INFO if $opt{I};
1205
1206exit 0;
1207