linux/scripts/generate_initcall_order.pl
<<
>>
Prefs
   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;
  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();
 271