blob: 1a88d3f1b91339b7c832ef72919b4501415c1fed [file] [log] [blame]
Sami Tolvanena8cccdd2020-12-11 10:46:24 -08001#!/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;
11use IO::Select;
12use POSIX ":sys_wait_h";
13
14my $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?";
15my $objtree = $ENV{'objtree'} || '.';
16
17## currently active child processes
18my $jobs = {}; # child process pid -> file handle
19## results from child processes
20my $results = {}; # object index -> [ { level, secname }, ... ]
21
22## reads _NPROCESSORS_ONLN to determine the maximum number of processes to
23## start
24sub get_online_processors {
25 open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |")
26 or die "$0: ERROR: 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## writes results to the parent process
38## format: <file index> <initcall level> <base initcall section name>
39sub write_results {
40 my ($index, $initcalls) = @_;
41
42 # sort by the counter value to ensure the order of initcalls within
43 # each object file is correct
44 foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) {
45 my $level = $initcalls->{$counter}->{'level'};
46
47 # section name for the initcall function
48 my $secname = $initcalls->{$counter}->{'module'} . '__' .
49 $counter . '_' .
50 $initcalls->{$counter}->{'line'} . '_' .
51 $initcalls->{$counter}->{'function'};
52
53 print "$index $level $secname\n";
54 }
55}
56
57## reads a result line from a child process and adds it to the $results array
58sub read_results{
59 my ($fh) = @_;
60
61 # each child prints out a full line w/ autoflush and exits after the
62 # last line, so even if buffered I/O blocks here, it shouldn't block
63 # very long
64 my $data = <$fh>;
65
66 if (!defined($data)) {
67 return 0;
68 }
69
70 chomp($data);
71
72 my ($index, $level, $secname) = $data =~
73 /^(\d+)\ ([^\ ]+)\ (.*)$/;
74
75 if (!defined($index) ||
76 !defined($level) ||
77 !defined($secname)) {
78 die "$0: ERROR: child process returned invalid data: $data\n";
79 }
80
81 $index = int($index);
82
83 if (!exists($results->{$index})) {
84 $results->{$index} = [];
85 }
86
87 push (@{$results->{$index}}, {
88 'level' => $level,
89 'secname' => $secname
90 });
91
92 return 1;
93}
94
95## finds initcalls from an object file or all object files in an archive, and
96## writes results back to the parent process
97sub find_initcalls {
98 my ($index, $file) = @_;
99
100 die "$0: ERROR: file $file doesn't exist?" if (! -f $file);
101
102 open(my $fh, "\"$nm\" --defined-only \"$file\" 2>/dev/null |")
103 or die "$0: ERROR: failed to execute \"$nm\": $!";
104
105 my $initcalls = {};
106
107 while (<$fh>) {
108 chomp;
109
110 # check for the start of a new object file (if processing an
111 # archive)
112 my ($path)= $_ =~ /^(.+)\:$/;
113
114 if (defined($path)) {
115 write_results($index, $initcalls);
116 $initcalls = {};
117 next;
118 }
119
120 # look for an initcall
121 my ($module, $counter, $line, $symbol) = $_ =~
122 /[a-z]\s+__initcall__(\S*)__(\d+)_(\d+)_(.*)$/;
123
124 if (!defined($module)) {
125 $module = ''
126 }
127
128 if (!defined($counter) ||
129 !defined($line) ||
130 !defined($symbol)) {
131 next;
132 }
133
134 # parse initcall level
135 my ($function, $level) = $symbol =~
136 /^(.*)((early|rootfs|con|[0-9])s?)$/;
137
138 die "$0: ERROR: invalid initcall name $symbol in $file($path)"
139 if (!defined($function) || !defined($level));
140
141 $initcalls->{$counter} = {
142 'module' => $module,
143 'line' => $line,
144 'function' => $function,
145 'level' => $level,
146 };
147 }
148
149 close($fh);
150 write_results($index, $initcalls);
151}
152
153## waits for any child process to complete, reads the results, and adds them to
154## the $results array for later processing
155sub wait_for_results {
156 my ($select) = @_;
157
158 my $pid = 0;
159 do {
160 # unblock children that may have a full write buffer
161 foreach my $fh ($select->can_read(0)) {
162 read_results($fh);
163 }
164
165 # check for children that have exited, read the remaining data
166 # from them, and clean up
167 $pid = waitpid(-1, WNOHANG);
168 if ($pid > 0) {
169 if (!exists($jobs->{$pid})) {
170 next;
171 }
172
173 my $fh = $jobs->{$pid};
174 $select->remove($fh);
175
176 while (read_results($fh)) {
177 # until eof
178 }
179
180 close($fh);
181 delete($jobs->{$pid});
182 }
183 } while ($pid > 0);
184}
185
186## forks a child to process each file passed in the command line and collects
187## the results
188sub process_files {
189 my $index = 0;
190 my $njobs = $ENV{'PARALLELISM'} || get_online_processors();
191 my $select = IO::Select->new();
192
193 while (my $file = shift(@ARGV)) {
194 # fork a child process and read it's stdout
195 my $pid = open(my $fh, '-|');
196
197 if (!defined($pid)) {
198 die "$0: ERROR: failed to fork: $!";
199 } elsif ($pid) {
200 # save the child process pid and the file handle
201 $select->add($fh);
202 $jobs->{$pid} = $fh;
203 } else {
204 # in the child process
205 STDOUT->autoflush(1);
206 find_initcalls($index, "$objtree/$file");
207 exit;
208 }
209
210 $index++;
211
212 # limit the number of children to $njobs
213 if (scalar(keys(%{$jobs})) >= $njobs) {
214 wait_for_results($select);
215 }
216 }
217
218 # wait for the remaining children to complete
219 while (scalar(keys(%{$jobs})) > 0) {
220 wait_for_results($select);
221 }
222}
223
224sub generate_initcall_lds() {
225 process_files();
226
227 my $sections = {}; # level -> [ secname, ...]
228
229 # sort results to retain link order and split to sections per
230 # initcall level
231 foreach my $index (sort { $a <=> $b } keys(%{$results})) {
232 foreach my $result (@{$results->{$index}}) {
233 my $level = $result->{'level'};
234
235 if (!exists($sections->{$level})) {
236 $sections->{$level} = [];
237 }
238
239 push(@{$sections->{$level}}, $result->{'secname'});
240 }
241 }
242
243 die "$0: ERROR: no initcalls?" if (!keys(%{$sections}));
244
245 # print out a linker script that defines the order of initcalls for
246 # each level
247 print "SECTIONS {\n";
248
249 foreach my $level (sort(keys(%{$sections}))) {
250 my $section;
251
252 if ($level eq 'con') {
253 $section = '.con_initcall.init';
254 } else {
255 $section = ".initcall${level}.init";
256 }
257
258 print "\t${section} : {\n";
259
260 foreach my $secname (@{$sections->{$level}}) {
261 print "\t\t*(${section}..${secname}) ;\n";
262 }
263
264 print "\t}\n";
265 }
266
267 print "}\n";
268}
269
270generate_initcall_lds();