Replace ksplice_mod_find_sym with module_on_each_symbol.
[ksplice.git] / ksplice-create.in
blob702b5b0fc67f5aa4130a08a062214e520f67651d
1 #!/usr/bin/perl
3 # Copyright (C) 2008 Jeffrey Brian Arnold <jbarnold@mit.edu>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License, version 2.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA
16 # 02110-1301, USA.
18 use Getopt::Long;
19 use Cwd 'abs_path', 'getcwd';
20 use Pod::Usage;
21 use strict;
22 use warnings;
23 use lib 'KSPLICE_DATA_DIR';
24 use ksplice;
26 my ($patchfile, $diffext, $orig_config_dir, $postdir, $jobs);
27 my ($help, $wantversion, $prebuild, $skip_prebuild, $apply, $patch_opt) = (0, 0, 0, 0, 0, "-p1");
28 Getopt::Long::Configure("bundling");
29 GetOptions("help|?" => \$help,
30 "version" => \$wantversion,
31 "verbose|v!" => \$verbose,
32 "patch=s" => \$patchfile,
33 "diffext=s" => \$diffext,
34 "prebuild" => \$prebuild,
35 "skip-prebuild" => \$skip_prebuild,
36 "jobs|j:i" => \$jobs,
37 "config=s" => \$orig_config_dir,
38 "apply" => \$apply,
39 "postdir=s" => \$postdir,
40 "patch-opt=s@" => \$patch_opt) or pod2usage(1);
42 if($wantversion) {
43 print $version_str;
44 exit(0);
46 pod2usage(1) if($help || scalar(@ARGV) != 1);
47 my $actions = (defined $patchfile) + (defined $diffext) + ($prebuild);
48 pod2usage(1) if($actions != 1);
50 my ($linuxtree) = (abs_path($ARGV[0]));
52 my $tmpdir = init_tmpdir();
53 runval("cp", "--", $patchfile, "$tmpdir/patch") if(defined $patchfile);
54 $patchfile = "$tmpdir/patch";
56 $patch_opt = "-p0" if(defined $diffext);
57 $patch_opt = join(" ", @$patch_opt) if(ref $patch_opt);
59 if(!defined $orig_config_dir) {
60 $orig_config_dir = "$linuxtree/ksplice";
62 else {
63 $orig_config_dir = abs_path($orig_config_dir);
64 if($orig_config_dir =~ $linuxtree) {
65 die "Aborting: User-specified ORIG_CONFIG cannot be KERNEL_SOURCE or a subdirectory";
68 if(!defined $orig_config_dir || ! -d $orig_config_dir) {
69 die "Failed to find ORIG_CONFIG directory ($orig_config_dir)";
71 if(! -e "$orig_config_dir/.config") {
72 die "Failed to find .config file in ORIG_CONFIG directory";
74 if(! -e "$orig_config_dir/System.map") {
75 die "Failed to find System.map file in ORIG_CONFIG directory";
78 if(!defined $postdir) {
79 $postdir = "$orig_config_dir/post";
81 elsif($postdir =~ $linuxtree) {
82 die "Aborting: User-specified postdir cannot be KERNEL_SOURCE or a subdirectory";
85 runval("cp", "--", "$orig_config_dir/.config", $linuxtree);
87 my @chars = ('a'..'z', 0..9);
88 my $kid = "";
89 for(my $z = 0; $z < 8; $z++) {
90 $kid .= $chars[int(rand(36))];
92 my $ksplice = "ksplice-$kid";
94 my %syms;
95 load_system_map();
97 # Some versions of Fedora have System.map files whose symbol addresses disagree
98 # with the running kernel by a constant address offset. Here, Ksplice notes the
99 # System.map address for printk so that it can later compare this address against
100 # the kernel's address for printk. This comparison helps Ksplice work around
101 # this Fedora problem, and this comparison also helps Ksplice detect whether
102 # the user has provided an incorrect System.map file.
103 my $map_printk = (find_sym_system_map("printk"))[0];
105 my @rmsyms = qw(
106 bust_spinlocks
107 task_curr
108 __kernel_text_address
109 tasklist_lock
110 stop_machine_run
111 module_mutex
112 modules
113 init_mm
114 kallsyms_addresses
115 kallsyms_num_syms
116 kallsyms_names
117 kallsyms_token_table
118 kallsyms_token_index
121 print "Starting kernel builds (this process might take a long time)...\n";
122 if(!$verbose) {
123 print "For output during this process, run ksplice-create with the option -v\n";
126 ###################################################################
127 # PHASE 1: Determine which object files are modified by the patch #
128 # - performs the pre and post kernel builds #
129 # - uses objdiff to identify which ELF sections have changed and #
130 # which ELF symbols are entry points to those sections #
131 ###################################################################
133 # We will refer to the object files modified by the patch as the "target object
134 # files", the ELF sections changed by the patch as the "target sections", and
135 # the entry points of those sections as the "target entry points".
137 my $origdir = getcwd();
138 runcd($linuxtree);
139 if(defined $diffext) {
140 runval("$libexecdir/ksplice-gendiff-reversed >$patchfile . $diffext");
143 my @jlevel = (defined $ENV{CONCURRENCY_LEVEL} ? ("-j$ENV{CONCURRENCY_LEVEL}") : ());
144 @jlevel = ("-j$jobs") if(defined $jobs);
145 my @make_ksplice = ("make", "-f", "$datadir/Makefile.ksplice", @jlevel);
147 sub revert_orig() {
148 for (split(/\0/, runstr(qw(find -name *.KSPLICE_pre -print0)))) {
149 s/\.KSPLICE_pre$//;
150 rename("$_.KSPLICE_pre", $_);
151 unlink("$_.KSPLICE") if(-e "$_.KSPLICE");
154 revert_orig();
156 if(!$skip_prebuild &&
157 runval_raw(@make_ksplice, "KSPLICE_MODE=snap") != 0) {
158 die "Aborting: Prebuild failed";
160 exit(0) if($prebuild);
161 runval("patch $patch_opt -bz .KSPLICE_pre < $patchfile");
162 if(runval_raw(@make_ksplice, "KSPLICE_MODE=diff") != 0) {
163 revert_orig();
164 die "Aborting: Applying the patch appears to break the kernel build";
167 runval("mkdir", "-p", "--", "$tmpdir/collect");
169 my @modules = ();
170 foreach(glob(".tmp_versions/*.mod.KSPLICE")) {
171 open MOD, '<', $_;
172 chomp(my $module = <MOD>);
173 close MOD;
174 runval("cp", "$module.o.KSPLICE", "$tmpdir/collect/");
175 runval("cp", "$module.o.KSPLICE_primary", "$tmpdir/collect/");
176 runval("cp", "$module.o.KSPLICE_helper", "$tmpdir/collect/");
177 $module =~ s/^.*\///;
178 push @modules, $module;
181 if(!@modules) {
182 revert_orig();
183 die "Aborting: No changes detected";
186 revert_orig();
188 my $word;
189 for my $module (@modules) {
190 runcd("$tmpdir/collect");
191 open IN, '<', "$module.o.KSPLICE";
192 chomp(my $bits = <IN>);
193 die if($bits != 32 && $bits != 64);
194 $word = ($bits == 64 ? "quad" : "long");
196 my ($patchlist, $relocs_primary, $relocs_helper) = ('', '', '');
197 $patchlist .= $_ while (($_ = <IN>) ne "\n");
198 $relocs_primary .= $_ while (($_ = <IN>) ne "\n");
199 $relocs_helper .= $_ while (($_ = <IN>) ne "\n");
201 close IN;
203 ################################################################################
204 # PHASE 3: Combine the target object files and prepare for kernel module build #
205 # - links the many target object files into two "collection" object files #
206 # - saves the reloc info extracted earlier in ELF sect .ksplice.ksplice_relocs #
207 # - uses objmanip's sizelist mode to save the names and sizes of target funcs #
208 # - uses ld-script to aggregate all ELF text sections into .text #
209 # - saves the list of target entry syms in ELF sect .ksplice.ksplice_patches #
210 ################################################################################
212 parse_and_save(\&parse_relocs, $relocs_primary, "$module.o.KSPLICE_primary",
213 "ksplice_relocs", "_global");
214 parse_and_save(\&parse_relocs, $relocs_helper, "$module.o.KSPLICE_helper",
215 "ksplice_relocs", "_global");
217 runcd($tmpdir);
218 runval("cp", "-a", "--", "$datadir/kmodsrc", "kmodsrc-$module");
219 runval("mv", "collect/$module.o.KSPLICE_primary", "kmodsrc-$module/collection.o.primary");
220 runval("mv", "collect/$module.o.KSPLICE_helper", "kmodsrc-$module/collection.o.helper");
221 runcd("kmodsrc-$module");
223 my $sizelist_primary = runsuc("objmanip", "collection.o.primary", "sizelist");
224 parse_and_save(\&parse_sizelist, $sizelist_primary, "collection.o.primary", "ksplice_sizes");
225 my $sizelist_helper = runsuc("objmanip", "collection.o.helper", "sizelist");
226 parse_and_save(\&parse_sizelist, $sizelist_helper, "collection.o.helper", "ksplice_sizes");
228 runval("ld", "--script=ld-script", "-r", "-o", "collection.o.primary.postld", "collection.o.primary");
229 runval("cp", "collection.o.primary.postld", "collection.o.primary");
230 runval("ld", "--script=ld-script", "-r", "-o", "collection.o.helper.postld", "collection.o.helper");
231 runval("cp", "collection.o.helper.postld", "collection.o.helper");
233 parse_and_save(\&parse_patchlist, $patchlist, "collection.o.primary", "ksplice_patches");
235 ###############################################################################
236 # PHASE 4: Build the kernel modules and create the update tarball #
237 # - builds primary and helper kernel modules #
238 # - uses objmanip's rmsyms mode to remove relocations to non-exported symbols #
239 # - creates a tarball of the primary module and the helper module #
240 ###############################################################################
242 my $kid_m = "${kid}_$module";
243 $kid_m =~ s/-/_/g;
244 my $ksplice_m = "ksplice-$kid_m";
245 runval("make", @jlevel, "modules", "KSPLICE_ID=$kid_m", "map_printk=$map_printk", "KERNELSRC=$linuxtree");
247 my $relocs = runsuc("objmanip", "$ksplice_m.ko", "rmsyms", @rmsyms);
248 parse_and_save(\&parse_relocs, $relocs, "$ksplice_m.ko", "ksplice_init_relocs", "");
251 runcd($tmpdir);
252 runval("mkdir", $ksplice);
253 runval("mv", "--", $patchfile, $ksplice);
254 for my $module (@modules) {
255 my $kid_m = "${kid}_$module";
256 $kid_m =~ s/-/_/g;
257 my $ksplice_m = "ksplice-$kid_m";
258 runval("mv", "--", "kmodsrc-$module/$ksplice_m.ko", "kmodsrc-$module/$ksplice_m-helper.ko", $ksplice);
260 runval("mkdir", "$ksplice/debug");
261 runval("mv", "collect", "$ksplice/debug");
262 for my $module (@modules) {
263 runval("mv", "kmodsrc-$module", "$ksplice/debug");
265 runval("tar", "czf", "$ksplice.tar.gz", "--", $ksplice);
266 runval("cp", "--", "$ksplice.tar.gz", $origdir);
267 runcd($origdir);
268 runval("rm", "-rf", "--", "$tmpdir");
270 print "Ksplice update tarball written to $ksplice.tar.gz\n";
272 if($apply) {
273 print "Now running ksplice-apply to apply update...\n";
274 exec("ksplice-apply", $ksplice) || die;
277 exit(0);
279 sub load_system_map {
280 open(SYMS, "<", "$orig_config_dir/System.map") or die;
281 my $line;
282 while(defined($line = <SYMS>)) {
283 my ($addr, $type, $sym, $mod) = split(/\s+/, $line);
284 next if($sym =~ /init_module/ ||
285 $sym =~ /cleanup_module/ ||
286 $sym =~ /this_module/);
288 $syms{$sym}{$addr} = 1;
290 close(SYMS);
293 sub find_sym_system_map {
294 my ($sym) = @_;
295 $sym =~ s/[.]text[.]//g;
296 $sym =~ s/[.]bss[.]//g;
297 $sym =~ s/[.]data[.]//g;
298 $sym =~ s/____.*//g;
299 if(defined $syms{$sym}) {
300 return keys(%{$syms{$sym}});
302 return ();
305 sub parse_and_save {
306 my ($funcref, $entries, $objfile, $suffix, @other) = @_;
307 my @entries = split(/\n/, $entries);
309 my @tosave;
310 foreach my $entry (@entries) {
311 print $entry, "\n" if($verbose);
312 &$funcref(\@tosave, $entry, @other);
314 save_using_asm(\@tosave, $objfile, $suffix);
317 BEGIN { # to make asm_id a static local variable
318 my ${asm_id} = "0";
319 sub save_using_asm {
320 my ($tosaveref, $objfile, $suffix) = @_;
322 open(ASM, ">", "asm${asm_id}.s");
323 print ASM ".section .ksplice.${suffix}_str, \"a\"\n";
324 print ASM "${suffix}_str:\n";
325 print ASM ".section .ksplice.${suffix}, \"a\"\n";
326 print ASM "${suffix}:\n";
328 my $num = 0;
329 foreach my $entryref (@$tosaveref) {
330 my @entry = @{$entryref};
332 if($entry[0] eq "str") {
333 print ASM ".section .ksplice.${suffix}_str, \"a\"\n";
334 print ASM $suffix, $num, ": .string \"", $entry[1], "\"\n";
335 print ASM ".section .ksplice.${suffix}, \"a\"\n";
336 print ASM ".$word ${suffix}", $num++, "\n";
338 elsif($entry[0] eq "array" && scalar(@entry) == 1) {
339 print ASM ".section .ksplice.${suffix}, \"a\"\n";
340 print ASM ".$word 0x0\n";
342 elsif($entry[0] eq "array") {
343 print ASM ".section .ksplice.${suffix}_array, \"a\"\n";
344 print ASM $suffix, $num, ":\n";
345 for(my $i = 1; $i < scalar(@entry); $i++) {
346 print ASM ".$word 0x", $entry[$i], "\n";
348 print ASM ".section .ksplice.${suffix}, \"a\"\n";
349 print ASM ".$word ${suffix}", $num++, "\n";
351 elsif($entry[0] eq "word") {
352 print ASM ".section .ksplice.${suffix}, \"a\"\n";
353 print ASM ".$word 0x", $entry[1], "\n";
355 elsif($entry[0] eq "ptr") {
356 print ASM ".section .ksplice.${suffix}, \"a\"\n";
357 print ASM ".$word ", $entry[1], "\n";
359 else { die; }
361 print ASM ".section .ksplice.${suffix}, \"a\"\n";
362 print ASM ".$word 0\n";
363 print ASM ".globl ${suffix}\n";
364 close(ASM);
366 runval("gcc", "-mcmodel=kernel", "-c", "asm${asm_id}.s", "-o", "asm${asm_id}.o");
367 runval("ld", "-r", "-o", "$objfile.new", $objfile, "asm${asm_id}.o");
368 runval("mv", "$objfile.new", $objfile);
369 ${asm_id}++;
371 } # close BEGIN
373 sub parse_relocs {
374 my ($tosaveref, $entry, $globalizer) = @_;
375 my ($sym, $sect, $addr, $pcrel, $addend, $size) = split(/\s/, $entry);
377 my ($func) = ($sect =~ /(.*)____/);
378 $sym =~ s/([.]data[.]__func__[.])\d+/$1${func}/g;
380 my @symvals = find_sym_system_map($sym);
381 my @sectvals = find_sym_system_map($sect);
383 push @$tosaveref, (["str", $sym], ["str", $sect],
384 ["ptr", "${sect}${globalizer}"],
385 ["word", $addr],
386 ["word", scalar(@symvals)],
387 ["array", @symvals],
388 ["word", scalar(@sectvals)],
389 ["array", @sectvals],
390 ["word", $pcrel],
391 ["word", $addend],
392 ["word", $size]);
395 sub parse_sizelist {
396 my ($tosaveref, $entry) = @_;
397 # grab the size and the symbol name from the end of the line
398 my ($size, $sym) = ($entry =~ /\s([a-z0-9]+)\s+(\S+)$/);
400 my @vals = find_sym_system_map($sym);
402 push @$tosaveref, (["str", $sym], ["word", $size],
403 ["ptr", "${sym}_global"], ["word", scalar(@vals)],
404 ["array", @vals]);
407 sub parse_patchlist {
408 my ($tosaveref, $entry) = @_;
409 my ($oldsym, $replsym) = split(/\s/, $entry);
411 my $oldaddr = 0;
412 my @vals = find_sym_system_map($oldsym);
413 $oldaddr = $vals[0] if(scalar(@vals) == 1);
415 push @$tosaveref, (["str", $oldsym], ["str", $replsym],
416 ["word", $oldaddr], ["ptr", "${replsym}_global"],
417 ["word", 0]);
420 =head1 NAME
422 ksplice-create - Create a set of kernel modules for a rebootless kernel update
424 =head1 SYNOPSIS
426 B<ksplice-create> [B<--config=>I<ORIG_CONFIG>] B<--patch=>I<PATCH_FILE> I<KERNEL_SOURCE>
428 B<ksplice-create> [B<--config=>I<ORIG_CONFIG>] B<--diffext=>I<EXTENSION> I<KERNEL_SOURCE>
430 B<ksplice-create> [B<--config=>I<ORIG_CONFIG>] B<--prebuild> I<KERNEL_SOURCE>
432 =head1 DESCRIPTION
434 B<ksplice-create> creates a set of Ksplice kernel modules that, when loaded,
435 will apply a user-specified source code patch to the running binary kernel.
437 Before you use B<ksplice-create> on a patch, you should confirm that the
438 desired source code change does not make any semantic changes to kernel data
439 structures--that is, changes that would require existing instances of kernel
440 data structures to be transformed (e.g., a patch that adds a field to a global
441 data structure would require the existing data structures to change). If you
442 use Ksplice on a patch that changes data structure semantics, Ksplice will not
443 detect the problem and you could experience kernel problems as a result.
445 The to-be-applied source code patch can be specified by providing a L<patch(1)>
446 file (B<--patch=>I<PATCH_FILE>) or by providing a file extension
447 (B<--diffext=>I<EXTENSION>).
449 If a file extension is specified, then the desired source code patch will be
450 determined by comparing all of the files in the I<KERNEL_SOURCE> directory tree
451 whose names end with the extra extension I<EXTENSION> against the corresponding
452 files without the extra extension. Only the new files containing the extra
453 extension in their filenames should be modified.
455 Here is an example of using a file extension to specify a patch:
457 $ cp KERNEL_SOURCE/kernel/sys.c KERNEL_SOURCE/kernel/sys.c.prctl_fixed
458 [edit sys.c.prctl_fixed to include the desired changes]
459 $ ksplice-create --diffext=.prctl_fixed KERNEL_SOURCE
461 KERNEL_SOURCE must be a directory containing the to-be-updated kernel's
462 original source code. If your Linux distribution applies patches to the Linux
463 kernel during the kernel build process, then those patches must be applied to
464 the I<KERNEL_SOURCE> directory before invoking B<ksplice-create> on that
465 directory. B<ksplice-create> will not modify the source code in the
466 I<KERNEL_SOURCE> directory tree, but it will perform a kernel build in that
467 directory tree.
469 I<ORIG_CONFIG> can be used to specify the directory containing the
470 to-be-updated kernel's original F<.config> file and original F<System.map> file
471 (the files should have exactly those names). I<ORIG_CONFIG> defaults to
472 I<KERNEL_SOURCE>B</ksplice>.
474 The default L<gcc(1)> compiler and L<as(1)> assembler on the system should be as
475 close to the compiler and assembler originally used to build the running kernel
476 as possible. If the current compiler and linker are too different from the
477 original compiler and linker, B<ksplice-apply> will abort when applying the
478 update.
480 B<ksplice-create> outputs a L<tar(1)> file, compressed with L<gzip(1)>,
481 containing the desired Ksplice update modules. This tarball will be created in
482 the current directory, and it can be manipulated using the other Ksplice
483 utilities, such as B<ksplice-apply>.
485 The first time that B<ksplice-create> is invoked on a I<KERNEL_SOURCE>
486 directory, it must build that kernel from scratch, which is much slower than
487 the rest of the update-creation process. B<--prebuild> can be used to perform
488 this initial kernel build (and set up a tentative B<post> directory tree)
489 without providing a source code patch.
491 In order to patch a function that has previously been patched by Ksplice, the
492 user needs to ensure that the I<KERNEL_SOURCE> directory provided to Ksplice
493 contains the source for the currently running kernel, including any patches
494 that have previously been applied by Ksplice.
496 =head1 OPTIONS
498 =over 8
500 =item B<-v>, B<--verbose>
502 Prints the commands being executed, the output of the commands being executed,
503 and various other pieces of information.
505 =item B<-j> I<JOBS>, B<--jobs=>I<JOBS>
507 Specifies the number of jobs to run simultaneously while performing kernel
508 builds. B<ksplice-create> also honors the environment variable
509 CONCURRENCY_LEVEL.
511 =item B<--apply>
513 Immediately applies the generated update to the running kernel by invoking
514 B<ksplice-apply>.
516 =item B<--postdir=>I<DIRECTORY>
518 Specifies a directory that is B<dedicated to Ksplice> to be used as the Ksplice
519 I<post> directory. Defaults to I<ORIG_CONFIG>B</post>. If this directory
520 exists, the directory's contents will be removed. If it does not exist, it
521 will be created.
523 =item B<--patch-opt=>I<OPTIONS>
525 Can be used to pass options to L<patch(1)>. If this option is NOT specified, then
526 B<-p1> is passed to B<patch>. If this option is specified, then only the
527 specified options will be passed to B<patch>. This option can be repeated in
528 order to pass multiple options to B<patch>. This option is ignored when the
529 to-be-applied source code patch is specified using B<--diffext>.
531 =back
533 =head1 BUGS
535 Please report bugs to <PACKAGE_BUGREPORT>.
537 =head1 SEE ALSO
539 L<ksplice-apply(8)>, L<ksplice-view(8)>, L<ksplice-undo(8)>
541 =head1 COPYRIGHT
543 Copyright (C) 2008 Jeffrey Brian Arnold <jbarnold@mit.edu>.
545 This is free software and documentation. You can redistribute and/or modify it
546 under the terms of the GNU General Public License, version 2.
548 =cut