271 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			271 lines
		
	
	
		
			5.9 KiB
		
	
	
	
		
			Perl
		
	
	
		
			Executable File
		
	
	
	
	
| #!/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;
 | |
| use IO::Select;
 | |
| use POSIX ":sys_wait_h";
 | |
| 
 | |
| my $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?";
 | |
| my $objtree = $ENV{'objtree'} || '.';
 | |
| 
 | |
| ## currently active child processes
 | |
| my $jobs = {};		# child process pid -> file handle
 | |
| ## results from child processes
 | |
| my $results = {};	# object index -> [ { level, secname }, ... ]
 | |
| 
 | |
| ## reads _NPROCESSORS_ONLN to determine the maximum number of processes to
 | |
| ## start
 | |
| sub get_online_processors {
 | |
| 	open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |")
 | |
| 		or die "$0: ERROR: failed to execute getconf: $!";
 | |
| 	my $procs = <$fh>;
 | |
| 	close($fh);
 | |
| 
 | |
| 	if (!($procs =~ /^\d+$/)) {
 | |
| 		return 1;
 | |
| 	}
 | |
| 
 | |
| 	return int($procs);
 | |
| }
 | |
| 
 | |
| ## writes results to the parent process
 | |
| ## format: <file index> <initcall level> <base initcall section name>
 | |
| sub write_results {
 | |
| 	my ($index, $initcalls) = @_;
 | |
| 
 | |
| 	# sort by the counter value to ensure the order of initcalls within
 | |
| 	# each object file is correct
 | |
| 	foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) {
 | |
| 		my $level = $initcalls->{$counter}->{'level'};
 | |
| 
 | |
| 		# section name for the initcall function
 | |
| 		my $secname = $initcalls->{$counter}->{'module'} . '__' .
 | |
| 			      $counter . '_' .
 | |
| 			      $initcalls->{$counter}->{'line'} . '_' .
 | |
| 			      $initcalls->{$counter}->{'function'};
 | |
| 
 | |
| 		print "$index $level $secname\n";
 | |
| 	}
 | |
| }
 | |
| 
 | |
| ## reads a result line from a child process and adds it to the $results array
 | |
| sub read_results{
 | |
| 	my ($fh) = @_;
 | |
| 
 | |
| 	# each child prints out a full line w/ autoflush and exits after the
 | |
| 	# last line, so even if buffered I/O blocks here, it shouldn't block
 | |
| 	# very long
 | |
| 	my $data = <$fh>;
 | |
| 
 | |
| 	if (!defined($data)) {
 | |
| 		return 0;
 | |
| 	}
 | |
| 
 | |
| 	chomp($data);
 | |
| 
 | |
| 	my ($index, $level, $secname) = $data =~
 | |
| 		/^(\d+)\ ([^\ ]+)\ (.*)$/;
 | |
| 
 | |
| 	if (!defined($index) ||
 | |
| 		!defined($level) ||
 | |
| 		!defined($secname)) {
 | |
| 		die "$0: ERROR: child process returned invalid data: $data\n";
 | |
| 	}
 | |
| 
 | |
| 	$index = int($index);
 | |
| 
 | |
| 	if (!exists($results->{$index})) {
 | |
| 		$results->{$index} = [];
 | |
| 	}
 | |
| 
 | |
| 	push (@{$results->{$index}}, {
 | |
| 		'level'   => $level,
 | |
| 		'secname' => $secname
 | |
| 	});
 | |
| 
 | |
| 	return 1;
 | |
| }
 | |
| 
 | |
| ## finds initcalls from an object file or all object files in an archive, and
 | |
| ## writes results back to the parent process
 | |
| sub find_initcalls {
 | |
| 	my ($index, $file) = @_;
 | |
| 
 | |
| 	die "$0: ERROR: file $file doesn't exist?" if (! -f $file);
 | |
| 
 | |
| 	open(my $fh, "\"$nm\" --defined-only \"$file\" 2>/dev/null |")
 | |
| 		or die "$0: ERROR: failed to execute \"$nm\": $!";
 | |
| 
 | |
| 	my $initcalls = {};
 | |
| 
 | |
| 	while (<$fh>) {
 | |
| 		chomp;
 | |
| 
 | |
| 		# check for the start of a new object file (if processing an
 | |
| 		# archive)
 | |
| 		my ($path)= $_ =~ /^(.+)\:$/;
 | |
| 
 | |
| 		if (defined($path)) {
 | |
| 			write_results($index, $initcalls);
 | |
| 			$initcalls = {};
 | |
| 			next;
 | |
| 		}
 | |
| 
 | |
| 		# look for an initcall
 | |
| 		my ($module, $counter, $line, $symbol) = $_ =~
 | |
| 			/[a-z]\s+__initcall__(\S*)__(\d+)_(\d+)_(.*)$/;
 | |
| 
 | |
| 		if (!defined($module)) {
 | |
| 			$module = ''
 | |
| 		}
 | |
| 
 | |
| 		if (!defined($counter) ||
 | |
| 			!defined($line) ||
 | |
| 			!defined($symbol)) {
 | |
| 			next;
 | |
| 		}
 | |
| 
 | |
| 		# parse initcall level
 | |
| 		my ($function, $level) = $symbol =~
 | |
| 			/^(.*)((early|rootfs|con|[0-9])s?)$/;
 | |
| 
 | |
| 		die "$0: ERROR: invalid initcall name $symbol in $file($path)"
 | |
| 			if (!defined($function) || !defined($level));
 | |
| 
 | |
| 		$initcalls->{$counter} = {
 | |
| 			'module'   => $module,
 | |
| 			'line'     => $line,
 | |
| 			'function' => $function,
 | |
| 			'level'    => $level,
 | |
| 		};
 | |
| 	}
 | |
| 
 | |
| 	close($fh);
 | |
| 	write_results($index, $initcalls);
 | |
| }
 | |
