• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env perl
2# SPDX-License-Identifier: GPL-2.0
3#
4# Generates a linker script that specifies the correct initcall order.
5#
6# Copyright (C) 2019 Google LLC
7
8use strict;
9use warnings;
10use IO::Handle;
11
12my $nm = $ENV{'LLVM_NM'} || "llvm-nm";
13my $ar = $ENV{'AR'}	 || "llvm-ar";
14my $objtree = $ENV{'objtree'} || ".";
15
16## list of all object files to process, in link order
17my @objects;
18## currently active child processes
19my $jobs = {};		# child process pid -> file handle
20## results from child processes
21my $results = {};	# object index -> { level, function }
22
23## reads _NPROCESSORS_ONLN to determine the number of processes to start
24sub get_online_processors {
25	open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |")
26		or die "$0: failed to execute getconf: $!";
27	my $procs = <$fh>;
28	close($fh);
29
30	if (!($procs =~ /^\d+$/)) {
31		return 1;
32	}
33
34	return int($procs);
35}
36
37## finds initcalls defined in an object file, parses level and function name,
38## and prints it out to the parent process
39sub find_initcalls {
40	my ($object) = @_;
41
42	die "$0: object file $object doesn't exist?" if (! -f $object);
43
44	open(my $fh, "\"$nm\" -just-symbol-name -defined-only \"$object\" 2>/dev/null |")
45		or die "$0: failed to execute \"$nm\": $!";
46
47	my $initcalls = {};
48
49	while (<$fh>) {
50		chomp;
51
52		my ($counter, $line, $symbol) = $_ =~ /^__initcall_(\d+)_(\d+)_(.*)$/;
53
54		if (!defined($counter) || !defined($line) || !defined($symbol)) {
55			next;
56		}
57
58		my ($function, $level) = $symbol =~
59			/^(.*)((early|rootfs|con|security|[0-9])s?)$/;
60
61		die "$0: duplicate initcall counter value in object $object: $_"
62			if exists($initcalls->{$counter});
63
64		$initcalls->{$counter} = {
65			'level'    => $level,
66			'line'     => $line,
67			'function' => $function
68		};
69	}
70
71	close($fh);
72
73	# sort initcalls in each object file numerically by the counter value
74	# to ensure they are in the order they were defined
75	foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) {
76		print $initcalls->{$counter}->{"level"} . " " .
77		      $counter . " " .
78		      $initcalls->{$counter}->{"line"} . " " .
79		      $initcalls->{$counter}->{"function"} . "\n";
80	}
81}
82
83## waits for any child process to complete, reads the results, and adds them to
84## the $results array for later processing
85sub wait_for_results {
86	my $pid = wait();
87	if ($pid > 0) {
88		my $fh = $jobs->{$pid};
89
90		# the child process prints out results in the following format:
91		#  line 1:    <object file index>
92		#  line 2..n: <level> <counter> <line> <function>
93
94		my $index = <$fh>;
95		chomp($index);
96
97		if (!($index =~ /^\d+$/)) {
98			die "$0: child $pid returned an invalid index: $index";
99		}
100		$index = int($index);
101
102		while (<$fh>) {
103			chomp;
104			my ($level, $counter, $line, $function) = $_ =~
105				/^([^\ ]+)\ (\d+)\ (\d+)\ (.*)$/;
106
107			if (!defined($level) ||
108				!defined($counter) ||
109				!defined($line) ||
110				!defined($function)) {
111				die "$0: child $pid returned invalid data";
112			}
113
114			if (!exists($results->{$index})) {
115				$results->{$index} = [];
116			}
117
118			push (@{$results->{$index}}, {
119				'level'    => $level,
120				'counter'  => $counter,
121				'line'     => $line,
122				'function' => $function
123			});
124		}
125
126		close($fh);
127		delete($jobs->{$pid});
128	}
129}
130
131## launches child processes to find initcalls from the object files, waits for
132## each process to complete and collects the results
133sub process_objects {
134	my $index = 0;	# link order index of the object file
135	my $njobs = get_online_processors();
136
137	while (scalar(@objects) > 0) {
138		my $object = shift(@objects);
139
140		# fork a child process and read it's stdout
141		my $pid = open(my $fh, '-|');
142
143		if (!defined($pid)) {
144			die "$0: failed to fork: $!";
145		} elsif ($pid) {
146			# save the child process pid and the file handle
147			$jobs->{$pid} = $fh;
148		} else {
149			STDOUT->autoflush(1);
150			print "$index\n";
151			find_initcalls("$objtree/$object");
152			exit;
153		}
154
155		$index++;
156
157		# if we reached the maximum number of processes, wait for one
158		# to complete before launching new ones
159		if (scalar(keys(%{$jobs})) >= $njobs && scalar(@objects) > 0) {
160			wait_for_results();
161		}
162	}
163
164	# wait for the remaining children to complete
165	while (scalar(keys(%{$jobs})) > 0) {
166		wait_for_results();
167	}
168}
169
170## gets a list of actual object files from thin archives, and adds them to
171## @objects in link order
172sub find_objects {
173	while (my $file = shift(@ARGV)) {
174		my $pid = open (my $fh, "\"$ar\" t \"$file\" 2>/dev/null |")
175			or die "$0: failed to execute $ar: $!";
176
177		my @output;
178
179		while (<$fh>) {
180			chomp;
181			push(@output, $_);
182		}
183
184		close($fh);
185
186		# if $ar failed, assume we have an object file
187		if ($? != 0) {
188			push(@objects, $file);
189			next;
190		}
191
192		# if $ar succeeded, read the list of object files
193		foreach (@output) {
194			push(@objects, $_);
195		}
196	}
197}
198
199## START
200find_objects();
201process_objects();
202
203## process results and add them to $sections in the correct order
204my $sections = {};
205
206foreach my $index (sort { $a <=> $b } keys(%{$results})) {
207	foreach my $result (@{$results->{$index}}) {
208		my $level = $result->{'level'};
209
210		if (!exists($sections->{$level})) {
211			$sections->{$level} = [];
212		}
213
214		my $fsname = $result->{'counter'} . '_' .
215			     $result->{'line'}    . '_' .
216			     $result->{'function'};
217
218		push(@{$sections->{$level}}, $fsname);
219	}
220}
221
222if (!keys(%{$sections})) {
223	exit(0); # no initcalls...?
224}
225
226## print out a linker script that defines the order of initcalls for each
227## level
228print "SECTIONS {\n";
229
230foreach my $level (sort(keys(%{$sections}))) {
231	my $section;
232
233	if ($level eq 'con') {
234		$section = '.con_initcall.init';
235	} elsif ($level eq 'security') {
236		$section = '.security_initcall.init';
237	} else {
238		$section = ".initcall${level}.init";
239	}
240
241	print "\t${section} : {\n";
242
243	foreach my $fsname (@{$sections->{$level}}) {
244		print "\t\t*(${section}..${fsname}) ;\n"
245	}
246
247	print "\t}\n";
248}
249
250print "}\n";
251