Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

init: lto: ensure initcall ordering

With LTO, the compiler doesn't necessarily obey the link order for
initcalls, and initcall variables need globally unique names to avoid
collisions at link time.

This change exports __KBUILD_MODNAME and adds the initcall_id() macro,
which uses it together with __COUNTER__ and __LINE__ to help ensure
these variables have unique names, and moves each variable to its own
section when LTO is enabled, so the correct order can be specified using
a linker script.

The generate_initcall_ordering.pl script uses nm to find initcalls from
the object files passed to the linker, and generates a linker script
that specifies the same order for initcalls that we would have without
LTO. With LTO enabled, the script is called in link-vmlinux.sh through
jobserver-exec to limit the number of jobs spawned.

Signed-off-by: Sami Tolvanen <samitolvanen@google.com>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/r/20201211184633.3213045-8-samitolvanen@google.com

authored by

Sami Tolvanen and committed by
Kees Cook
a8cccdd9 fbe078d3

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