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