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