xref: /illumos-gate/usr/src/cmd/sgs/lari/lari.pl (revision 9acbbeaf2a1ffe5c14b244867d427714fab43c5c)
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 2006 Sun Microsystems, Inc.  All rights reserved.
25# Use is subject to license terms.
26#
27# ident	"%Z%%M%	%I%	%E% SMI"
28#
29# Link Analysis of Runtime Interfaces.
30#
31
32# Define all global variables (required for strict)
33use vars  qw($Prog $DestDir $ObjRef $ObjFlag $ObjSize $TmpDir $LddArgs);
34use vars  qw($Glob $Intp $Cpyr $Prot $Extn $Self $Filt $Dirc $Plta $User $Func);
35use vars  qw($Objt $UndefSym $IgnSyms $Rtld $MultSyms $CrtSyms $GlobWeak);
36use vars  qw($DbgSeed %opt %Symbols %Objects %Versioned %DemSyms);
37use vars  qw($Platform $Nodi $Osft $Oaft $Ssft $Saft $Msft);
38
39use strict;
40
41use Getopt::Std;
42use File::Basename;
43
44# Pattern match to skip objects.
45$Rtld = qr{
46	/lib/ld\.so\.1 |
47	/usr/lib/ld\.so\.1 |
48	/lib/sparcv9/ld\.so\.1 |
49	/usr/lib/sparcv9/ld\.so\.1 |
50	/lib/amd64/ld\.so\.1 |
51	/usr/lib/amd64/ld\.so\.1
52}x;
53
54# Pattern matching required to determine a global symbol.
55$GlobWeak = qr{ ^(?:
56	GLOB |
57	WEAK
58	)$
59}x;
60
61# Pattern matching to determine link-editor specific symbols and those common
62# to the compilation environment (ie. provided by all crt's).
63$MultSyms = qr{ ^(?:
64	 _DYNAMIC |
65	 _GLOBAL_OFFSET_TABLE_ |
66	 _PROCEDURE_LINKAGE_TABLE_ |
67	 _etext |
68	 _edata |
69	 _end |
70	 _init |
71	 _fini |
72	 _lib_version |			# Defined in values
73	 __xpg4 |			# Defined in values
74	 __xpg6				# Defined in values
75	)$
76}x;
77
78$CrtSyms = qr{ ^(?:
79	 ___Argv |			# Defined in crt
80	 __environ_lock |		# Defined in crt
81	 _environ |			# Defined in crt
82	 environ			# Defined in crt
83	 )$
84}x;
85
86# Pattern match to remove undefined, NOTY and versioning symbols.
87$UndefSym = qr{ ^(?:
88	UNDEF
89	)$
90}x;
91
92$IgnSyms = qr{ ^(?:
93	NOTY |
94	ABS
95	)$
96}x;
97
98# Symbol flags.
99$Glob = 0x00001;	# symbol is global
100$Intp = 0x00010;	# symbol originates for explicit interposer
101$Dirc = 0x00020;	# symbol bound to directly
102$Cpyr = 0x00040;	# symbol bound to copy-relocation reference
103$Prot = 0x00080;	# symbol is protected (symbolic)
104$Extn = 0x00100;	# symbol has been bound to from an external reference
105$Self = 0x00200;	# symbol has been bound to from the same object
106$Filt = 0x00400;	# symbol bound to a filtee
107$Plta = 0x00800;	# symbol bound to executables plt address
108$User = 0x01000;	# symbol binding originates from user (dlsym) request
109$Func = 0x02000;	# symbol is of type function
110$Objt = 0x04000;	# symbol is of type object
111$Nodi = 0x08000;	# symbol prohibits direct binding
112
113$Osft = 0x10000;	# symbol is an standard object filter
114$Oaft = 0x20000;	# symbol is an auxiliary object filter
115$Ssft = 0x40000;	# symbol is a per-symbol standard filter
116$Saft = 0x80000;	# symbol is a per-symbol auxilary filter
117$Msft = 0xf0000;	# filter mask
118
119# Offsets into $Symbols{$SymName}{$Obj} array.
120$ObjRef =	0;
121$ObjFlag =	1;
122$ObjSize =	2;
123
124
125# Establish locale
126use POSIX qw(locale_h);
127use Sun::Solaris::Utils qw(textdomain gettext);
128
129setlocale(LC_ALL, "");
130textdomain("SUNW_OST_SGS");
131
132# Establish a program name for any error diagnostics.
133$Prog = basename($0);
134
135sub inappropriate {
136	my ($Opt1, $Opt2, $Flag) = @_;
137
138	if ($Flag) {
139	    printf STDERR
140		gettext("%s: inappropriate use of %s with %s: %s ignored\n"),
141		$Prog, $Opt1, $Opt2, $Opt1;
142	} else {
143	    printf STDERR
144		gettext("%s: inappropriate use of %s without %s: %s ignored\n"),
145		$Prog, $Opt1, $Opt2, $Opt1;
146	}
147}
148
149# Cleanup any temporary files on interruption
150sub Cleanup {
151	my ($Sig) = @_;
152
153	$SIG{$Sig} = 'IGNORE';
154
155	if ($DbgSeed ne "") {
156		foreach my $File (<\Q${DbgSeed}\E.*>) {
157			if ($File =~ /^\Q$DbgSeed\E\.\d+$/) {
158				unlink($File);
159			}
160		}
161	}
162	exit 1;
163}
164
165# Check that we have arguments.
166if ((getopts('abCDd:imosv', \%opt) == 0) || ($#ARGV < 0)) {
167	printf STDERR gettext("usage:\n");
168	printf STDERR
169	    gettext("    %s [-bCDsv] [-a | -i | -o ] file | dir ...\n"), $Prog;
170	printf STDERR
171	    gettext("    %s [-CDosv] [-m [-d mapdir]] file\n"), $Prog;
172	print STDERR
173	    gettext("\t[-a]     print diagnostics for all symbols\n");
174	print STDERR
175	    gettext("\t[-b]     print diagnostics for multiple-bound " .
176		"symbols\n");
177	print STDERR
178	    gettext("\t[-C]     print demangled symbol names also\n");
179	print STDERR
180	    gettext("\t[-D]     read debugging information from \"file\"\n");
181	print STDERR
182	    gettext("\t[-d dir] create mapfiles in \"mapdir\"\n");
183	print STDERR
184	    gettext("\t[-i]     print interesting information (default)\n");
185	print STDERR
186	    gettext("\t[-m]     create mapfiles for interface requirements\n");
187	print STDERR
188	    gettext("\t[-o]     print overhead information\n");
189	print STDERR
190	    gettext("\t[-s]     save bindings information created by ldd(1)\n");
191	print STDERR
192	    gettext("\t[-v]     ignore versioned objects\n");
193	exit 1;
194} else {
195	my ($Mult, $Error);
196
197	# Catch any incompatible argument usage.
198	if ($opt{m}) {
199		if ($opt{a}) {
200			inappropriate("-a", "-m", 1);
201			$opt{a} = 0;
202		}
203		if ($opt{i}) {
204			inappropriate("-i", "-m", 1);
205			$opt{i} = 0;
206		}
207	} else {
208		if ($opt{d}) {
209			inappropriate("-d", "-m", 0);
210			$opt{d} = 0;
211		}
212	}
213	if ($opt{a}) {
214		if ($opt{o}) {
215			inappropriate("-a", "-o", 1);
216			$opt{o} = 0;
217		}
218		if ($opt{i}) {
219			inappropriate("-a", "-i", 1);
220			$opt{i} = 0;
221		}
222	}
223	if ($opt{o}) {
224		if ($opt{i}) {
225			inappropriate("-o", "-i", 1);
226			$opt{i} = 0;
227		}
228		if ($opt{b}) {
229			inappropriate("-o", "-b", 1);
230			$opt{b} = 0;
231		}
232	}
233
234	# If -m is used, only one input file is applicable.
235	if ($opt{m} && ($#ARGV != 0)) {
236		printf STDERR gettext("%s: only one input file is allowed " .
237		    "with the -m option\n"), $Prog;
238		exit 1;
239	}
240
241	# Insure any specified directory exists, or apply a default.
242	if ($opt{d}) {
243		# User specified directory - make sure it exists.
244		if (! -d $opt{d}) {
245			printf STDERR gettext("%s: %s is not a directory\n"),
246			    $Prog, $opt{d};
247			exit 1;
248		}
249		$DestDir = $opt{d};
250	} else {
251		$DestDir = ".";
252	}
253
254	# Establish a temporary directory if necessary.
255	if (!$opt{D}) {
256		if (!($TmpDir = $ENV{TMPDIR}) || (! -d $TmpDir)) {
257			$TmpDir = "/tmp";
258		}
259	}
260
261	# Establish any initial ldd(1) argument requirements.
262	if ($LddArgs = $ENV{LARI_LDD_ARGS}) {
263		$LddArgs = $LddArgs . ' -r -e LD_DEBUG=bindings,files,detail';
264	} else {
265		$LddArgs = '-r -e LD_DEBUG=bindings,files,detail';
266	}
267
268	# If we've been asked to demangle symbols, make sure we can find the
269	# demangler.
270	if ($opt{C}) {
271		my ($DemName) = `dem XXXX 2> /dev/null`;
272		if (!$DemName) {
273			printf STDERR gettext("%s: can not locate demangler: " .
274			    "-C ignored\n"), $Prog;
275			$opt{C} = 0;
276		}
277	}
278
279	# If -a or -o hasn't been specified, default to -i.
280	if (!$opt{a} && !$opt{o}) {
281		$opt{i} = 1;
282	}
283
284	# Determine whether we have multiple input files.
285	if ($#ARGV == 0) {
286		$Mult = 0;
287	} else {
288		$Mult = 1;
289	}
290
291	# Determine what platform we're running on - some inappropriate
292	# platform specific dependencies are better skipped.
293	chomp($Platform = `uname -i`);
294
295	# Establish signal handlers
296	$SIG{INT} = \&Cleanup;
297	$SIG{QUIT} = \&Cleanup;
298
299	$DbgSeed = "";
300
301	# For each argument determine if we're dealing with a file or directory.
302	$Error = 0;
303	foreach my $Arg (@ARGV) {
304		if (!stat($Arg)) {
305			printf STDERR gettext("%s: %s: unable to stat file\n"),
306			    $Prog, $Arg;
307			$Error = 1;
308			next;
309		}
310
311		# Process simple files.
312		if (-f _) {
313			if (!-r _) {
314				printf STDERR gettext("%s: %s: unable to " .
315				   "read file\n"), $Prog, $Arg;
316				$Error = 1;
317				next;
318			}
319			if (!$opt{D}) {
320				if (ProcFile($Arg, $Mult, 1) == 0) {
321					$Error = 1;
322				}
323			} else {
324				# If the -D option is specified, read the
325				# bindings debugging information from the
326				# specified file.
327				if ($Mult) {
328					print STDOUT "$Arg:\n";
329				}
330				ProcBindings($Arg, $Mult, $Arg);
331			}
332			next;
333		}
334
335		# Process directories.
336		if (-d _) {
337			ProcDir($Arg);
338			next;
339		}
340
341		printf STDERR gettext("%s: %s: is not a file or directory\n"),
342		    $Prog, $Arg;
343		$Error = 1;
344	}
345	exit $Error;
346}
347
348sub ProcDir {
349	my ($Dir) = @_;
350	my ($File);
351
352	# Open the directory and read each entry, omit "." and "..".  Sorting
353	# the directory listing makes analyzing different source hierarchies
354	# easier.
355	if (opendir(DIR, $Dir)) {
356		foreach my $Entry (sort(readdir(DIR))) {
357			if (($Entry eq '.') || ($Entry eq '..')) {
358				next;
359			}
360
361			# If we're decending into a platform directory, ignore
362			# any inappropriate platform specific files.  These
363			# files can have dependencies that in turn bring in the
364			# appropriate platform specific file, resulting in more
365			# than one dependency offering the same interfaces.  In
366			# practice, the non-appropriate platform specific file
367			# wouldn't be loaded with a process.
368			if (($Dir =~ /\/platform$/) &&
369			    ($Entry !~ /^$Platform$/)) {
370				next;
371			}
372
373			$File = "$Dir/$Entry";
374			if (!lstat($File)) {
375				next;
376			}
377			# Ignore symlinks.
378			if (-l _) {
379				next;
380			}
381
382			# Descend into, and process any directories.
383			if (-d _) {
384				ProcDir($File);
385				next;
386			}
387
388			# Process any standard files.
389			if (-f _ && -r _) {
390				ProcFile($File, 1, 0);
391				next;
392
393			}
394		}
395		closedir(DIR);
396	}
397}
398
399# Process a file.  If the file was explicitly defined on the command-line, and
400# an error occurs, tell the user.  Otherwise, this file probably came about from
401# scanning a directory, in which case just skip it and move on.
402sub ProcFile {
403	my ($File, $Mult, $CmdLine) = @_;
404	my (@Ldd, $NoFound, $DbgFile, @DbgGlob, $Type);
405
406	# If we're scanning a directory (ie. /lib) and have picked up ld.so.1,
407	# ignore it.
408	if (($CmdLine eq 0) && ($File =~ $Rtld)) {
409		return 1;
410	}
411
412	$Type = `LC_ALL=C file '$File' 2>&1`;
413	if (($Type !~ /dynamically linked/) || ($Type =~ /Sun demand paged/)) {
414		if ($CmdLine) {
415			printf STDERR gettext("%s: %s: is an invalid file " .
416			    "type\n"), $Prog, $File;
417		}
418		return 0;
419	}
420
421	# Create a temporary filename for capturing binding information.
422	$DbgSeed = basename($File);
423	$DbgSeed = "$TmpDir/lari.dbg.$$.$DbgSeed";
424
425	# Exercise the file under ldd(1), capturing all the bindings.
426	@Ldd = split(/\n/,
427	    `LC_ALL=C ldd $LddArgs -e LD_DEBUG_OUTPUT='$DbgSeed' '$File' 2>&1`);
428
429	# If ldd isn't -e capable we'll get a usage message.  The -e option was
430	# introduced in Solaris 9 and related patches.  Also, make sure the user
431	# sees any ldd errors.
432	$NoFound = 0;
433	for my $Line (@Ldd) {
434		if ($Line =~ /^usage: ldd/) {
435			printf STDERR gettext("%s: ldd: does not support -e, " .
436			    "unable to capture bindings output\n"), $Prog;
437			exit 1;
438		}
439		if ($Line =~ /not found/) {
440			$NoFound = 1;
441			last;
442		}
443	}
444
445	# The runtime linker will have appended a process id to the debug file.
446	# As we have to intuit the name, make sure there is only one debug
447	# file match, otherwise there must be some clutter in the output
448	# directory that is going to mess up our analysis.
449	foreach my $Match (<\Q${DbgSeed}\E.*>) {
450		if ($Match =~ /^\Q$DbgSeed\E\.\d+$/) {
451			push(@DbgGlob, $Match);
452		}
453	}
454	if (@DbgGlob == 0) {
455		# If there is no debug file, bail.  This can occur if the file
456		# being processed is secure.
457		if ($CmdLine) {
458			printf STDERR gettext("%s: %s: unable to capture " .
459			    "bindings output - possible secure application?\n"),
460			    $Prog, $File;
461		}
462		return 0;
463	} elsif (@DbgGlob > 1) {
464		# Too many debug files found.
465		if ($CmdLine) {
466			printf STDERR gettext("%s: %s: multiple bindings " .
467			    "output files exist: %s: clean up temporary " .
468			    "directory\n"), $Prog, $File, $DbgSeed;
469		}
470		return 0;
471	} else {
472		$DbgFile = $DbgGlob[0];
473	}
474
475	# Ok, we're ready to process the bindings information.  Print a header
476	# if necessary, and if there were any ldd(1) errors push some of them
477	# out before any bindings information.  Limit the output, as it can
478	# sometimes be excessive.  If there are errors, the bindings information
479	# is likely to be incomplete.
480	if ($Mult) {
481		print STDOUT "$File:\n";
482	}
483	if ($NoFound) {
484		my ($Cnt) = 4;
485
486		for my $Line (@Ldd) {
487			if ($Line =~ /not found/) {
488				print STDOUT "$Line\n";
489				$Cnt--;
490			}
491			if ($Cnt == 0) {
492				print STDOUT gettext("\tcontinued ...\n");
493				last;
494			}
495		}
496	}
497
498	# If the user wants the original debugging file left behind, rename it
499	# so that it doesn't get re-read by another instance of lari processing
500	# this file.
501	if ($opt{s}) {
502		rename($DbgFile, $DbgSeed);
503		$DbgFile = $DbgSeed;
504		printf STDOUT gettext("%s: %s: bindings information " .
505		    "saved as: %s\n"), $Prog, $File, $DbgFile;
506	}
507
508	ProcBindings($File, $Mult, $DbgFile);
509
510	# Now that we've finished with the debugging file, nuke it if necessary.
511	if (!$opt{s}) {
512		unlink($DbgFile);
513	}
514	$DbgSeed = "";
515	return 1;
516}
517
518sub ProcBindings {
519	my ($File, $Mult, $DbgFile) = @_;
520	my (%Filtees, $FileHandle);
521
522	# Reinitialize our arrays when we're dealing with multiple files.
523	if ($Mult) {
524		%Symbols = ();
525		%Objects = ();
526		%Versioned = ();
527	}
528
529	# As debugging output can be significant, read a line at a time.
530	open($FileHandle, "<$DbgFile");
531	while (defined(my $Line = <$FileHandle>)) {
532		chomp($Line);
533
534		# If we find a relationship between a filter and filtee, save
535		# it, we'll come back to this once we've gathered everybodies
536		# symbols.
537		if ($Line =~ /;  filtered by /) {
538			my ($Filtee) = $Line;
539			my ($Filter) = $Line;
540
541			# Separate the filter and filtee names, ignore the
542			# runtime linker.
543			$Filtee =~ s/^.*: file=(.*);  filtered by .*/$1/;
544			if ($Filtee =~ $Rtld) {
545				next;
546			}
547			$Filter =~ s/^.*;  filtered by //;
548			$Filtees{$Filtee}{$Filter} = 1;
549			next;
550		}
551
552		# If we find a configuration alternative, determine whether it
553		# is for one of our filtees, and if so record it.
554		if ($Line =~ / configuration alternate found:/) {
555			my ($Orig) = $Line;
556			my ($Altr) = $Line;
557
558			# Separate the original and alternative names.
559			$Orig =~ s/^.*: file=(.*)  .*$/$1/;
560			$Altr =~ s/^.* configuration alternate found: (.*)$/$1/;
561
562			for my $Filtee (keys(%Filtees)) {
563				if ($Filtee ne $Orig) {
564					next;
565				}
566				for my $Filter (keys(%{$Filtees{$Filtee}})) {
567					$Filtees{$Altr}{$Filter} = 1;
568				}
569			}
570			next;
571		}
572
573		# Collect the symbols from any file analyzed.
574		if ($Line =~ /^.*: file=(.*);  analyzing .*/) {
575			GetAllSymbols($1);
576			next;
577		}
578
579		# Process any symbolic relocations that bind to a file.
580		if ($Line =~ /: binding file=.* to file=/) {
581			my ($RefFile, $DstFile, $SymName);
582			my (@Syms, $Found, @Fields);
583			my ($BndInfo) = 0;
584			my ($Offset) = 1;
585			my ($Dlsym) = 0;
586			my ($Detail) = 0;
587
588			# For greatest flexibility, split the line into fields
589			# and walk each field until we find what we need.
590			@Fields = split(' ', $Line);
591
592			# The referencing file, "... binding file=".*".
593			while ($Fields[$Offset]) {
594				if ($Fields[$Offset] =~ /^file=(.*)/) {
595					$RefFile = $1;
596					$Offset++;
597					last;
598				}
599				$Offset++;
600			}
601			# The referencing offset, typically this is the address
602			# of the reference, "(0x1234...)", but in the case of a
603			# user lookup it's the string "(dlsym)".  If we don't
604			# find this offset information we've been given a debug
605			# file that didn't user the "datail" token, in which case
606			# we're not getting all the information we need.
607			if ($Fields[$Offset] =~ /^\((.*)\)/) {
608				if ($1 eq 'dlsym') {
609					$Dlsym = 1;
610				}
611				$Detail = 1;
612				$Offset++;
613			}
614			# The destination file, "... to file=".*".
615			while ($Fields[$Offset]) {
616				if ($Fields[$Offset] =~ /^file=(.*)/) {
617					$DstFile = $1;
618					$Offset++;
619					last;
620				}
621				$Offset++;
622			}
623			# The symbol being bound, "... symbol `.*' ...".
624			while ($Fields[$Offset]) {
625				if ($Fields[$Offset] =~ /^\`(.*)\'$/) {
626					$SymName = $1;
627					$Offset++;
628					last;
629				}
630				$Offset++;
631			}
632			# Possible trailing binding info, "... (direct,.*)$".
633			while ($Fields[$Offset]) {
634				if ($Fields[$Offset] =~ /^\((.*)\)$/) {
635					$BndInfo = $1;
636					$Offset++;
637					last;
638				}
639				$Offset++;
640			}
641
642			if ($Detail == 0) {
643				printf STDERR gettext("%s: %s: debug file " .
644				    "does not contain `detail' information\n"),
645				    $Prog, $DbgFile;
646				return;
647			}
648
649			# Collect the symbols from each object.
650			GetAllSymbols($RefFile);
651			GetAllSymbols($DstFile);
652
653			# Identify that this definition has been bound to.
654			$Symbols{$SymName}{$DstFile}[$ObjRef]++;
655			if ($RefFile eq $DstFile) {
656				# If the reference binds to a definition within
657				# the same file this symbol may be a candidate
658				# for reducing to local.
659				$Symbols{$SymName}{$DstFile}[$ObjFlag] |= $Self;
660				$Objects{$DstFile}{$SymName} |= $Self;
661			} else {
662				# This symbol is required to satisfy an external
663				# reference.
664				$Symbols{$SymName}{$DstFile}[$ObjFlag] |= $Extn;
665				$Objects{$DstFile}{$SymName} |= $Extn;
666			}
667
668			# Assign any other state indicated by the binding info
669			# associated with the diagnostic output.
670			if (!$BndInfo) {
671				next;
672			}
673
674			if ($BndInfo =~ /direct/) {
675				$Symbols{$SymName}{$DstFile}[$ObjFlag] |= $Dirc;
676				$Objects{$DstFile}{$SymName} |= $Dirc;
677			}
678			if ($BndInfo =~ /copy-ref/) {
679				$Symbols{$SymName}{$DstFile}[$ObjFlag] |= $Cpyr;
680				$Objects{$DstFile}{$SymName} |= $Cpyr;
681			}
682			if ($BndInfo =~ /filtee/) {
683				$Symbols{$SymName}{$DstFile}[$ObjFlag] |= $Filt;
684				$Objects{$DstFile}{$SymName} |= $Filt;
685			}
686			if ($BndInfo =~ /interpose/) {
687				$Symbols{$SymName}{$DstFile}[$ObjFlag] |= $Intp;
688				$Objects{$DstFile}{$SymName} |= $Intp;
689			}
690			if ($BndInfo =~ /plt-addr/) {
691				$Symbols{$SymName}{$DstFile}[$ObjFlag] |= $Plta;
692				$Objects{$DstFile}{$SymName} |= $Plta;
693			}
694			if ($Dlsym) {
695				$Symbols{$SymName}{$DstFile}[$ObjFlag] |= $User;
696				$Objects{$DstFile}{$SymName} |= $User;
697			}
698		}
699	}
700	close($FileHandle);
701
702	# Now that we've processed all objects, complete any auxiliary filtee
703	# tagging.  For each filtee, determine which of the symbols it exports
704	# are also defined in its filters.  If a filtee is bound to, the
705	# runtime linkers diagnostics will indicate a filtee binding.  However,
706	# some of the filtee symbols may not be bound to, so here we mark them
707	# all so as to remove them from any interesting output.
708	for my $Filtee (keys(%Filtees)) {
709
710		# Standard filters aren't captured at all, as nothing can bind
711		# to them.
712		if (!exists($Objects{$Filtee})) {
713			next;
714		}
715
716		# Determine what symbols this filtee offers.
717		foreach my $SymName (keys(%{$Objects{$Filtee}})) {
718
719			# Ignore the usual reserved stuff.
720			if (!$opt{a} && (($SymName =~ $MultSyms) ||
721			    ($SymName =~ $CrtSyms))) {
722				next;
723			}
724
725			# Determine whether this symbol exists in our filter.
726			for my $Filter (keys(%{$Filtees{$Filtee}})) {
727				if (!$Symbols{$SymName}{$Filter}) {
728					next;
729				}
730				if (!($Symbols{$SymName}{$Filter}[$ObjFlag] &
731				    $Msft)) {
732					next;
733				}
734				$Symbols{$SymName}{$Filtee}[$ObjFlag] |= $Filt;
735			}
736		}
737	}
738
739	# Process objects and their symbols as required.
740	if ($opt{m}) {
741		# If we're creating a mapfile, traverse each object we've
742		# collected.
743		foreach my $Obj (keys(%Objects)) {
744			my ($File, $Path);
745
746			# Skip any objects that should be ignored.
747			if ($Obj =~ $Rtld) {
748				next;
749			}
750
751			# Skip any versioned objects if required.
752			if ($opt{v} && $Versioned{$Obj}) {
753				next;
754			}
755
756			# Open the mapfile if required.
757			$File = basename($Obj);
758			$Path = "$DestDir/mapfile-$File";
759			if (!open(MAPOUT, "> $Path")) {
760				printf STDERR gettext("%s: %s: open failed:" .
761				    "%s\n"), $Prog, $Path, $!;
762				exit 1;
763			}
764
765			# Establish the mapfile preamble.
766			print MAPOUT "#\n# Interface Definition mapfile for:\n";
767			print MAPOUT "#\tDynamic Object: $Obj\n";
768			print MAPOUT "#\tProcess:        $File\n#\n\n";
769
770			# Process each global symbol.
771			print MAPOUT "$File {\n\tglobal:\n";
772
773			foreach my $SymName (sort(keys(%{$Objects{$Obj}}))) {
774				my ($Flag) = $Objects{$Obj}{$SymName};
775
776				# For the first pass we're only interested in
777				# symbols that have been bound to from an
778				# external object, or must be global to enable
779				# a binding to an interposing definition.
780				# Skip bindings to ourself as these are
781				# candidates for demoting to local.
782				if (!($Flag & ($Extn | $Intp))) {
783					next;
784				}
785				if (($Flag & ($Extn | $Self)) == $Self) {
786					next;
787				}
788
789				# Add the demangled name as a comment if
790				# required.
791				if ($opt{C}) {
792					my ($DemName) = Demangle($SymName);
793
794					if ($DemName ne "") {
795						print MAPOUT "\t\t#$DemName\n";
796					}
797				}
798				print MAPOUT "\t\t$SymName;\n";
799			}
800
801			# Process each local demotion.
802			print MAPOUT "\tlocal:\n";
803
804			if ($opt{o}) {
805				foreach my $SymName
806				    (sort(keys(%{$Objects{$Obj}}))) {
807					my ($Flag) = $Objects{$Obj}{$SymName};
808
809					# For this pass we're only interested
810					# in symbol definitions that haven't
811					# been bound to, or have only been
812					# bound to from the same object.
813					if ($Flag & $Extn) {
814						next;
815					}
816
817					# Add the demangled name as a comment if
818					# required.
819					if ($opt{C}) {
820						my ($DemName) =
821						    Demangle($SymName);
822
823						if ($DemName ne "") {
824							print MAPOUT
825							    "\t\t#$DemName\n";
826						}
827					}
828					print MAPOUT "\t\t$SymName;\n";
829				}
830			}
831
832			# Capture everything else as local.
833			print MAPOUT "\t\t\*;\n};\n";
834			close MAPOUT;
835		}
836
837	} else {
838		# If we're gathering information regarding the symbols used by
839		# the process, automatically sort any standard output using the
840		# symbol name.
841		if (!open(SORT, "| sort +1")) {
842			printf STDERR gettext("%s: fork failed: %s\n"),
843			    $Prog, $!;
844			exit 1;
845		}
846
847		foreach my $SymName (keys(%Symbols)) {
848			my ($Cnt);
849
850			# If we're looking for interesting symbols, inspect
851			# each definition of each symbol.  If one is found to
852			# be interesting, the whole family are printed.
853			if (($Cnt = Interesting($SymName)) == 0) {
854				next;
855			}
856
857			# We've found something interesting, or all symbols
858			# should be output.  List all objects that define this
859			# symbol.
860			foreach my $Obj (keys(%{$Symbols{$SymName}})) {
861				my ($DemName, $Type);
862				my ($Flag) = $Symbols{$SymName}{$Obj}[$ObjFlag];
863				my ($Str) = "$Cnt:";
864
865				# Do we just want overhead symbols.  Consider
866				# copy-relocations, and plt address binding,
867				# as overhead too.
868				if ($opt{o} && (($Flag &
869				    ($Extn | $Cpyr | $Plta)) == $Extn)) {
870					next;
871				}
872
873				# Do we just want all symbols that have been
874				# bound to.
875				if (($opt{a} || $opt{o}) && $opt{b} &&
876				    (($Flag & ($Extn | $Self | $Prot)) == 0)) {
877					next;
878				}
879
880				# If we haven't been asked for all symbols, only
881				# print those reserved symbols that have been
882				# bound to, as the number of reserved symbols
883				# can be quite excessive.
884				if (!$opt{a} && ((($SymName =~ $MultSyms) &&
885				    (($Flag & ($Extn | $Self)) == 0)) ||
886				    (($SymName =~ $CrtSyms) && (($Flag &
887				    ($Extn | $Self | $Prot)) == 0)))) {
888					next;
889				}
890
891				# Skip any versioned objects if required.
892				if ($opt{v} && $Versioned{$Obj}) {
893					next;
894				}
895
896				# Display this symbol.
897				if ($Symbols{$SymName}{$Obj}[$ObjRef]) {
898					$Str = $Str .
899					    $Symbols{$SymName}{$Obj}[$ObjRef];
900				} else {
901					$Str = $Str . '0';
902				}
903
904				# Has the symbol been bound to externally
905				if ($Flag & $Extn) {
906					$Str = $Str . 'E';
907				}
908				# Has the symbol been bound to from the same
909				# object.
910				if ($Flag & $Self) {
911					$Str = $Str . 'S';
912				}
913				# Has the symbol been bound to directly.
914				if ($Flag & $Dirc) {
915					$Str = $Str . 'D';
916				}
917				# Does this symbol originate for an explicit
918				# interposer.
919				if ($Flag & $Intp) {
920					$Str = $Str . 'I';
921				}
922				# Is this symbol the reference data of a copy
923				# relocation.
924				if ($Flag & $Cpyr) {
925					$Str = $Str . 'C';
926				}
927				# Is this symbol part of filtee.
928				if ($Flag & $Filt) {
929					$Str = $Str . 'F';
930				}
931				# Is this symbol protected (in which case there
932				# may be a symbolic binding within the same
933				# object to this symbol).
934				if ($Flag & $Prot) {
935					$Str = $Str . 'P';
936				}
937				# Is this symbol an executables .plt address.
938				if ($Flag & $Plta) {
939					$Str = $Str . 'A';
940				}
941				# Does this binding originate from a user
942				# (dlsym) request.
943				if ($Flag & $User) {
944					$Str = $Str . 'U';
945				}
946				# Does this definition redirect the binding.
947				if ($Flag & $Msft) {
948					$Str = $Str . 'R';
949				}
950				# Does this definition explicity define no
951				# direct binding.
952				if ($Flag & $Nodi) {
953					$Str = $Str . 'N';
954				}
955
956				# Determine whether this is a function or a data
957				# object.  For the latter, display the symbol
958				# size.  Otherwise, the symbol is a reserved
959				# label, and is left untyped.
960				if ($Flag & $Func) {
961					$Type = '()';
962				} elsif ($Flag & $Objt) {
963					$Type = '[' .
964					    $Symbols{$SymName}{$Obj}[$ObjSize] .
965					']';
966				} else {
967					$Type = "";
968				}
969
970				# Demangle the symbol name if desired.
971				$DemName = Demangle($SymName);
972
973				if ($Mult) {
974					print SORT "  [$Str]: " .
975					    "$SymName$Type$DemName: $Obj\n";
976				} else {
977					print SORT "[$Str]: " .
978					    "$SymName$Type$DemName: $Obj\n";
979				}
980			}
981		}
982		close SORT;
983	}
984}
985
986# Heuristics to determine whether a symbol binding is interesting.  In most
987# applications there can be a large amount of symbol binding information to
988# wade through.  The most typical binding, to a single definition, probably
989# isn't interesting or the cause of unexpected behavior.  Here, we try and
990# determine those bindings that may can cause unexpected behavior.
991#
992# Note, this routine is actually called for all symbols so that their count
993# can be calculated in one place.
994sub Interesting
995{
996	my ($SymName) = @_;
997	my ($ObjCnt, $GFlags, $BndCnt, $FltCnt, $NodiCnt, $RdirCnt, $ExRef);
998
999	# Scan all definitions of this symbol, thus determining the definition
1000	# count, the number of filters, redirections, executable references
1001	# (copy-relocations, or plt addresses), no-direct bindings, and the
1002	# number of definitions that have been bound to.
1003	$ObjCnt = $GFlags = $BndCnt = $FltCnt =
1004		$NodiCnt = $RdirCnt = $ExRef = 0;
1005	foreach my $Obj (keys(%{$Symbols{$SymName}})) {
1006		my ($Flag) = $Symbols{$SymName}{$Obj}[$ObjFlag];
1007
1008		# Ignore standard filters when determining the symbol count, as
1009		# a standard filter can never be bound to.
1010		if (($Flag & ($Osft | $Ssft)) == 0) {
1011			$ObjCnt++;
1012		}
1013
1014		$GFlags |= $Flag;
1015		if ($Flag & $Filt) {
1016			$FltCnt++;
1017		}
1018		if ($Flag & $Nodi) {
1019			$NodiCnt++;
1020		}
1021		if ($Flag & ($Cpyr | $Plta)) {
1022			$ExRef++;
1023		}
1024		if ($Flag & $Msft) {
1025			$RdirCnt++;
1026		}
1027
1028		# Ignore bindings to undefined .plts, and copy-relocation
1029		# references.  These are implementation details, rather than
1030		# a truly interesting multiple-binding.  If a symbol is tagged
1031		# as protected, count it as having bound to itself, even though
1032		# we can't tell if it's really been used.
1033		if (($Flag & ($Self | $Extn | $Prot)) &&
1034		    (($Flag & ($Plta | $Cpyr)) == 0)) {
1035			$BndCnt++;
1036		}
1037	}
1038
1039	# If we want all overhead symbols, return the count.
1040	if ($opt{o}) {
1041		return $ObjCnt;
1042	}
1043
1044	# If we want all symbols, return the count.  If we want all bound
1045	# symbols, return the count provided it is non-zero.
1046	if ($opt{a} && (!$opt{b} || ($BndCnt > 0))) {
1047		return $ObjCnt;
1048	}
1049
1050	# Single instance symbol definitions aren't very interesting.
1051	if ($ObjCnt == 1) {
1052		return 0;
1053	}
1054
1055	# Traverse each symbol definition looking for the following:
1056	#
1057	#   .	Multiple symbols are bound to externally.
1058	#   .	A symbol is bound to externally, and possibly symbolically.
1059	#
1060	# Two symbol bindings are acceptable in some cases, and thus aren't
1061	# interesting:
1062	#
1063	#   .	Copy relocations.  Here, the executable binds to a shared object
1064	#	to access the data definition, which is then copied to the
1065	#	executable.  All other references should then bind to the copied
1066	#	data.
1067	#   .	Non-plt relocations to functions that are referenced by the
1068	#	executable will bind to the .plt in the executable.  This
1069	#	provides for address comparison calculations (although plainly
1070	#	an overhead).
1071	#
1072	# Multiple symbol bindings are acceptable in some cases, and thus aren't
1073	# interesting:
1074	#
1075	#   .	Filtees.  Multiple filtees may exist for one filter.
1076	#
1077	if ((($ObjCnt == 2) && ($GFlags & ($Cpyr | $Plta))) ||
1078	    ($ObjCnt == ($FltCnt + 1))) {
1079		return 0;
1080	}
1081
1082	# Only display any reserved symbols if more than one binding has
1083	# occurred.
1084	if ((($SymName =~ $MultSyms) || ($SymName =~ $CrtSyms)) &&
1085	    ($BndCnt < 2)) {
1086		return (0);
1087	}
1088
1089	# For all other symbols, determine whether a binding has occurred.
1090	# Note: definitions within an executable are tagged as protected ("P")
1091	# as they may have been bound to from within the executable - we can't
1092	# tell.
1093	if ($opt{b} && ($BndCnt == 0)) {
1094		return (0);
1095	}
1096
1097	# Multiple instances of a definition, where all but one are filter
1098	# references and/or copy relocations, are also uninteresting.
1099	# Effectively, only one symbol is providing the final binding.
1100	if (($FltCnt && $RdirCnt) &&
1101	    (($FltCnt + $RdirCnt + $ExRef) == $ObjCnt)) {
1102		return (0);
1103	}
1104
1105	# Multiple instances of explicitly defined no-direct binding symbols
1106	# are known to occur, and their no-binding definition indicates they
1107	# are expected and accounted for.  Thus, these aren't interesting.
1108	if (($ExRef + $NodiCnt) == $ObjCnt) {
1109		return (0);
1110	}
1111
1112	# We have an interesting symbol, returns its count.
1113	return $ObjCnt;
1114}
1115
1116# Obtain the global symbol definitions of an object and determine whether the
1117# object has been versioned.
1118sub GetAllSymbols {
1119	my ($Obj) = @_;
1120	my (@Elfd, @Elfs, @Elfr, $Type, $Exec, $FileHandle);
1121	my (%AddrToName, %NameToAddr);
1122	my ($Vers) = 0;
1123	my ($Symb) = 0;
1124	my ($Copy) = 0;
1125	my ($Interpose) = 0;
1126	my ($Fltr) = 0;
1127
1128	# Determine whether we've already retrieved this object's symbols.
1129	# Also, ignore the runtime linker, it's on a separate link-map, and
1130	# except for the filtee symbols that might be bound via libdl, is
1131	# uninteresting.  Tag the runtime linker as versioned to simplify
1132	# possible -v processing.
1133	if ($Objects{$Obj}) {
1134		return;
1135	}
1136
1137	if ($Obj =~ $Rtld) {
1138		$Versioned{$Obj} = 1;
1139		return;
1140	}
1141
1142	# Get the dynamic information.
1143	@Elfd = split(/\n/, `LC_ALL=C elfdump -d '$Obj' 2> /dev/null`);
1144
1145	# If there's no information, it's possible we've been given a debug
1146	# output file and are processing it from a location from which the
1147	# dependencies specified in the debug file aren't accessible.
1148	if (!@Elfd) {
1149		printf STDERR gettext("%s: %s: unable to process ELF file\n"),
1150		    $Prog, $Obj;
1151
1152		# Add the file to our list, so that we don't create the same
1153		# message again.  Processing should continue so that we can
1154		# flush out as many error messages as possible.
1155		$Objects{$Obj}{"DoesNotExist"} = 0;
1156		return;
1157	}
1158
1159	# If we're processing a filter there's no need to save any symbols, as
1160	# no bindings will occur to this object.
1161	#
1162	# Determine whether we've got a symbolicly bound object.  With newer
1163	# linkers all symbols will be marked as protected ("P"), but with older
1164	# linkers this state could only be intuited from the symbolic dynamic
1165	# tag.
1166	foreach my $Line (@Elfd) {
1167		my (@Fields);
1168		@Fields = split(' ', $Line);
1169
1170		# Determine if the FILTER tag is set.
1171		if ($#Fields == 3) {
1172			if ($Fields[1] eq "FILTER") {
1173				$Fltr |= $Osft;
1174				next;
1175			}
1176			if ($Fields[1] eq "AUXILIARY") {
1177				$Fltr |= $Oaft;
1178				next;
1179			}
1180			next;
1181		}
1182
1183		# We're only interested in the FLAGS entry.
1184		if (($#Fields < 4) || ($Fields[1] !~ "^FLAGS")) {
1185			next;
1186		}
1187		if (($Fields[1] eq "FLAGS") && ($Line =~ " SYMBOLIC ")) {
1188			$Symb = 1;
1189			next;
1190		}
1191		if (($Fields[1] eq "FLAGS_1") && ($Line =~ " INTERPOSE ")) {
1192			$Interpose = 1;
1193		}
1194	}
1195
1196	# If this file is a dynamic executable, determine if this object has
1197	# any copy relocations so that any associated bindings can be labeled
1198	# more meaningfully.
1199	$Type = `LC_ALL=C file '$Obj'`;
1200	if ($Type =~ /executable/) {
1201		$Exec = 1;
1202		# Obtain any copy relocations.
1203		@Elfr = split(/\n/, `LC_ALL=C elfdump -r '$Obj' 2>&1`);
1204
1205		foreach my $Rel (@Elfr) {
1206			my ($SymName, @Fields);
1207
1208			if ($Rel !~ / R_[A-Z0-9]+_COPY /) {
1209				next;
1210			}
1211			@Fields = split(' ', $Rel);
1212			# Intel relocation records don't contain an addend,
1213			# where as every other supported platform does.
1214			if ($Fields[0] eq 'R_386_COPY') {
1215				$SymName = $Fields[3];
1216			} else {
1217				$SymName = $Fields[4];
1218			}
1219
1220			$Symbols{$SymName}{$Obj}[$ObjFlag] |= $Cpyr;
1221			$Objects{$Obj}{$SymName} |= $Cpyr;
1222			$Copy = 1;
1223		}
1224	} else {
1225		$Exec = 0;
1226	}
1227
1228	# Obtain the dynamic symbol table for this object.  Symbol tables can
1229	# be quite large, so open the elfump command through a pipe.
1230	open($FileHandle, "LC_ALL=C elfdump -sN.dynsym '$Obj' 2> /dev/null |");
1231
1232	# Now process all symbols.
1233	while (defined(my $Line = <$FileHandle>)) {
1234		chomp($Line);
1235
1236		my (@Fields) = split(' ', $Line);
1237		my ($Flags);
1238
1239		# We're only interested in defined non-reserved symbol entries.
1240		# Note, ABS and NOTY symbols of non-zero size have been known to
1241		# occur, so capture them.
1242		if (($#Fields < 8) || ($Fields[4] !~ $GlobWeak) ||
1243		    ($Fields[7] =~ $UndefSym) || (!$opt{a} &&
1244		    ($Fields[7] =~ $IgnSyms) && (oct($Fields[2]) eq 0))) {
1245			next;
1246		}
1247
1248		# If we're found copy relocations, save the address and names
1249		# of any OBJT definitions, together with the copy symbol.
1250		if ($Copy && ($Fields[3] eq 'OBJT')) {
1251			push(@{$AddrToName{$Fields[1]}}, $Fields[8]);
1252		}
1253		if (($Symbols{$Fields[8]}{$Obj}) &&
1254		    ($Symbols{$Fields[8]}{$Obj}[$ObjFlag] & $Cpyr)) {
1255			$NameToAddr{$Fields[8]} = $Fields[1];
1256		}
1257
1258		# If the symbol visibility is protected, this is an internal
1259		# symbolic binding (NOTE, an INTERNAL visibility for a global
1260		# symbol is invalid, but for a while ld(1) was setting this
1261		# attribute mistakenly for protected).
1262		# If this is a dynamic executable, mark its symbols as protected
1263		# (they can't be interposed on any more than symbols defined
1264		# protected within shared objects).
1265		$Flags = $Glob | $Fltr;
1266		if (($Fields[5] =~ /^[IP]$/) || $Symb || $Exec) {
1267			$Flags |= $Prot;
1268		}
1269
1270		# If this object is marked as an interposer, tag each symbol.
1271		if ($Interpose) {
1272			$Flags |= $Intp;
1273		}
1274
1275		# Identify the symbol as a function or data type, and for the
1276		# latter, capture the symbol size.  Ignore the standard
1277		# symbolic labels, as we don't want to type them.
1278		if ($Fields[8] !~ $MultSyms) {
1279			if ($Fields[3] =~ /^FUNC$/) {
1280				$Flags |= $Func;
1281			} elsif ($Fields[3] =~ /^OBJT$/) {
1282				my ($Size) = $Fields[2];
1283
1284				if (oct($Size) eq 0) {
1285					$Size = "0";
1286				} else {
1287					$Size =~ s/0x0*/0x/;
1288				}
1289				$Flags |= $Objt;
1290				$Symbols{$Fields[8]}{$Obj}[$ObjSize] = $Size;
1291			}
1292		}
1293
1294		$Symbols{$Fields[8]}{$Obj}[$ObjFlag] |= $Flags;
1295		$Objects{$Obj}{$Fields[8]} |= $Flags;
1296
1297		# If the version field is non-null this object has already been
1298		# versioned.
1299		if (($Vers == 0) && ($Fields[6] ne '0')) {
1300			$Versioned{$Obj} = 1;
1301			$Vers = 1;
1302		}
1303	}
1304	close($FileHandle);
1305
1306	# Obtain any symbol information table for this object.  Symbol tables can
1307	# be quite large, so open the elfump command through a pipe.
1308	open($FileHandle, "LC_ALL=C elfdump -y '$Obj' 2> /dev/null |");
1309
1310	# Now process all symbols.
1311	while (defined(my $Line = <$FileHandle>)) {
1312		chomp($Line);
1313
1314		my (@Fields) = split(' ', $Line);
1315		my ($Flags) = 0;
1316
1317		# Binding attributes are in the second column.
1318		if ($#Fields < 1) {
1319			next;
1320		}
1321		if ($Fields[1] =~ /N/) {
1322			$Flags |= $Nodi
1323		}
1324		if ($Fields[1] =~ /F/) {
1325			$Flags |= $Ssft;
1326		}
1327		if ($Fields[1] =~ /A/) {
1328			$Flags |= $Saft;
1329		}
1330
1331		# Determine the symbol name based upon the number of fields.
1332		if ($Flags && $Symbols{$Fields[$#Fields]}{$Obj}) {
1333			$Symbols{$Fields[$#Fields]}{$Obj}[$ObjFlag] |= $Flags;
1334			$Objects{$Obj}{$Fields[$#Fields]} |= $Flags;
1335		}
1336	}
1337	close($FileHandle);
1338
1339	# If this symbol has already been marked as a copy-relocation reference,
1340	# see if this symbol has any aliases, which should also be marked.
1341	if ($Copy) {
1342		foreach my $SymName (keys(%NameToAddr)) {
1343			my ($Addr) = $NameToAddr{$SymName};
1344
1345			# Determine all symbols that have the same address.
1346			foreach my $AliasName (@{$AddrToName{$Addr}}) {
1347				if ($SymName eq $AliasName) {
1348					next;
1349				}
1350				$Symbols{$AliasName}{$Obj}[$ObjFlag] |= $Cpyr;
1351				$Objects{$Obj}{$AliasName} |= $Cpyr;
1352			}
1353		}
1354	}
1355}
1356
1357# Demangle a symbol name if required.
1358sub Demangle
1359{
1360	my ($SymName) = @_;
1361	my ($DemName);
1362
1363	if ($opt{C}) {
1364		my (@Dem);
1365
1366		# Determine if we've already demangled this name.
1367		if (exists($DemSyms{$SymName})) {
1368			return $DemSyms{$SymName};
1369		}
1370
1371		@Dem = split(/\n/, `dem '$SymName'`);
1372		foreach my $Line (@Dem) {
1373			my (@Fields) = split(' ', $Line);
1374
1375			if (($#Fields < 2) || ($Fields[1] ne '==') ||
1376			    ($Fields[0] eq $Fields[2])) {
1377				next;
1378			}
1379			$DemName = $Line;
1380			$DemName =~ s/.*== (.*)$/ \[$1]/;
1381			$DemSyms{$SymName} = $DemName;
1382			return($DemName);
1383		}
1384	}
1385	$DemSyms{$SymName} = "";
1386	return("");
1387}
1388