18c2ecf20Sopenharmony_ci#!/usr/bin/env perl 28c2ecf20Sopenharmony_ci# SPDX-License-Identifier: GPL-2.0 38c2ecf20Sopenharmony_ci# 48c2ecf20Sopenharmony_ci# Generates a linker script that specifies the correct initcall order. 58c2ecf20Sopenharmony_ci# 68c2ecf20Sopenharmony_ci# Copyright (C) 2019 Google LLC 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ciuse strict; 98c2ecf20Sopenharmony_ciuse warnings; 108c2ecf20Sopenharmony_ciuse IO::Handle; 118c2ecf20Sopenharmony_ciuse IO::Select; 128c2ecf20Sopenharmony_ciuse POSIX ":sys_wait_h"; 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_cimy $nm = $ENV{'NM'} || die "$0: ERROR: NM not set?"; 158c2ecf20Sopenharmony_cimy $objtree = $ENV{'objtree'} || '.'; 168c2ecf20Sopenharmony_ci 178c2ecf20Sopenharmony_ci## currently active child processes 188c2ecf20Sopenharmony_cimy $jobs = {}; # child process pid -> file handle 198c2ecf20Sopenharmony_ci## results from child processes 208c2ecf20Sopenharmony_cimy $results = {}; # object index -> [ { level, secname }, ... ] 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_ci## reads _NPROCESSORS_ONLN to determine the maximum number of processes to 238c2ecf20Sopenharmony_ci## start 248c2ecf20Sopenharmony_cisub get_online_processors { 258c2ecf20Sopenharmony_ci open(my $fh, "getconf _NPROCESSORS_ONLN 2>/dev/null |") 268c2ecf20Sopenharmony_ci or die "$0: ERROR: failed to execute getconf: $!"; 278c2ecf20Sopenharmony_ci my $procs = <$fh>; 288c2ecf20Sopenharmony_ci close($fh); 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci if (!($procs =~ /^\d+$/)) { 318c2ecf20Sopenharmony_ci return 1; 328c2ecf20Sopenharmony_ci } 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci return int($procs); 358c2ecf20Sopenharmony_ci} 368c2ecf20Sopenharmony_ci 378c2ecf20Sopenharmony_ci## writes results to the parent process 388c2ecf20Sopenharmony_ci## format: <file index> <initcall level> <base initcall section name> 398c2ecf20Sopenharmony_cisub write_results { 408c2ecf20Sopenharmony_ci my ($index, $initcalls) = @_; 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci # sort by the counter value to ensure the order of initcalls within 438c2ecf20Sopenharmony_ci # each object file is correct 448c2ecf20Sopenharmony_ci foreach my $counter (sort { $a <=> $b } keys(%{$initcalls})) { 458c2ecf20Sopenharmony_ci my $level = $initcalls->{$counter}->{'level'}; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci # section name for the initcall function 488c2ecf20Sopenharmony_ci my $secname = $initcalls->{$counter}->{'module'} . '__' . 498c2ecf20Sopenharmony_ci $counter . '_' . 508c2ecf20Sopenharmony_ci $initcalls->{$counter}->{'line'} . '_' . 518c2ecf20Sopenharmony_ci $initcalls->{$counter}->{'function'}; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci print "$index $level $secname\n"; 548c2ecf20Sopenharmony_ci } 558c2ecf20Sopenharmony_ci} 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci## reads a result line from a child process and adds it to the $results array 588c2ecf20Sopenharmony_cisub read_results{ 598c2ecf20Sopenharmony_ci my ($fh) = @_; 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_ci # each child prints out a full line w/ autoflush and exits after the 628c2ecf20Sopenharmony_ci # last line, so even if buffered I/O blocks here, it shouldn't block 638c2ecf20Sopenharmony_ci # very long 648c2ecf20Sopenharmony_ci my $data = <$fh>; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci if (!defined($data)) { 678c2ecf20Sopenharmony_ci return 0; 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci chomp($data); 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci my ($index, $level, $secname) = $data =~ 738c2ecf20Sopenharmony_ci /^(\d+)\ ([^\ ]+)\ (.*)$/; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci if (!defined($index) || 768c2ecf20Sopenharmony_ci !defined($level) || 778c2ecf20Sopenharmony_ci !defined($secname)) { 788c2ecf20Sopenharmony_ci die "$0: ERROR: child process returned invalid data: $data\n"; 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci $index = int($index); 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci if (!exists($results->{$index})) { 848c2ecf20Sopenharmony_ci $results->{$index} = []; 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci push (@{$results->{$index}}, { 888c2ecf20Sopenharmony_ci 'level' => $level, 898c2ecf20Sopenharmony_ci 'secname' => $secname 908c2ecf20Sopenharmony_ci }); 918c2ecf20Sopenharmony_ci 928c2ecf20Sopenharmony_ci return 1; 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_ci## finds initcalls from an object file or all object files in an archive, and 968c2ecf20Sopenharmony_ci## writes results back to the parent process 978c2ecf20Sopenharmony_cisub find_initcalls { 988c2ecf20Sopenharmony_ci my ($index, $file) = @_; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci die "$0: ERROR: file $file doesn't exist?" if (! -f $file); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci open(my $fh, "\"$nm\" --defined-only \"$file\" 2>/dev/null |") 1038c2ecf20Sopenharmony_ci or die "$0: ERROR: failed to execute \"$nm\": $!"; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci my $initcalls = {}; 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_ci while (<$fh>) { 1088c2ecf20Sopenharmony_ci chomp; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci # check for the start of a new object file (if processing an 1118c2ecf20Sopenharmony_ci # archive) 1128c2ecf20Sopenharmony_ci my ($path)= $_ =~ /^(.+)\:$/; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci if (defined($path)) { 1158c2ecf20Sopenharmony_ci write_results($index, $initcalls); 1168c2ecf20Sopenharmony_ci $initcalls = {}; 1178c2ecf20Sopenharmony_ci next; 1188c2ecf20Sopenharmony_ci } 1198c2ecf20Sopenharmony_ci 1208c2ecf20Sopenharmony_ci # look for an initcall 1218c2ecf20Sopenharmony_ci my ($module, $counter, $line, $symbol) = $_ =~ 1228c2ecf20Sopenharmony_ci /[a-z]\s+__initcall__(\S*)__(\d+)_(\d+)_(.*)$/; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci if (!defined($module)) { 1258c2ecf20Sopenharmony_ci $module = '' 1268c2ecf20Sopenharmony_ci } 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (!defined($counter) || 1298c2ecf20Sopenharmony_ci !defined($line) || 1308c2ecf20Sopenharmony_ci !defined($symbol)) { 1318c2ecf20Sopenharmony_ci next; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci # parse initcall level 1358c2ecf20Sopenharmony_ci my ($function, $level) = $symbol =~ 1368c2ecf20Sopenharmony_ci /^(.*)((early|rootfs|con|[0-9])s?)$/; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci die "$0: ERROR: invalid initcall name $symbol in $file($path)" 1398c2ecf20Sopenharmony_ci if (!defined($function) || !defined($level)); 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci $initcalls->{$counter} = { 1428c2ecf20Sopenharmony_ci 'module' => $module, 1438c2ecf20Sopenharmony_ci 'line' => $line, 1448c2ecf20Sopenharmony_ci 'function' => $function, 1458c2ecf20Sopenharmony_ci 'level' => $level, 1468c2ecf20Sopenharmony_ci }; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci close($fh); 1508c2ecf20Sopenharmony_ci write_results($index, $initcalls); 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci## waits for any child process to complete, reads the results, and adds them to 1548c2ecf20Sopenharmony_ci## the $results array for later processing 1558c2ecf20Sopenharmony_cisub wait_for_results { 1568c2ecf20Sopenharmony_ci my ($select) = @_; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci my $pid = 0; 1598c2ecf20Sopenharmony_ci do { 1608c2ecf20Sopenharmony_ci # unblock children that may have a full write buffer 1618c2ecf20Sopenharmony_ci foreach my $fh ($select->can_read(0)) { 1628c2ecf20Sopenharmony_ci read_results($fh); 1638c2ecf20Sopenharmony_ci } 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci # check for children that have exited, read the remaining data 1668c2ecf20Sopenharmony_ci # from them, and clean up 1678c2ecf20Sopenharmony_ci $pid = waitpid(-1, WNOHANG); 1688c2ecf20Sopenharmony_ci if ($pid > 0) { 1698c2ecf20Sopenharmony_ci if (!exists($jobs->{$pid})) { 1708c2ecf20Sopenharmony_ci next; 1718c2ecf20Sopenharmony_ci } 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci my $fh = $jobs->{$pid}; 1748c2ecf20Sopenharmony_ci $select->remove($fh); 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci while (read_results($fh)) { 1778c2ecf20Sopenharmony_ci # until eof 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci close($fh); 1818c2ecf20Sopenharmony_ci delete($jobs->{$pid}); 1828c2ecf20Sopenharmony_ci } 1838c2ecf20Sopenharmony_ci } while ($pid > 0); 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci## forks a child to process each file passed in the command line and collects 1878c2ecf20Sopenharmony_ci## the results 1888c2ecf20Sopenharmony_cisub process_files { 1898c2ecf20Sopenharmony_ci my $index = 0; 1908c2ecf20Sopenharmony_ci my $njobs = $ENV{'PARALLELISM'} || get_online_processors(); 1918c2ecf20Sopenharmony_ci my $select = IO::Select->new(); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci while (my $file = shift(@ARGV)) { 1948c2ecf20Sopenharmony_ci # fork a child process and read it's stdout 1958c2ecf20Sopenharmony_ci my $pid = open(my $fh, '-|'); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci if (!defined($pid)) { 1988c2ecf20Sopenharmony_ci die "$0: ERROR: failed to fork: $!"; 1998c2ecf20Sopenharmony_ci } elsif ($pid) { 2008c2ecf20Sopenharmony_ci # save the child process pid and the file handle 2018c2ecf20Sopenharmony_ci $select->add($fh); 2028c2ecf20Sopenharmony_ci $jobs->{$pid} = $fh; 2038c2ecf20Sopenharmony_ci } else { 2048c2ecf20Sopenharmony_ci # in the child process 2058c2ecf20Sopenharmony_ci STDOUT->autoflush(1); 2068c2ecf20Sopenharmony_ci find_initcalls($index, "$objtree/$file"); 2078c2ecf20Sopenharmony_ci exit; 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci $index++; 2118c2ecf20Sopenharmony_ci 2128c2ecf20Sopenharmony_ci # limit the number of children to $njobs 2138c2ecf20Sopenharmony_ci if (scalar(keys(%{$jobs})) >= $njobs) { 2148c2ecf20Sopenharmony_ci wait_for_results($select); 2158c2ecf20Sopenharmony_ci } 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci # wait for the remaining children to complete 2198c2ecf20Sopenharmony_ci while (scalar(keys(%{$jobs})) > 0) { 2208c2ecf20Sopenharmony_ci wait_for_results($select); 2218c2ecf20Sopenharmony_ci } 2228c2ecf20Sopenharmony_ci} 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cisub generate_initcall_lds() { 2258c2ecf20Sopenharmony_ci process_files(); 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci my $sections = {}; # level -> [ secname, ...] 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci # sort results to retain link order and split to sections per 2308c2ecf20Sopenharmony_ci # initcall level 2318c2ecf20Sopenharmony_ci foreach my $index (sort { $a <=> $b } keys(%{$results})) { 2328c2ecf20Sopenharmony_ci foreach my $result (@{$results->{$index}}) { 2338c2ecf20Sopenharmony_ci my $level = $result->{'level'}; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci if (!exists($sections->{$level})) { 2368c2ecf20Sopenharmony_ci $sections->{$level} = []; 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_ci push(@{$sections->{$level}}, $result->{'secname'}); 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci } 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci die "$0: ERROR: no initcalls?" if (!keys(%{$sections})); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci # print out a linker script that defines the order of initcalls for 2468c2ecf20Sopenharmony_ci # each level 2478c2ecf20Sopenharmony_ci print "SECTIONS {\n"; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci foreach my $level (sort(keys(%{$sections}))) { 2508c2ecf20Sopenharmony_ci my $section; 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ci if ($level eq 'con') { 2538c2ecf20Sopenharmony_ci $section = '.con_initcall.init'; 2548c2ecf20Sopenharmony_ci } else { 2558c2ecf20Sopenharmony_ci $section = ".initcall${level}.init"; 2568c2ecf20Sopenharmony_ci } 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci print "\t${section} : {\n"; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci foreach my $secname (@{$sections->{$level}}) { 2618c2ecf20Sopenharmony_ci print "\t\t*(${section}..${secname}) ;\n"; 2628c2ecf20Sopenharmony_ci } 2638c2ecf20Sopenharmony_ci 2648c2ecf20Sopenharmony_ci print "\t}\n"; 2658c2ecf20Sopenharmony_ci } 2668c2ecf20Sopenharmony_ci 2678c2ecf20Sopenharmony_ci print "}\n"; 2688c2ecf20Sopenharmony_ci} 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_cigenerate_initcall_lds(); 271