| 
 | |
| ## 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 ($select) = @_;
 | |
| 
 | |
| 	my $pid = 0;
 | |
| 	do {
 | |
| 		# unblock children that may have a full write buffer
 | |
| 		foreach my $fh ($select->can_read(0)) {
 | |
| 			read_results($fh);
 | |
| 		}
 | |
| 
 | |
| 		# check for children that have exited, read the remaining data
 | |
| 		# from them, and clean up
 | |
| 		$pid = waitpid(-1, WNOHANG);
 | |
| 		if ($pid > 0) {
 | |
| 			if (!exists($jobs->{$pid})) {
 | |
| 				next;
 | |
| 			}
 | |
| 
 | |
| 			my $fh = $jobs->{$pid};
 | |
| 			$select->remove($fh);
 | |
| 
 | |
| 			while (read_results($fh)) {
 | |
| 				# until eof
 | |
| 			}
 | |
| 
 | |
| 			close($fh);
 | |
| 			delete($jobs->{$pid});
 | |
| 		}
 | |
| 	} while ($pid > 0);
 | |
| }
 | |
| 
 | |
| ## forks a child to process each file passed in the command line and collects
 | |
| ## the results
 | |
| sub process_files {
 | |
| 	my $index = 0;
 | |
| 	my $njobs = $ENV{'PARALLELISM'} || get_online_processors();
 | |
| 	my $select = IO::Select->new();
 | |
| 
 | |
| 	while (my $file = shift(@ARGV)) {
 | |
| 		# fork a child process and read it's stdout
 | |
| 		my $pid = open(my $fh, '-|');
 | |
| 
 | |
| 		if (!defined($pid)) {
 | |
| 			die "$0: ERROR: failed to fork: $!";
 | |
| 		} elsif ($pid) {
 | |
| 			# save the child process pid and the file handle
 | |
| 			$select->add($fh);
 | |
| 			$jobs->{$pid} = $fh;
 | |
| 		} else {
 | |
| 			# in the child process
 | |
| 			STDOUT->autoflush(1);
 | |
| 			find_initcalls($index, "$objtree/$file");
 | |
| 			exit;
 | |
| 		}
 | |
| 
 | |
| 		$index++;
 | |
| 
 | |
| 		# limit the number of children to $njobs
 | |
| 		if (scalar(keys(%{$jobs})) >= $njobs) {
 | |
| 			wait_for_results($select);
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	# wait for the remaining children to complete
 | |
| 	while (scalar(keys(%{$jobs})) > 0) {
 | |
| 		wait_for_results($select);
 | |
| 	}
 | |
| }
 | |
| 
 | |
| sub generate_initcall_lds() {
 | |
| 	process_files();
 | |
| 
 | |
| 	my $sections = {};	# level -> [ secname, ...]
 | |
| 
 | |
| 	# sort results to retain link order and split to sections per
 | |
| 	# initcall level
 | |
| 	foreach my $index (sort { $a <=> $b } keys(%{$results})) {
 | |
| 		foreach my $result (@{$results->{$index}}) {
 | |
| 			my $level = $result->{'level'};
 | |
| 
 | |
| 			if (!exists($sections->{$level})) {
 | |
| 				$sections->{$level} = [];
 | |
| 			}
 | |
| 
 | |
| 			push(@{$sections->{$level}}, $result->{'secname'});
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	die "$0: ERROR: no initcalls?" if (!keys(%{$sections}));
 | |
| 
 | |
| 	# 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';
 | |
| 		} else {
 | |
| 			$section = ".initcall${level}.init";
 | |
| 		}
 | |
| 
 | |
| 		print "\t${section} : {\n";
 | |
| 
 | |
| 		foreach my $secname (@{$sections->{$level}}) {
 | |
| 			print "\t\t*(${section}..${secname}) ;\n";
 | |
| 		}
 | |
| 
 | |
| 		print "\t}\n";
 | |
| 	}
 | |
| 
 | |
| 	print "}\n";
 | |
| }
 | |
| 
 | |
| generate_initcall_lds();
 |