summaryrefslogtreecommitdiff
path: root/scripts/generate_initcall_order.pl
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/generate_initcall_order.pl')
-rwxr-xr-xscripts/generate_initcall_order.pl250
1 files changed, 250 insertions, 0 deletions
diff --git a/scripts/generate_initcall_order.pl b/scripts/generate_initcall_order.pl
new file mode 100755
index 000000000000..f772b4a01caa
--- /dev/null
+++ b/scripts/generate_initcall_order.pl
@@ -0,0 +1,250 @@
+#!/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;
+
+my $nm = $ENV{'LLVM_NM'} || "llvm-nm";
+my $ar = $ENV{'AR'} || "llvm-ar";
+my $objtree = $ENV{'objtree'} || ".";
+
+## list of all object files to process, in link order
+my @objects;
+## currently active child processes
+my $jobs = {}; # child process pid -> file handle
+## results from child processes
+my $results = {}; # object index -> { level, function }
+
+## reads _NPROCESSORS_ONLN to determine the number of processes to start
+sub get_online_processors {
+ open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |")
+ or die "$0: failed to execute getconf: $!";
+ my $procs = <$fh>;
+ close($fh);
+
+ if (!($procs =~ /^\d+$/)) {
+ return 1;
+ }
+
+ return int($procs);
+}
+
+## finds initcalls defined in an object file, parses level and function name,
+## and prints it out to the parent process
+sub find_initcalls {
+ my ($object) = @_;
+
+ die "$0: object file $object doesn't exist?" if (! -f $object);
+
+ open(my $fh, "\"$nm\" -just-symbol-name -defined-only \"$object\" 2>/dev/null |")
+ or die "$0: failed to execute \"$nm\": $!";
+
+ my $initcalls = {};
+
+ while (<$fh>) {
+ chomp;
+
+ my ($counter, $line, $symbol) = $_ =~ /^__initcall_(\d+)_(\d+)_(.*)$/;
+
+ if (!defined($counter) || !defined($line) || !defined($symbol)) {
+ next;
+ }
+
+ my ($function, $level) = $symbol =~
+ /^(.*)((early|rootfs|con|security|[0-9])s?)$/;
+
+ die "$0: duplicate initcall counter value in object $object: $_"
+ if exists($initcalls->{$counter});
+
+ $initcalls->{$counter} = {
+ 'level' => $level,
+ 'line' => $line,
+ 'function' => $function
+ };
+ }
+
+ close($fh);
+
+ # sort initcalls in each object file numerically by the counter value
+ # to ensure they are in the order they were defined
+ foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) {
+ print $initcalls->{$counter}->{"level"} . " " .
+ $counter . " " .
+ $initcalls->{$counter}->{"line"} . " " .
+ $initcalls->{$counter}->{"function"} . "\n";
+ }
+}
+
+## 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 $pid = wait();
+ if ($pid > 0) {
+ my $fh = $jobs->{$pid};
+
+ # the child process prints out results in the following format:
+ # line 1: <object file index>
+ # line 2..n: <level> <counter> <line> <function>
+
+ my $index = <$fh>;
+ chomp($index);
+
+ if (!($index =~ /^\d+$/)) {
+ die "$0: child $pid returned an invalid index: $index";
+ }
+ $index = int($index);
+
+ while (<$fh>) {
+ chomp;
+ my ($level, $counter, $line, $function) = $_ =~
+ /^([^\ ]+)\ (\d+)\ (\d+)\ (.*)$/;
+
+ if (!defined($level) ||
+ !defined($counter) ||
+ !defined($line) ||
+ !defined($function)) {
+ die "$0: child $pid returned invalid data";
+ }
+
+ if (!exists($results->{$index})) {
+ $results->{$index} = [];
+ }
+
+ push (@{$results->{$index}}, {
+ 'level' => $level,
+ 'counter' => $counter,
+ 'line' => $line,
+ 'function' => $function
+ });
+ }
+
+ close($fh);
+ delete($jobs->{$pid});
+ }
+}
+
+## launches child processes to find initcalls from the object files, waits for
+## each process to complete and collects the results
+sub process_objects {
+ my $index = 0; # link order index of the object file
+ my $njobs = get_online_processors();
+
+ while (scalar(@objects) > 0) {
+ my $object = shift(@objects);
+
+ # fork a child process and read it's stdout
+ my $pid = open(my $fh, '-|');
+
+ if (!defined($pid)) {
+ die "$0: failed to fork: $!";
+ } elsif ($pid) {
+ # save the child process pid and the file handle
+ $jobs->{$pid} = $fh;
+ } else {
+ STDOUT->autoflush(1);
+ print "$index\n";
+ find_initcalls("$objtree/$object");
+ exit;
+ }
+
+ $index++;
+
+ # if we reached the maximum number of processes, wait for one
+ # to complete before launching new ones
+ if (scalar(keys(%{$jobs})) >= $njobs && scalar(@objects) > 0) {
+ wait_for_results();
+ }
+ }
+
+ # wait for the remaining children to complete
+ while (scalar(keys(%{$jobs})) > 0) {
+ wait_for_results();
+ }
+}
+
+## gets a list of actual object files from thin archives, and adds them to
+## @objects in link order
+sub find_objects {
+ while (my $file = shift(@ARGV)) {
+ my $pid = open (my $fh, "\"$ar\" t \"$file\" 2>/dev/null |")
+ or die "$0: failed to execute $ar: $!";
+
+ my @output;
+
+ while (<$fh>) {
+ chomp;
+ push(@output, $_);
+ }
+
+ close($fh);
+
+ # if $ar failed, assume we have an object file
+ if ($? != 0) {
+ push(@objects, $file);
+ next;
+ }
+
+ # if $ar succeeded, read the list of object files
+ foreach (@output) {
+ push(@objects, $_);
+ }
+ }
+}
+
+## START
+find_objects();
+process_objects();
+
+## process results and add them to $sections in the correct order
+my $sections = {};
+
+foreach my $index (sort { $a <=> $b } keys(%{$results})) {
+ foreach my $result (@{$results->{$index}}) {
+ my $level = $result->{'level'};
+
+ if (!exists($sections->{$level})) {
+ $sections->{$level} = [];
+ }
+
+ my $fsname = $result->{'counter'} . '_' .
+ $result->{'line'} . '_' .
+ $result->{'function'};
+
+ push(@{$sections->{$level}}, $fsname);
+ }
+}
+
+if (!keys(%{$sections})) {
+ exit(0); # no initcalls...?
+}
+
+## 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';
+ } elsif ($level eq 'security') {
+ $section = '.security_initcall.init';
+ } else {
+ $section = ".initcall${level}.init";
+ }
+
+ print "\t${section} : {\n";
+
+ foreach my $fsname (@{$sections->{$level}}) {
+ print "\t\t*(${section}..${fsname}) ;\n"
+ }
+
+ print "\t}\n";
+}
+
+print "}\n";