Back to home page

OSCL-LXR

 
 

    


0001 #!/usr/bin/env perl
0002 # SPDX-License-Identifier: GPL-2.0
0003 #
0004 # Generates a linker script that specifies the correct initcall order.
0005 #
0006 # Copyright (C) 2019 Google LLC
0007 
0008 use strict;
0009 use warnings;
0010 use IO::Handle;
0011 use IO::Select;
0012 use POSIX ":sys_wait_h";
0013 
0014 my $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?";
0015 my $objtree = $ENV{'objtree'} || '.';
0016 
0017 ## currently active child processes
0018 my $jobs = {};      # child process pid -> file handle
0019 ## results from child processes
0020 my $results = {};   # object index -> [ { level, secname }, ... ]
0021 
0022 ## reads _NPROCESSORS_ONLN to determine the maximum number of processes to
0023 ## start
0024 sub get_online_processors {
0025     open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |")
0026         or die "$0: ERROR: failed to execute getconf: $!";
0027     my $procs = <$fh>;
0028     close($fh);
0029 
0030     if (!($procs =~ /^\d+$/)) {
0031         return 1;
0032     }
0033 
0034     return int($procs);
0035 }
0036 
0037 ## writes results to the parent process
0038 ## format: <file index> <initcall level> <base initcall section name>
0039 sub write_results {
0040     my ($index, $initcalls) = @_;
0041 
0042     # sort by the counter value to ensure the order of initcalls within
0043     # each object file is correct
0044     foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) {
0045         my $level = $initcalls->{$counter}->{'level'};
0046 
0047         # section name for the initcall function
0048         my $secname = $initcalls->{$counter}->{'module'} . '__' .
0049                   $counter . '_' .
0050                   $initcalls->{$counter}->{'line'} . '_' .
0051                   $initcalls->{$counter}->{'function'};
0052 
0053         print "$index $level $secname\n";
0054     }
0055 }
0056 
0057 ## reads a result line from a child process and adds it to the $results array
0058 sub read_results{
0059     my ($fh) = @_;
0060 
0061     # each child prints out a full line w/ autoflush and exits after the
0062     # last line, so even if buffered I/O blocks here, it shouldn't block
0063     # very long
0064     my $data = <$fh>;
0065 
0066     if (!defined($data)) {
0067         return 0;
0068     }
0069 
0070     chomp($data);
0071 
0072     my ($index, $level, $secname) = $data =~
0073         /^(\d+)\ ([^\ ]+)\ (.*)$/;
0074 
0075     if (!defined($index) ||
0076         !defined($level) ||
0077         !defined($secname)) {
0078         die "$0: ERROR: child process returned invalid data: $data\n";
0079     }
0080 
0081     $index = int($index);
0082 
0083     if (!exists($results->{$index})) {
0084         $results->{$index} = [];
0085     }
0086 
0087     push (@{$results->{$index}}, {
0088         'level'   => $level,
0089         'secname' => $secname
0090     });
0091 
0092     return 1;
0093 }
0094 
0095 ## finds initcalls from an object file or all object files in an archive, and
0096 ## writes results back to the parent process
0097 sub find_initcalls {
0098     my ($index, $file) = @_;
0099 
0100     die "$0: ERROR: file $file doesn't exist?" if (! -f $file);
0101 
0102     open(my $fh, "\"$nm\" --defined-only \"$file\" 2>/dev/null |")
0103         or die "$0: ERROR: failed to execute \"$nm\": $!";
0104 
0105     my $initcalls = {};
0106 
0107     while (<$fh>) {
0108         chomp;
0109 
0110         # check for the start of a new object file (if processing an
0111         # archive)
0112         my ($path)= $_ =~ /^(.+)\:$/;
0113 
0114         if (defined($path)) {
0115             write_results($index, $initcalls);
0116             $initcalls = {};
0117             next;
0118         }
0119 
0120         # look for an initcall
0121         my ($module, $counter, $line, $symbol) = $_ =~
0122             /[a-z]\s+__initcall__(\S*)__(\d+)_(\d+)_(.*)$/;
0123 
0124         if (!defined($module)) {
0125             $module = ''
0126         }
0127 
0128         if (!defined($counter) ||
0129             !defined($line) ||
0130             !defined($symbol)) {
0131             next;
0132         }
0133 
0134         # parse initcall level
0135         my ($function, $level) = $symbol =~
0136             /^(.*)((early|rootfs|con|[0-9])s?)$/;
0137 
0138         die "$0: ERROR: invalid initcall name $symbol in $file($path)"
0139             if (!defined($function) || !defined($level));
0140 
0141         $initcalls->{$counter} = {
0142             'module'   => $module,
0143             'line'     => $line,
0144             'function' => $function,
0145             'level'    => $level,
0146         };
0147     }
0148 
0149     close($fh);
0150     write_results($index, $initcalls);
0151 }
0152 
0153 ## waits for any child process to complete, reads the results, and adds them to
0154 ## the $results array for later processing
0155 sub wait_for_results {
0156     my ($select) = @_;
0157 
0158     my $pid = 0;
0159     do {
0160         # unblock children that may have a full write buffer
0161         foreach my $fh ($select->can_read(0)) {
0162             read_results($fh);
0163         }
0164 
0165         # check for children that have exited, read the remaining data
0166         # from them, and clean up
0167         $pid = waitpid(-1, WNOHANG);
0168         if ($pid > 0) {
0169             if (!exists($jobs->{$pid})) {
0170                 next;
0171             }
0172 
0173             my $fh = $jobs->{$pid};
0174             $select->remove($fh);
0175 
0176             while (read_results($fh)) {
0177                 # until eof
0178             }
0179 
0180             close($fh);
0181             delete($jobs->{$pid});
0182         }
0183     } while ($pid > 0);
0184 }
0185 
0186 ## forks a child to process each file passed in the command line and collects
0187 ## the results
0188 sub process_files {
0189     my $index = 0;
0190     my $njobs = $ENV{'PARALLELISM'} || get_online_processors();
0191     my $select = IO::Select->new();
0192 
0193     while (my $file = shift(@ARGV)) {
0194         # fork a child process and read it's stdout
0195         my $pid = open(my $fh, '-|');
0196 
0197         if (!defined($pid)) {
0198             die "$0: ERROR: failed to fork: $!";
0199         } elsif ($pid) {
0200             # save the child process pid and the file handle
0201             $select->add($fh);
0202             $jobs->{$pid} = $fh;
0203         } else {
0204             # in the child process
0205             STDOUT->autoflush(1);
0206             find_initcalls($index, "$objtree/$file");
0207             exit;
0208         }
0209 
0210         $index++;
0211 
0212         # limit the number of children to $njobs
0213         if (scalar(keys(%{$jobs})) >= $njobs) {
0214             wait_for_results($select);
0215         }
0216     }
0217 
0218     # wait for the remaining children to complete
0219     while (scalar(keys(%{$jobs})) > 0) {
0220         wait_for_results($select);
0221     }
0222 }
0223 
0224 sub generate_initcall_lds() {
0225     process_files();
0226 
0227     my $sections = {};  # level -> [ secname, ...]
0228 
0229     # sort results to retain link order and split to sections per
0230     # initcall level
0231     foreach my $index (sort { $a <=> $b } keys(%{$results})) {
0232         foreach my $result (@{$results->{$index}}) {
0233             my $level = $result->{'level'};
0234 
0235             if (!exists($sections->{$level})) {
0236                 $sections->{$level} = [];
0237             }
0238 
0239             push(@{$sections->{$level}}, $result->{'secname'});
0240         }
0241     }
0242 
0243     die "$0: ERROR: no initcalls?" if (!keys(%{$sections}));
0244 
0245     # print out a linker script that defines the order of initcalls for
0246     # each level
0247     print "SECTIONS {\n";
0248 
0249     foreach my $level (sort(keys(%{$sections}))) {
0250         my $section;
0251 
0252         if ($level eq 'con') {
0253             $section = '.con_initcall.init';
0254         } else {
0255             $section = ".initcall${level}.init";
0256         }
0257 
0258         print "\t${section} : {\n";
0259 
0260         foreach my $secname (@{$sections->{$level}}) {
0261             print "\t\t*(${section}..${secname}) ;\n";
0262         }
0263 
0264         print "\t}\n";
0265     }
0266 
0267     print "}\n";
0268 }
0269 
0270 generate_initcall_lds();