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