#!/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: # line 2..n: 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";