|
@@ -0,0 +1,270 @@
|
|
|
+#!/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();
|