Back to home page

OSCL-LXR

 
 

    


0001 #!/usr/bin/perl -w
0002 # SPDX-License-Identifier: GPL-2.0-only
0003 #
0004 # Copyright 2015 - Steven Rostedt, Red Hat Inc.
0005 # Copyright 2017 - Steven Rostedt, VMware, Inc.
0006 #
0007 
0008 # usage:
0009 #  config-bisect.pl [options] good-config bad-config [good|bad]
0010 #
0011 
0012 # Compares a good config to a bad config, then takes half of the diffs
0013 # and produces a config that is somewhere between the good config and
0014 # the bad config. That is, the resulting config will start with the
0015 # good config and will try to make half of the differences of between
0016 # the good and bad configs match the bad config. It tries because of
0017 # dependencies between the two configs it may not be able to change
0018 # exactly half of the configs that are different between the two config
0019 # files.
0020 
0021 # Here's a normal way to use it:
0022 #
0023 #  $ cd /path/to/linux/kernel
0024 #  $ config-bisect.pl /path/to/good/config /path/to/bad/config
0025 
0026 # This will now pull in good config (blowing away .config in that directory
0027 # so do not make that be one of the good or bad configs), and then
0028 # build the config with "make oldconfig" to make sure it matches the
0029 # current kernel. It will then store the configs in that result for
0030 # the good config. It does the same for the bad config as well.
0031 # The algorithm will run, merging half of the differences between
0032 # the two configs and building them with "make oldconfig" to make sure
0033 # the result changes (dependencies may reset changes the tool had made).
0034 # It then copies the result of its good config to /path/to/good/config.tmp
0035 # and the bad config to /path/to/bad/config.tmp (just appends ".tmp" to the
0036 # files passed in). And the ".config" that you should test will be in
0037 # directory
0038 
0039 # After the first run, determine if the result is good or bad then
0040 # run the same command appending the result
0041 
0042 # For good results:
0043 #  $ config-bisect.pl /path/to/good/config /path/to/bad/config good
0044 
0045 # For bad results:
0046 #  $ config-bisect.pl /path/to/good/config /path/to/bad/config bad
0047 
0048 # Do not change the good-config or bad-config, config-bisect.pl will
0049 # copy the good-config to a temp file with the same name as good-config
0050 # but with a ".tmp" after it. It will do the same with the bad-config.
0051 
0052 # If "good" or "bad" is not stated at the end, it will copy the good and
0053 # bad configs to the .tmp versions. If a .tmp version already exists, it will
0054 # warn before writing over them (-r will not warn, and just write over them).
0055 # If the last config is labeled "good", then it will copy it to the good .tmp
0056 # version. If the last config is labeled "bad", it will copy it to the bad
0057 # .tmp version. It will continue this until it can not merge the two any more
0058 # without the result being equal to either the good or bad .tmp configs.
0059 
0060 my $start = 0;
0061 my $val = "";
0062 
0063 my $pwd = `pwd`;
0064 chomp $pwd;
0065 my $tree = $pwd;
0066 my $build;
0067 
0068 my $output_config;
0069 my $reset_bisect;
0070 
0071 sub usage {
0072     print << "EOF"
0073 
0074 usage: config-bisect.pl [-l linux-tree][-b build-dir] good-config bad-config [good|bad]
0075   -l [optional] define location of linux-tree (default is current directory)
0076   -b [optional] define location to build (O=build-dir) (default is linux-tree)
0077   good-config the config that is considered good
0078   bad-config the config that does not work
0079   "good" add this if the last run produced a good config
0080   "bad" add this if the last run produced a bad config
0081   If "good" or "bad" is not specified, then it is the start of a new bisect
0082 
0083   Note, each run will create copy of good and bad configs with ".tmp" appended.
0084 
0085 EOF
0086 ;
0087 
0088     exit(-1);
0089 }
0090 
0091 sub doprint {
0092     print @_;
0093 }
0094 
0095 sub dodie {
0096     doprint "CRITICAL FAILURE... ", @_, "\n";
0097 
0098     die @_, "\n";
0099 }
0100 
0101 sub expand_path {
0102     my ($file) = @_;
0103 
0104     if ($file =~ m,^/,) {
0105     return $file;
0106     }
0107     return "$pwd/$file";
0108 }
0109 
0110 sub read_prompt {
0111     my ($cancel, $prompt) = @_;
0112 
0113     my $ans;
0114 
0115     for (;;) {
0116     if ($cancel) {
0117         print "$prompt [y/n/C] ";
0118     } else {
0119         print "$prompt [y/N] ";
0120     }
0121     $ans = <STDIN>;
0122     chomp $ans;
0123     if ($ans =~ /^\s*$/) {
0124         if ($cancel) {
0125         $ans = "c";
0126         } else {
0127         $ans = "n";
0128         }
0129     }
0130     last if ($ans =~ /^y$/i || $ans =~ /^n$/i);
0131     if ($cancel) {
0132         last if ($ans =~ /^c$/i);
0133         print "Please answer either 'y', 'n' or 'c'.\n";
0134     } else {
0135         print "Please answer either 'y' or 'n'.\n";
0136     }
0137     }
0138     if ($ans =~ /^c/i) {
0139     exit;
0140     }
0141     if ($ans !~ /^y$/i) {
0142     return 0;
0143     }
0144     return 1;
0145 }
0146 
0147 sub read_yn {
0148     my ($prompt) = @_;
0149 
0150     return read_prompt 0, $prompt;
0151 }
0152 
0153 sub read_ync {
0154     my ($prompt) = @_;
0155 
0156     return read_prompt 1, $prompt;
0157 }
0158 
0159 sub run_command {
0160     my ($command, $redirect) = @_;
0161     my $start_time;
0162     my $end_time;
0163     my $dord = 0;
0164     my $pid;
0165 
0166     $start_time = time;
0167 
0168     doprint("$command ... ");
0169 
0170     $pid = open(CMD, "$command 2>&1 |") or
0171     dodie "unable to exec $command";
0172 
0173     if (defined($redirect)) {
0174     open (RD, ">$redirect") or
0175         dodie "failed to write to redirect $redirect";
0176     $dord = 1;
0177     }
0178 
0179     while (<CMD>) {
0180     print RD  if ($dord);
0181     }
0182 
0183     waitpid($pid, 0);
0184     my $failed = $?;
0185 
0186     close(CMD);
0187     close(RD)  if ($dord);
0188 
0189     $end_time = time;
0190     my $delta = $end_time - $start_time;
0191 
0192     if ($delta == 1) {
0193     doprint "[1 second] ";
0194     } else {
0195     doprint "[$delta seconds] ";
0196     }
0197 
0198     if ($failed) {
0199     doprint "FAILED!\n";
0200     } else {
0201     doprint "SUCCESS\n";
0202     }
0203 
0204     return !$failed;
0205 }
0206 
0207 ###### CONFIG BISECT ######
0208 
0209 # config_ignore holds the configs that were set (or unset) for
0210 # a good config and we will ignore these configs for the rest
0211 # of a config bisect. These configs stay as they were.
0212 my %config_ignore;
0213 
0214 # config_set holds what all configs were set as.
0215 my %config_set;
0216 
0217 # config_off holds the set of configs that the bad config had disabled.
0218 # We need to record them and set them in the .config when running
0219 # olddefconfig, because olddefconfig keeps the defaults.
0220 my %config_off;
0221 
0222 # config_off_tmp holds a set of configs to turn off for now
0223 my @config_off_tmp;
0224 
0225 # config_list is the set of configs that are being tested
0226 my %config_list;
0227 my %null_config;
0228 
0229 my %dependency;
0230 
0231 my $make;
0232 
0233 sub make_oldconfig {
0234 
0235     if (!run_command "$make olddefconfig") {
0236     # Perhaps olddefconfig doesn't exist in this version of the kernel
0237     # try oldnoconfig
0238     doprint "olddefconfig failed, trying make oldnoconfig\n";
0239     if (!run_command "$make oldnoconfig") {
0240         doprint "oldnoconfig failed, trying yes '' | make oldconfig\n";
0241         # try a yes '' | oldconfig
0242         run_command "yes '' | $make oldconfig" or
0243         dodie "failed make config oldconfig";
0244     }
0245     }
0246 }
0247 
0248 sub assign_configs {
0249     my ($hash, $config) = @_;
0250 
0251     doprint "Reading configs from $config\n";
0252 
0253     open (IN, $config)
0254     or dodie "Failed to read $config";
0255 
0256     while (<IN>) {
0257     chomp;
0258     if (/^((CONFIG\S*)=.*)/) {
0259         ${$hash}{$2} = $1;
0260     } elsif (/^(# (CONFIG\S*) is not set)/) {
0261         ${$hash}{$2} = $1;
0262     }
0263     }
0264 
0265     close(IN);
0266 }
0267 
0268 sub process_config_ignore {
0269     my ($config) = @_;
0270 
0271     assign_configs \%config_ignore, $config;
0272 }
0273 
0274 sub get_dependencies {
0275     my ($config) = @_;
0276 
0277     my $arr = $dependency{$config};
0278     if (!defined($arr)) {
0279     return ();
0280     }
0281 
0282     my @deps = @{$arr};
0283 
0284     foreach my $dep (@{$arr}) {
0285     print "ADD DEP $dep\n";
0286     @deps = (@deps, get_dependencies $dep);
0287     }
0288 
0289     return @deps;
0290 }
0291 
0292 sub save_config {
0293     my ($pc, $file) = @_;
0294 
0295     my %configs = %{$pc};
0296 
0297     doprint "Saving configs into $file\n";
0298 
0299     open(OUT, ">$file") or dodie "Can not write to $file";
0300 
0301     foreach my $config (keys %configs) {
0302     print OUT "$configs{$config}\n";
0303     }
0304     close(OUT);
0305 }
0306 
0307 sub create_config {
0308     my ($name, $pc) = @_;
0309 
0310     doprint "Creating old config from $name configs\n";
0311 
0312     save_config $pc, $output_config;
0313 
0314     make_oldconfig;
0315 }
0316 
0317 # compare two config hashes, and return configs with different vals.
0318 # It returns B's config values, but you can use A to see what A was.
0319 sub diff_config_vals {
0320     my ($pa, $pb) = @_;
0321 
0322     # crappy Perl way to pass in hashes.
0323     my %a = %{$pa};
0324     my %b = %{$pb};
0325 
0326     my %ret;
0327 
0328     foreach my $item (keys %a) {
0329     if (defined($b{$item}) && $b{$item} ne $a{$item}) {
0330         $ret{$item} = $b{$item};
0331     }
0332     }
0333 
0334     return %ret;
0335 }
0336 
0337 # compare two config hashes and return the configs in B but not A
0338 sub diff_configs {
0339     my ($pa, $pb) = @_;
0340 
0341     my %ret;
0342 
0343     # crappy Perl way to pass in hashes.
0344     my %a = %{$pa};
0345     my %b = %{$pb};
0346 
0347     foreach my $item (keys %b) {
0348     if (!defined($a{$item})) {
0349         $ret{$item} = $b{$item};
0350     }
0351     }
0352 
0353     return %ret;
0354 }
0355 
0356 # return if two configs are equal or not
0357 # 0 is equal +1 b has something a does not
0358 # +1 if a and b have a different item.
0359 # -1 if a has something b does not
0360 sub compare_configs {
0361     my ($pa, $pb) = @_;
0362 
0363     my %ret;
0364 
0365     # crappy Perl way to pass in hashes.
0366     my %a = %{$pa};
0367     my %b = %{$pb};
0368 
0369     foreach my $item (keys %b) {
0370     if (!defined($a{$item})) {
0371         return 1;
0372     }
0373     if ($a{$item} ne $b{$item}) {
0374         return 1;
0375     }
0376     }
0377 
0378     foreach my $item (keys %a) {
0379     if (!defined($b{$item})) {
0380         return -1;
0381     }
0382     }
0383 
0384     return 0;
0385 }
0386 
0387 sub process_failed {
0388     my ($config) = @_;
0389 
0390     doprint "\n\n***************************************\n";
0391     doprint "Found bad config: $config\n";
0392     doprint "***************************************\n\n";
0393 }
0394 
0395 sub process_new_config {
0396     my ($tc, $nc, $gc, $bc) = @_;
0397 
0398     my %tmp_config = %{$tc};
0399     my %good_configs = %{$gc};
0400     my %bad_configs = %{$bc};
0401 
0402     my %new_configs;
0403 
0404     my $runtest = 1;
0405     my $ret;
0406 
0407     create_config "tmp_configs", \%tmp_config;
0408     assign_configs \%new_configs, $output_config;
0409 
0410     $ret = compare_configs \%new_configs, \%bad_configs;
0411     if (!$ret) {
0412     doprint "New config equals bad config, try next test\n";
0413     $runtest = 0;
0414     }
0415 
0416     if ($runtest) {
0417     $ret = compare_configs \%new_configs, \%good_configs;
0418     if (!$ret) {
0419         doprint "New config equals good config, try next test\n";
0420         $runtest = 0;
0421     }
0422     }
0423 
0424     %{$nc} = %new_configs;
0425 
0426     return $runtest;
0427 }
0428 
0429 sub convert_config {
0430     my ($config) = @_;
0431 
0432     if ($config =~ /^# (.*) is not set/) {
0433     $config = "$1=n";
0434     }
0435 
0436     $config =~ s/^CONFIG_//;
0437     return $config;
0438 }
0439 
0440 sub print_config {
0441     my ($sym, $config) = @_;
0442 
0443     $config = convert_config $config;
0444     doprint "$sym$config\n";
0445 }
0446 
0447 sub print_config_compare {
0448     my ($good_config, $bad_config) = @_;
0449 
0450     $good_config = convert_config $good_config;
0451     $bad_config = convert_config $bad_config;
0452 
0453     my $good_value = $good_config;
0454     my $bad_value = $bad_config;
0455     $good_value =~ s/(.*)=//;
0456     my $config = $1;
0457 
0458     $bad_value =~ s/.*=//;
0459 
0460     doprint " $config $good_value -> $bad_value\n";
0461 }
0462 
0463 # Pass in:
0464 # $phalf: half of the configs names you want to add
0465 # $oconfigs: The orginial configs to start with
0466 # $sconfigs: The source to update $oconfigs with (from $phalf)
0467 # $which: The name of which half that is updating (top / bottom)
0468 # $type: The name of the source type (good / bad)
0469 sub make_half {
0470     my ($phalf, $oconfigs, $sconfigs, $which, $type) = @_;
0471 
0472     my @half = @{$phalf};
0473     my %orig_configs = %{$oconfigs};
0474     my %source_configs = %{$sconfigs};
0475 
0476     my %tmp_config = %orig_configs;
0477 
0478     doprint "Settings bisect with $which half of $type configs:\n";
0479     foreach my $item (@half) {
0480     doprint "Updating $item to $source_configs{$item}\n";
0481     $tmp_config{$item} = $source_configs{$item};
0482     }
0483 
0484     return %tmp_config;
0485 }
0486 
0487 sub run_config_bisect {
0488     my ($pgood, $pbad) = @_;
0489 
0490     my %good_configs = %{$pgood};
0491     my %bad_configs = %{$pbad};
0492 
0493     my %diff_configs = diff_config_vals \%good_configs, \%bad_configs;
0494     my %b_configs = diff_configs \%good_configs, \%bad_configs;
0495     my %g_configs = diff_configs \%bad_configs, \%good_configs;
0496 
0497     # diff_arr is what is in both good and bad but are different (y->n)
0498     my @diff_arr = keys %diff_configs;
0499     my $len_diff = $#diff_arr + 1;
0500 
0501     # b_arr is what is in bad but not in good (has depends)
0502     my @b_arr = keys %b_configs;
0503     my $len_b = $#b_arr + 1;
0504 
0505     # g_arr is what is in good but not in bad
0506     my @g_arr = keys %g_configs;
0507     my $len_g = $#g_arr + 1;
0508 
0509     my $runtest = 0;
0510     my %new_configs;
0511     my $ret;
0512 
0513     # Look at the configs that are different between good and bad.
0514     # This does not include those that depend on other configs
0515     #  (configs depending on other configs that are not set would
0516     #   not show up even as a "# CONFIG_FOO is not set"
0517 
0518 
0519     doprint "# of configs to check:             $len_diff\n";
0520     doprint "# of configs showing only in good: $len_g\n";
0521     doprint "# of configs showing only in bad:  $len_b\n";
0522 
0523     if ($len_diff > 0) {
0524     # Now test for different values
0525 
0526     doprint "Configs left to check:\n";
0527     doprint "  Good Config\t\t\tBad Config\n";
0528     doprint "  -----------\t\t\t----------\n";
0529     foreach my $item (@diff_arr) {
0530         doprint "  $good_configs{$item}\t$bad_configs{$item}\n";
0531     }
0532 
0533     my $half = int($#diff_arr / 2);
0534     my @tophalf = @diff_arr[0 .. $half];
0535 
0536     doprint "Set tmp config to be good config with some bad config values\n";
0537 
0538     my %tmp_config = make_half \@tophalf, \%good_configs,
0539         \%bad_configs, "top", "bad";
0540 
0541     $runtest = process_new_config \%tmp_config, \%new_configs,
0542                 \%good_configs, \%bad_configs;
0543 
0544     if (!$runtest) {
0545         doprint "Set tmp config to be bad config with some good config values\n";
0546 
0547         my %tmp_config = make_half \@tophalf, \%bad_configs,
0548         \%good_configs, "top", "good";
0549 
0550         $runtest = process_new_config \%tmp_config, \%new_configs,
0551         \%good_configs, \%bad_configs;
0552     }
0553     }
0554 
0555     if (!$runtest && $len_diff > 0) {
0556     # do the same thing, but this time with bottom half
0557 
0558     my $half = int($#diff_arr / 2);
0559     my @bottomhalf = @diff_arr[$half+1 .. $#diff_arr];
0560 
0561     doprint "Set tmp config to be good config with some bad config values\n";
0562 
0563     my %tmp_config = make_half \@bottomhalf, \%good_configs,
0564         \%bad_configs, "bottom", "bad";
0565 
0566     $runtest = process_new_config \%tmp_config, \%new_configs,
0567                 \%good_configs, \%bad_configs;
0568 
0569     if (!$runtest) {
0570         doprint "Set tmp config to be bad config with some good config values\n";
0571 
0572         my %tmp_config = make_half \@bottomhalf, \%bad_configs,
0573         \%good_configs, "bottom", "good";
0574 
0575         $runtest = process_new_config \%tmp_config, \%new_configs,
0576         \%good_configs, \%bad_configs;
0577     }
0578     }
0579 
0580     if ($runtest) {
0581     make_oldconfig;
0582     doprint "READY TO TEST .config IN $build\n";
0583     return 0;
0584     }
0585 
0586     doprint "\n%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%\n";
0587     doprint "Hmm, can't make any more changes without making good == bad?\n";
0588     doprint "Difference between good (+) and bad (-)\n";
0589 
0590     foreach my $item (keys %bad_configs) {
0591     if (!defined($good_configs{$item})) {
0592         print_config "-", $bad_configs{$item};
0593     }
0594     }
0595 
0596     foreach my $item (keys %good_configs) {
0597     next if (!defined($bad_configs{$item}));
0598     if ($good_configs{$item} ne $bad_configs{$item}) {
0599         print_config_compare $good_configs{$item}, $bad_configs{$item};
0600     }
0601     }
0602 
0603     foreach my $item (keys %good_configs) {
0604     if (!defined($bad_configs{$item})) {
0605         print_config "+", $good_configs{$item};
0606     }
0607     }
0608     return -1;
0609 }
0610 
0611 sub config_bisect {
0612     my ($good_config, $bad_config) = @_;
0613     my $ret;
0614 
0615     my %good_configs;
0616     my %bad_configs;
0617     my %tmp_configs;
0618 
0619     doprint "Run good configs through make oldconfig\n";
0620     assign_configs \%tmp_configs, $good_config;
0621     create_config "$good_config", \%tmp_configs;
0622     assign_configs \%good_configs, $output_config;
0623 
0624     doprint "Run bad configs through make oldconfig\n";
0625     assign_configs \%tmp_configs, $bad_config;
0626     create_config "$bad_config", \%tmp_configs;
0627     assign_configs \%bad_configs, $output_config;
0628 
0629     save_config \%good_configs, $good_config;
0630     save_config \%bad_configs, $bad_config;
0631 
0632     return run_config_bisect \%good_configs, \%bad_configs;
0633 }
0634 
0635 while ($#ARGV >= 0) {
0636     if ($ARGV[0] !~ m/^-/) {
0637     last;
0638     }
0639     my $opt = shift @ARGV;
0640 
0641     if ($opt eq "-b") {
0642     $val = shift @ARGV;
0643     if (!defined($val)) {
0644         die "-b requires value\n";
0645     }
0646     $build = $val;
0647     }
0648 
0649     elsif ($opt eq "-l") {
0650     $val = shift @ARGV;
0651     if (!defined($val)) {
0652         die "-l requires value\n";
0653     }
0654     $tree = $val;
0655     }
0656 
0657     elsif ($opt eq "-r") {
0658     $reset_bisect = 1;
0659     }
0660 
0661     elsif ($opt eq "-h") {
0662     usage;
0663     }
0664 
0665     else {
0666     die "Unknown option $opt\n";
0667     }
0668 }
0669 
0670 $build = $tree if (!defined($build));
0671 
0672 $tree = expand_path $tree;
0673 $build = expand_path $build;
0674 
0675 if ( ! -d $tree ) {
0676     die "$tree not a directory\n";
0677 }
0678 
0679 if ( ! -d $build ) {
0680     die "$build not a directory\n";
0681 }
0682 
0683 usage if $#ARGV < 1;
0684 
0685 if ($#ARGV == 1) {
0686     $start = 1;
0687 } elsif ($#ARGV == 2) {
0688     $val = $ARGV[2];
0689     if ($val ne "good" && $val ne "bad") {
0690     die "Unknown command '$val', bust be either \"good\" or \"bad\"\n";
0691     }
0692 } else {
0693     usage;
0694 }
0695 
0696 my $good_start = expand_path $ARGV[0];
0697 my $bad_start = expand_path $ARGV[1];
0698 
0699 my $good = "$good_start.tmp";
0700 my $bad = "$bad_start.tmp";
0701 
0702 $make = "make";
0703 
0704 if ($build ne $tree) {
0705     $make = "make O=$build"
0706 }
0707 
0708 $output_config = "$build/.config";
0709 
0710 if ($start) {
0711     if ( ! -f $good_start ) {
0712     die "$good_start not found\n";
0713     }
0714     if ( ! -f $bad_start ) {
0715     die "$bad_start not found\n";
0716     }
0717     if ( -f $good || -f $bad ) {
0718     my $p = "";
0719 
0720     if ( -f $good ) {
0721         $p = "$good exists\n";
0722     }
0723 
0724     if ( -f $bad ) {
0725         $p = "$p$bad exists\n";
0726     }
0727 
0728     if (!defined($reset_bisect)) {
0729         if (!read_yn "${p}Overwrite and start new bisect anyway?") {
0730         exit (-1);
0731         }
0732     }
0733     }
0734     run_command "cp $good_start $good" or die "failed to copy to $good\n";
0735     run_command "cp $bad_start $bad" or die "failed to copy to $bad\n";
0736 } else {
0737     if ( ! -f $good ) {
0738     die "Can not find file $good\n";
0739     }
0740     if ( ! -f $bad ) {
0741     die "Can not find file $bad\n";
0742     }
0743     if ($val eq "good") {
0744     run_command "cp $output_config $good" or die "failed to copy $config to $good\n";
0745     } elsif ($val eq "bad") {
0746     run_command "cp $output_config $bad" or die "failed to copy $config to $bad\n";
0747     }
0748 }
0749 
0750 chdir $tree || die "can't change directory to $tree";
0751 
0752 my $ret = config_bisect $good, $bad;
0753 
0754 if (!$ret) {
0755     exit(0);
0756 }
0757 
0758 if ($ret > 0) {
0759     doprint "Cleaning temp files\n";
0760     run_command "rm $good";
0761     run_command "rm $bad";
0762     exit(1);
0763 } else {
0764     doprint "See good and bad configs for details:\n";
0765     doprint "good: $good\n";
0766     doprint "bad:  $bad\n";
0767     doprint "%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%\n";
0768 }
0769 exit(2);