| #!/usr/bin/env perl | 
 | # SPDX-License-Identifier: GPL-2.0 | 
 | # | 
 | # Generates a linker script that specifies the correct initcall order. | 
 | # | 
 | # Copyright (C) 2019 Google LLC | 
 |  | 
 | use strict; | 
 | use warnings; | 
 | use IO::Handle; | 
 |  | 
 | my $nm = $ENV{'NM'} || "llvm-nm"; | 
 | my $ar = $ENV{'AR'}	 || "llvm-ar"; | 
 | my $objtree = $ENV{'objtree'} || "."; | 
 |  | 
 | ## list of all object files to process, in link order | 
 | my @objects; | 
 | ## currently active child processes | 
 | my $jobs = {};		# child process pid -> file handle | 
 | ## results from child processes | 
 | my $results = {};	# object index -> { level, function } | 
 |  | 
 | ## reads _NPROCESSORS_ONLN to determine the number of processes to start | 
 | sub get_online_processors { | 
 | 	open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |") | 
 | 		or die "$0: failed to execute getconf: $!"; | 
 | 	my $procs = <$fh>; | 
 | 	close($fh); | 
 |  | 
 | 	if (!($procs =~ /^\d+$/)) { | 
 | 		return 1; | 
 | 	} | 
 |  | 
 | 	return int($procs); | 
 | } | 
 |  | 
 | ## finds initcalls defined in an object file, parses level and function name, | 
 | ## and prints it out to the parent process | 
 | sub find_initcalls { | 
 | 	my ($object) = @_; | 
 |  | 
 | 	die "$0: object file $object doesn't exist?" if (! -f $object); | 
 |  | 
 | 	open(my $fh, "\"$nm\" --just-symbol-name --defined-only \"$object\" 2>/dev/null |") | 
 | 		or die "$0: failed to execute \"$nm\": $!"; | 
 |  | 
 | 	my $initcalls = {}; | 
 |  | 
 | 	while (<$fh>) { | 
 | 		chomp; | 
 |  | 
 | 		my ($counter, $line, $symbol) = $_ =~ /^__initcall_(\d+)_(\d+)_(.*)$/; | 
 |  | 
 | 		if (!defined($counter) || !defined($line) || !defined($symbol)) { | 
 | 			next; | 
 | 		} | 
 |  | 
 | 		my ($function, $level) = $symbol =~ | 
 | 			/^(.*)((early|rootfs|con|security|[0-9])s?)$/; | 
 |  | 
 | 		die "$0: duplicate initcall counter value in object $object: $_" | 
 | 			if exists($initcalls->{$counter}); | 
 |  | 
 | 		$initcalls->{$counter} = { | 
 | 			'level'    => $level, | 
 | 			'line'     => $line, | 
 | 			'function' => $function | 
 | 		}; | 
 | 	} | 
 |  | 
 | 	close($fh); | 
 |  | 
 | 	# sort initcalls in each object file numerically by the counter value | 
 | 	# to ensure they are in the order they were defined | 
 | 	foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) { | 
 | 		print $initcalls->{$counter}->{"level"} . " " . | 
 | 		      $counter . " " . | 
 | 		      $initcalls->{$counter}->{"line"} . " " . | 
 | 		      $initcalls->{$counter}->{"function"} . "\n"; | 
 | 	} | 
 | } | 
 |  | 
 | ## waits for any child process to complete, reads the results, and adds them to | 
 | ## the $results array for later processing | 
 | sub wait_for_results { | 
 | 	my $pid = wait(); | 
 | 	if ($pid > 0) { | 
 | 		my $fh = $jobs->{$pid}; | 
 |  | 
 | 		# the child process prints out results in the following format: | 
 | 		#  line 1:    <object file index> | 
 | 		#  line 2..n: <level> <counter> <line> <function> | 
 |  | 
 | 		my $index = <$fh>; | 
 | 		chomp($index); | 
 |  | 
 | 		if (!($index =~ /^\d+$/)) { | 
 | 			die "$0: child $pid returned an invalid index: $index"; | 
 | 		} | 
 | 		$index = int($index); | 
 |  | 
 | 		while (<$fh>) { | 
 | 			chomp; | 
 | 			my ($level, $counter, $line, $function) = $_ =~ | 
 | 				/^([^\ ]+)\ (\d+)\ (\d+)\ (.*)$/; | 
 |  | 
 | 			if (!defined($level) || | 
 | 				!defined($counter) || | 
 | 				!defined($line) || | 
 | 				!defined($function)) { | 
 | 				die "$0: child $pid returned invalid data"; | 
 | 			} | 
 |  | 
 | 			if (!exists($results->{$index})) { | 
 | 				$results->{$index} = []; | 
 | 			} | 
 |  | 
 | 			push (@{$results->{$index}}, { | 
 | 				'level'    => $level, | 
 | 				'counter'  => $counter, | 
 | 				'line'     => $line, | 
 | 				'function' => $function | 
 | 			}); | 
 | 		} | 
 |  | 
 | 		close($fh); | 
 | 		delete($jobs->{$pid}); | 
 | 	} | 
 | } | 
 |  | 
 | ## launches child processes to find initcalls from the object files, waits for | 
 | ## each process to complete and collects the results | 
 | sub process_objects { | 
 | 	my $index = 0;	# link order index of the object file | 
 | 	my $njobs = get_online_processors(); | 
 |  | 
 | 	while (scalar(@objects) > 0) { | 
 | 		my $object = shift(@objects); | 
 |  | 
 | 		# fork a child process and read it's stdout | 
 | 		my $pid = open(my $fh, '-|'); | 
 |  | 
 | 		if (!defined($pid)) { | 
 | 			die "$0: failed to fork: $!"; | 
 | 		} elsif ($pid) { | 
 | 			# save the child process pid and the file handle | 
 | 			$jobs->{$pid} = $fh; | 
 | 		} else { | 
 | 			STDOUT->autoflush(1); | 
 | 			print "$index\n"; | 
 | 			find_initcalls("$objtree/$object"); | 
 | 			exit; | 
 | 		} | 
 |  | 
 | 		$index++; | 
 |  | 
 | 		# if we reached the maximum number of processes, wait for one | 
 | 		# to complete before launching new ones | 
 | 		if (scalar(keys(%{$jobs})) >= $njobs && scalar(@objects) > 0) { | 
 | 			wait_for_results(); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	# wait for the remaining children to complete | 
 | 	while (scalar(keys(%{$jobs})) > 0) { | 
 | 		wait_for_results(); | 
 | 	} | 
 | } | 
 |  | 
 | ## gets a list of actual object files from thin archives, and adds them to | 
 | ## @objects in link order | 
 | sub find_objects { | 
 | 	while (my $file = shift(@ARGV)) { | 
 | 		my $pid = open (my $fh, "\"$ar\" t \"$file\" 2>/dev/null |") | 
 | 			or die "$0: failed to execute $ar: $!"; | 
 |  | 
 | 		my @output; | 
 |  | 
 | 		while (<$fh>) { | 
 | 			chomp; | 
 | 			push(@output, $_); | 
 | 		} | 
 |  | 
 | 		close($fh); | 
 |  | 
 | 		# if $ar failed, assume we have an object file | 
 | 		if ($? != 0) { | 
 | 			push(@objects, $file); | 
 | 			next; | 
 | 		} | 
 |  | 
 | 		# if $ar succeeded, read the list of object files | 
 | 		foreach (@output) { | 
 | 			push(@objects, $_); | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | ## START | 
 | find_objects(); | 
 | process_objects(); | 
 |  | 
 | ## process results and add them to $sections in the correct order | 
 | my $sections = {}; | 
 |  | 
 | foreach my $index (sort { $a <=> $b } keys(%{$results})) { | 
 | 	foreach my $result (@{$results->{$index}}) { | 
 | 		my $level = $result->{'level'}; | 
 |  | 
 | 		if (!exists($sections->{$level})) { | 
 | 			$sections->{$level} = []; | 
 | 		} | 
 |  | 
 | 		my $fsname = $result->{'counter'} . '_' . | 
 | 			     $result->{'line'}    . '_' . | 
 | 			     $result->{'function'}; | 
 |  | 
 | 		push(@{$sections->{$level}}, $fsname); | 
 | 	} | 
 | } | 
 |  | 
 | if (!keys(%{$sections})) { | 
 | 	exit(0); # no initcalls...? | 
 | } | 
 |  | 
 | ## print out a linker script that defines the order of initcalls for each | 
 | ## level | 
 | print "SECTIONS {\n"; | 
 |  | 
 | foreach my $level (sort(keys(%{$sections}))) { | 
 | 	my $section; | 
 |  | 
 | 	if ($level eq 'con') { | 
 | 		$section = '.con_initcall.init'; | 
 | 	} elsif ($level eq 'security') { | 
 | 		$section = '.security_initcall.init'; | 
 | 	} else { | 
 | 		$section = ".initcall${level}.init"; | 
 | 	} | 
 |  | 
 | 	print "\t${section} : {\n"; | 
 |  | 
 | 	foreach my $fsname (@{$sections->{$level}}) { | 
 | 		print "\t\t*(${section}..${fsname}) ;\n" | 
 | 	} | 
 |  | 
 | 	print "\t}\n"; | 
 | } | 
 |  | 
 | print "}\n"; |