xref: /linux/tools/testing/ktest/config-bisect.pl (revision e9f0878c4b2004ac19581274c1ae4c61ae3ca70e)
1#!/usr/bin/perl -w
2#
3# Copyright 2015 - Steven Rostedt, Red Hat Inc.
4# Copyright 2017 - Steven Rostedt, VMware, Inc.
5#
6# Licensed under the terms of the GNU GPL License version 2
7#
8
9# usage:
10#  config-bisect.pl [options] good-config bad-config [good|bad]
11#
12
13# Compares a good config to a bad config, then takes half of the diffs
14# and produces a config that is somewhere between the good config and
15# the bad config. That is, the resulting config will start with the
16# good config and will try to make half of the differences of between
17# the good and bad configs match the bad config. It tries because of
18# dependencies between the two configs it may not be able to change
19# exactly half of the configs that are different between the two config
20# files.
21
22# Here's a normal way to use it:
23#
24#  $ cd /path/to/linux/kernel
25#  $ config-bisect.pl /path/to/good/config /path/to/bad/config
26
27# This will now pull in good config (blowing away .config in that directory
28# so do not make that be one of the good or bad configs), and then
29# build the config with "make oldconfig" to make sure it matches the
30# current kernel. It will then store the configs in that result for
31# the good config. It does the same for the bad config as well.
32# The algorithm will run, merging half of the differences between
33# the two configs and building them with "make oldconfig" to make sure
34# the result changes (dependencies may reset changes the tool had made).
35# It then copies the result of its good config to /path/to/good/config.tmp
36# and the bad config to /path/to/bad/config.tmp (just appends ".tmp" to the
37# files passed in). And the ".config" that you should test will be in
38# directory
39
40# After the first run, determine if the result is good or bad then
41# run the same command appending the result
42
43# For good results:
44#  $ config-bisect.pl /path/to/good/config /path/to/bad/config good
45
46# For bad results:
47#  $ config-bisect.pl /path/to/good/config /path/to/bad/config bad
48
49# Do not change the good-config or bad-config, config-bisect.pl will
50# copy the good-config to a temp file with the same name as good-config
51# but with a ".tmp" after it. It will do the same with the bad-config.
52
53# If "good" or "bad" is not stated at the end, it will copy the good and
54# bad configs to the .tmp versions. If a .tmp version already exists, it will
55# warn before writing over them (-r will not warn, and just write over them).
56# If the last config is labeled "good", then it will copy it to the good .tmp
57# version. If the last config is labeled "bad", it will copy it to the bad
58# .tmp version. It will continue this until it can not merge the two any more
59# without the result being equal to either the good or bad .tmp configs.
60
61my $start = 0;
62my $val = "";
63
64my $pwd = `pwd`;
65chomp $pwd;
66my $tree = $pwd;
67my $build;
68
69my $output_config;
70my $reset_bisect;
71
72sub usage {
73    print << "EOF"
74
75usage: config-bisect.pl [-l linux-tree][-b build-dir] good-config bad-config [good|bad]
76  -l [optional] define location of linux-tree (default is current directory)
77  -b [optional] define location to build (O=build-dir) (default is linux-tree)
78  good-config the config that is considered good
79  bad-config the config that does not work
80  "good" add this if the last run produced a good config
81  "bad" add this if the last run produced a bad config
82  If "good" or "bad" is not specified, then it is the start of a new bisect
83
84  Note, each run will create copy of good and bad configs with ".tmp" appended.
85
86EOF
87;
88
89    exit(-1);
90}
91
92sub doprint {
93    print @_;
94}
95
96sub dodie {
97    doprint "CRITICAL FAILURE... ", @_, "\n";
98
99    die @_, "\n";
100}
101
102sub expand_path {
103    my ($file) = @_;
104
105    if ($file =~ m,^/,) {
106	return $file;
107    }
108    return "$pwd/$file";
109}
110
111sub read_prompt {
112    my ($cancel, $prompt) = @_;
113
114    my $ans;
115
116    for (;;) {
117	if ($cancel) {
118	    print "$prompt [y/n/C] ";
119	} else {
120	    print "$prompt [y/N] ";
121	}
122	$ans = <STDIN>;
123	chomp $ans;
124	if ($ans =~ /^\s*$/) {
125	    if ($cancel) {
126		$ans = "c";
127	    } else {
128		$ans = "n";
129	    }
130	}
131	last if ($ans =~ /^y$/i || $ans =~ /^n$/i);
132	if ($cancel) {
133	    last if ($ans =~ /^c$/i);
134	    print "Please answer either 'y', 'n' or 'c'.\n";
135	} else {
136	    print "Please answer either 'y' or 'n'.\n";
137	}
138    }
139    if ($ans =~ /^c/i) {
140	exit;
141    }
142    if ($ans !~ /^y$/i) {
143	return 0;
144    }
145    return 1;
146}
147
148sub read_yn {
149    my ($prompt) = @_;
150
151    return read_prompt 0, $prompt;
152}
153
154sub read_ync {
155    my ($prompt) = @_;
156
157    return read_prompt 1, $prompt;
158}
159
160sub run_command {
161    my ($command, $redirect) = @_;
162    my $start_time;
163    my $end_time;
164    my $dord = 0;
165    my $pid;
166
167    $start_time = time;
168
169    doprint("$command ... ");
170
171    $pid = open(CMD, "$command 2>&1 |") or
172	dodie "unable to exec $command";
173
174    if (defined($redirect)) {
175	open (RD, ">$redirect") or
176	    dodie "failed to write to redirect $redirect";
177	$dord = 1;
178    }
179
180    while (<CMD>) {
181	print RD  if ($dord);
182    }
183
184    waitpid($pid, 0);
185    my $failed = $?;
186
187    close(CMD);
188    close(RD)  if ($dord);
189
190    $end_time = time;
191    my $delta = $end_time - $start_time;
192
193    if ($delta == 1) {
194	doprint "[1 second] ";
195    } else {
196	doprint "[$delta seconds] ";
197    }
198
199    if ($failed) {
200	doprint "FAILED!\n";
201    } else {
202	doprint "SUCCESS\n";
203    }
204
205    return !$failed;
206}
207
208###### CONFIG BISECT ######
209
210# config_ignore holds the configs that were set (or unset) for
211# a good config and we will ignore these configs for the rest
212# of a config bisect. These configs stay as they were.
213my %config_ignore;
214
215# config_set holds what all configs were set as.
216my %config_set;
217
218# config_off holds the set of configs that the bad config had disabled.
219# We need to record them and set them in the .config when running
220# olddefconfig, because olddefconfig keeps the defaults.
221my %config_off;
222
223# config_off_tmp holds a set of configs to turn off for now
224my @config_off_tmp;
225
226# config_list is the set of configs that are being tested
227my %config_list;
228my %null_config;
229
230my %dependency;
231
232my $make;
233
234sub make_oldconfig {
235
236    if (!run_command "$make olddefconfig") {
237	# Perhaps olddefconfig doesn't exist in this version of the kernel
238	# try oldnoconfig
239	doprint "olddefconfig failed, trying make oldnoconfig\n";
240	if (!run_command "$make oldnoconfig") {
241	    doprint "oldnoconfig failed, trying yes '' | make oldconfig\n";
242	    # try a yes '' | oldconfig
243	    run_command "yes '' | $make oldconfig" or
244		dodie "failed make config oldconfig";
245	}
246    }
247}
248
249sub assign_configs {
250    my ($hash, $config) = @_;
251
252    doprint "Reading configs from $config\n";
253
254    open (IN, $config)
255	or dodie "Failed to read $config";
256
257    while (<IN>) {
258	chomp;
259	if (/^((CONFIG\S*)=.*)/) {
260	    ${$hash}{$2} = $1;
261	} elsif (/^(# (CONFIG\S*) is not set)/) {
262	    ${$hash}{$2} = $1;
263	}
264    }
265
266    close(IN);
267}
268
269sub process_config_ignore {
270    my ($config) = @_;
271
272    assign_configs \%config_ignore, $config;
273}
274
275sub get_dependencies {
276    my ($config) = @_;
277
278    my $arr = $dependency{$config};
279    if (!defined($arr)) {
280	return ();
281    }
282
283    my @deps = @{$arr};
284
285    foreach my $dep (@{$arr}) {
286	print "ADD DEP $dep\n";
287	@deps = (@deps, get_dependencies $dep);
288    }
289
290    return @deps;
291}
292
293sub save_config {
294    my ($pc, $file) = @_;
295
296    my %configs = %{$pc};
297
298    doprint "Saving configs into $file\n";
299
300    open(OUT, ">$file") or dodie "Can not write to $file";
301
302    foreach my $config (keys %configs) {
303	print OUT "$configs{$config}\n";
304    }
305    close(OUT);
306}
307
308sub create_config {
309    my ($name, $pc) = @_;
310
311    doprint "Creating old config from $name configs\n";
312
313    save_config $pc, $output_config;
314
315    make_oldconfig;
316}
317
318# compare two config hashes, and return configs with different vals.
319# It returns B's config values, but you can use A to see what A was.
320sub diff_config_vals {
321    my ($pa, $pb) = @_;
322
323    # crappy Perl way to pass in hashes.
324    my %a = %{$pa};
325    my %b = %{$pb};
326
327    my %ret;
328
329    foreach my $item (keys %a) {
330	if (defined($b{$item}) && $b{$item} ne $a{$item}) {
331	    $ret{$item} = $b{$item};
332	}
333    }
334
335    return %ret;
336}
337
338# compare two config hashes and return the configs in B but not A
339sub diff_configs {
340    my ($pa, $pb) = @_;
341
342    my %ret;
343
344    # crappy Perl way to pass in hashes.
345    my %a = %{$pa};
346    my %b = %{$pb};
347
348    foreach my $item (keys %b) {
349	if (!defined($a{$item})) {
350	    $ret{$item} = $b{$item};
351	}
352    }
353
354    return %ret;
355}
356
357# return if two configs are equal or not
358# 0 is equal +1 b has something a does not
359# +1 if a and b have a different item.
360# -1 if a has something b does not
361sub compare_configs {
362    my ($pa, $pb) = @_;
363
364    my %ret;
365
366    # crappy Perl way to pass in hashes.
367    my %a = %{$pa};
368    my %b = %{$pb};
369
370    foreach my $item (keys %b) {
371	if (!defined($a{$item})) {
372	    return 1;
373	}
374	if ($a{$item} ne $b{$item}) {
375	    return 1;
376	}
377    }
378
379    foreach my $item (keys %a) {
380	if (!defined($b{$item})) {
381	    return -1;
382	}
383    }
384
385    return 0;
386}
387
388sub process_failed {
389    my ($config) = @_;
390
391    doprint "\n\n***************************************\n";
392    doprint "Found bad config: $config\n";
393    doprint "***************************************\n\n";
394}
395
396sub process_new_config {
397    my ($tc, $nc, $gc, $bc) = @_;
398
399    my %tmp_config = %{$tc};
400    my %good_configs = %{$gc};
401    my %bad_configs = %{$bc};
402
403    my %new_configs;
404
405    my $runtest = 1;
406    my $ret;
407
408    create_config "tmp_configs", \%tmp_config;
409    assign_configs \%new_configs, $output_config;
410
411    $ret = compare_configs \%new_configs, \%bad_configs;
412    if (!$ret) {
413	doprint "New config equals bad config, try next test\n";
414	$runtest = 0;
415    }
416
417    if ($runtest) {
418	$ret = compare_configs \%new_configs, \%good_configs;
419	if (!$ret) {
420	    doprint "New config equals good config, try next test\n";
421	    $runtest = 0;
422	}
423    }
424
425    %{$nc} = %new_configs;
426
427    return $runtest;
428}
429
430sub convert_config {
431    my ($config) = @_;
432
433    if ($config =~ /^# (.*) is not set/) {
434	$config = "$1=n";
435    }
436
437    $config =~ s/^CONFIG_//;
438    return $config;
439}
440
441sub print_config {
442    my ($sym, $config) = @_;
443
444    $config = convert_config $config;
445    doprint "$sym$config\n";
446}
447
448sub print_config_compare {
449    my ($good_config, $bad_config) = @_;
450
451    $good_config = convert_config $good_config;
452    $bad_config = convert_config $bad_config;
453
454    my $good_value = $good_config;
455    my $bad_value = $bad_config;
456    $good_value =~ s/(.*)=//;
457    my $config = $1;
458
459    $bad_value =~ s/.*=//;
460
461    doprint " $config $good_value -> $bad_value\n";
462}
463
464# Pass in:
465# $phalf: half of the configs names you want to add
466# $oconfigs: The orginial configs to start with
467# $sconfigs: The source to update $oconfigs with (from $phalf)
468# $which: The name of which half that is updating (top / bottom)
469# $type: The name of the source type (good / bad)
470sub make_half {
471    my ($phalf, $oconfigs, $sconfigs, $which, $type) = @_;
472
473    my @half = @{$phalf};
474    my %orig_configs = %{$oconfigs};
475    my %source_configs = %{$sconfigs};
476
477    my %tmp_config = %orig_configs;
478
479    doprint "Settings bisect with $which half of $type configs:\n";
480    foreach my $item (@half) {
481	doprint "Updating $item to $source_configs{$item}\n";
482	$tmp_config{$item} = $source_configs{$item};
483    }
484
485    return %tmp_config;
486}
487
488sub run_config_bisect {
489    my ($pgood, $pbad) = @_;
490
491    my %good_configs = %{$pgood};
492    my %bad_configs = %{$pbad};
493
494    my %diff_configs = diff_config_vals \%good_configs, \%bad_configs;
495    my %b_configs = diff_configs \%good_configs, \%bad_configs;
496    my %g_configs = diff_configs \%bad_configs, \%good_configs;
497
498    # diff_arr is what is in both good and bad but are different (y->n)
499    my @diff_arr = keys %diff_configs;
500    my $len_diff = $#diff_arr + 1;
501
502    # b_arr is what is in bad but not in good (has depends)
503    my @b_arr = keys %b_configs;
504    my $len_b = $#b_arr + 1;
505
506    # g_arr is what is in good but not in bad
507    my @g_arr = keys %g_configs;
508    my $len_g = $#g_arr + 1;
509
510    my $runtest = 0;
511    my %new_configs;
512    my $ret;
513
514    # Look at the configs that are different between good and bad.
515    # This does not include those that depend on other configs
516    #  (configs depending on other configs that are not set would
517    #   not show up even as a "# CONFIG_FOO is not set"
518
519
520    doprint "# of configs to check:             $len_diff\n";
521    doprint "# of configs showing only in good: $len_g\n";
522    doprint "# of configs showing only in bad:  $len_b\n";
523
524    if ($len_diff > 0) {
525	# Now test for different values
526
527	doprint "Configs left to check:\n";
528	doprint "  Good Config\t\t\tBad Config\n";
529	doprint "  -----------\t\t\t----------\n";
530	foreach my $item (@diff_arr) {
531	    doprint "  $good_configs{$item}\t$bad_configs{$item}\n";
532	}
533
534	my $half = int($#diff_arr / 2);
535	my @tophalf = @diff_arr[0 .. $half];
536
537	doprint "Set tmp config to be good config with some bad config values\n";
538
539	my %tmp_config = make_half \@tophalf, \%good_configs,
540	    \%bad_configs, "top", "bad";
541
542	$runtest = process_new_config \%tmp_config, \%new_configs,
543			    \%good_configs, \%bad_configs;
544
545	if (!$runtest) {
546	    doprint "Set tmp config to be bad config with some good config values\n";
547
548	    my %tmp_config = make_half \@tophalf, \%bad_configs,
549		\%good_configs, "top", "good";
550
551	    $runtest = process_new_config \%tmp_config, \%new_configs,
552		\%good_configs, \%bad_configs;
553	}
554    }
555
556    if (!$runtest && $len_diff > 0) {
557	# do the same thing, but this time with bottom half
558
559	my $half = int($#diff_arr / 2);
560	my @bottomhalf = @diff_arr[$half+1 .. $#diff_arr];
561
562	doprint "Set tmp config to be good config with some bad config values\n";
563
564	my %tmp_config = make_half \@bottomhalf, \%good_configs,
565	    \%bad_configs, "bottom", "bad";
566
567	$runtest = process_new_config \%tmp_config, \%new_configs,
568			    \%good_configs, \%bad_configs;
569
570	if (!$runtest) {
571	    doprint "Set tmp config to be bad config with some good config values\n";
572
573	    my %tmp_config = make_half \@bottomhalf, \%bad_configs,
574		\%good_configs, "bottom", "good";
575
576	    $runtest = process_new_config \%tmp_config, \%new_configs,
577		\%good_configs, \%bad_configs;
578	}
579    }
580
581    if ($runtest) {
582	make_oldconfig;
583	doprint "READY TO TEST .config IN $build\n";
584	return 0;
585    }
586
587    doprint "\n%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%\n";
588    doprint "Hmm, can't make any more changes without making good == bad?\n";
589    doprint "Difference between good (+) and bad (-)\n";
590
591    foreach my $item (keys %bad_configs) {
592	if (!defined($good_configs{$item})) {
593	    print_config "-", $bad_configs{$item};
594	}
595    }
596
597    foreach my $item (keys %good_configs) {
598	next if (!defined($bad_configs{$item}));
599	if ($good_configs{$item} ne $bad_configs{$item}) {
600	    print_config_compare $good_configs{$item}, $bad_configs{$item};
601	}
602    }
603
604    foreach my $item (keys %good_configs) {
605	if (!defined($bad_configs{$item})) {
606	    print_config "+", $good_configs{$item};
607	}
608    }
609    return -1;
610}
611
612sub config_bisect {
613    my ($good_config, $bad_config) = @_;
614    my $ret;
615
616    my %good_configs;
617    my %bad_configs;
618    my %tmp_configs;
619
620    doprint "Run good configs through make oldconfig\n";
621    assign_configs \%tmp_configs, $good_config;
622    create_config "$good_config", \%tmp_configs;
623    assign_configs \%good_configs, $output_config;
624
625    doprint "Run bad configs through make oldconfig\n";
626    assign_configs \%tmp_configs, $bad_config;
627    create_config "$bad_config", \%tmp_configs;
628    assign_configs \%bad_configs, $output_config;
629
630    save_config \%good_configs, $good_config;
631    save_config \%bad_configs, $bad_config;
632
633    return run_config_bisect \%good_configs, \%bad_configs;
634}
635
636while ($#ARGV >= 0) {
637    if ($ARGV[0] !~ m/^-/) {
638	last;
639    }
640    my $opt = shift @ARGV;
641
642    if ($opt eq "-b") {
643	$val = shift @ARGV;
644	if (!defined($val)) {
645	    die "-b requires value\n";
646	}
647	$build = $val;
648    }
649
650    elsif ($opt eq "-l") {
651	$val = shift @ARGV;
652	if (!defined($val)) {
653	    die "-l requires value\n";
654	}
655	$tree = $val;
656    }
657
658    elsif ($opt eq "-r") {
659	$reset_bisect = 1;
660    }
661
662    elsif ($opt eq "-h") {
663	usage;
664    }
665
666    else {
667	die "Unknow option $opt\n";
668    }
669}
670
671$build = $tree if (!defined($build));
672
673$tree = expand_path $tree;
674$build = expand_path $build;
675
676if ( ! -d $tree ) {
677    die "$tree not a directory\n";
678}
679
680if ( ! -d $build ) {
681    die "$build not a directory\n";
682}
683
684usage if $#ARGV < 1;
685
686if ($#ARGV == 1) {
687    $start = 1;
688} elsif ($#ARGV == 2) {
689    $val = $ARGV[2];
690    if ($val ne "good" && $val ne "bad") {
691	die "Unknown command '$val', bust be either \"good\" or \"bad\"\n";
692    }
693} else {
694    usage;
695}
696
697my $good_start = expand_path $ARGV[0];
698my $bad_start = expand_path $ARGV[1];
699
700my $good = "$good_start.tmp";
701my $bad = "$bad_start.tmp";
702
703$make = "make";
704
705if ($build ne $tree) {
706    $make = "make O=$build"
707}
708
709$output_config = "$build/.config";
710
711if ($start) {
712    if ( ! -f $good_start ) {
713	die "$good_start not found\n";
714    }
715    if ( ! -f $bad_start ) {
716	die "$bad_start not found\n";
717    }
718    if ( -f $good || -f $bad ) {
719	my $p = "";
720
721	if ( -f $good ) {
722	    $p = "$good exists\n";
723	}
724
725	if ( -f $bad ) {
726	    $p = "$p$bad exists\n";
727	}
728
729	if (!defined($reset_bisect)) {
730	    if (!read_yn "${p}Overwrite and start new bisect anyway?") {
731		exit (-1);
732	    }
733	}
734    }
735    run_command "cp $good_start $good" or die "failed to copy to $good\n";
736    run_command "cp $bad_start $bad" or die "faield to copy to $bad\n";
737} else {
738    if ( ! -f $good ) {
739	die "Can not find file $good\n";
740    }
741    if ( ! -f $bad ) {
742	die "Can not find file $bad\n";
743    }
744    if ($val eq "good") {
745	run_command "cp $output_config $good" or die "failed to copy $config to $good\n";
746    } elsif ($val eq "bad") {
747	run_command "cp $output_config $bad" or die "failed to copy $config to $bad\n";
748    }
749}
750
751chdir $tree || die "can't change directory to $tree";
752
753my $ret = config_bisect $good, $bad;
754
755if (!$ret) {
756    exit(0);
757}
758
759if ($ret > 0) {
760    doprint "Cleaning temp files\n";
761    run_command "rm $good";
762    run_command "rm $bad";
763    exit(1);
764} else {
765    doprint "See good and bad configs for details:\n";
766    doprint "good: $good\n";
767    doprint "bad:  $bad\n";
768    doprint "%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%\n";
769}
770exit(2);
771