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