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