generate_initcall_order.pl 5.9 KB

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