You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
250 lines
5.7 KiB
250 lines
5.7 KiB
#!/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: <object file index>
|
|
# line 2..n: <level> <counter> <line> <function>
|
|
|
|
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";
|
|
|