#!/usr/bin/perl -w# SPDX-License-Identifier: GPL-2.0-only## Copyright 2015 - Steven Rostedt, Red Hat Inc.# Copyright 2017 - Steven Rostedt, VMware, Inc.# # usage:# config-bisect.pl [options] good-config bad-config [good|bad]# # Compares a good config to a bad config, then takes half of the diffs# and produces a config that is somewhere between the good config and# the bad config. That is, the resulting config will start with the# good config and will try to make half of the differences of between# the good and bad configs match the bad config. It tries because of# dependencies between the two configs it may not be able to change# exactly half of the configs that are different between the two config# files. # Here's a normal way to use it:## $ cd /path/to/linux/kernel# $ config-bisect.pl /path/to/good/config /path/to/bad/config # This will now pull in good config (blowing away .config in that directory# so do not make that be one of the good or bad configs), and then# build the config with "make oldconfig" to make sure it matches the# current kernel. It will then store the configs in that result for# the good config. It does the same for the bad config as well.# The algorithm will run, merging half of the differences between# the two configs and building them with "make oldconfig" to make sure# the result changes (dependencies may reset changes the tool had made).# It then copies the result of its good config to /path/to/good/config.tmp# and the bad config to /path/to/bad/config.tmp (just appends ".tmp" to the# files passed in). And the ".config" that you should test will be in# directory # After the first run, determine if the result is good or bad then# run the same command appending the result # For good results:# $ config-bisect.pl /path/to/good/config /path/to/bad/config good # For bad results:# $ config-bisect.pl /path/to/good/config /path/to/bad/config bad # Do not change the good-config or bad-config, config-bisect.pl will# copy the good-config to a temp file with the same name as good-config# but with a ".tmp" after it. It will do the same with the bad-config. # If "good" or "bad" is not stated at the end, it will copy the good and# bad configs to the .tmp versions. If a .tmp version already exists, it will# warn before writing over them (-r will not warn, and just write over them).# If the last config is labeled "good", then it will copy it to the good .tmp# version. If the last config is labeled "bad", it will copy it to the bad# .tmp version. It will continue this until it can not merge the two any more# without the result being equal to either the good or bad .tmp configs. my $start = 0;my $val = ""; my $pwd = `pwd`;chomp $pwd;my $tree = $pwd;my $build; my $output_config;my $reset_bisect; sub usage { print << "EOF" usage: config-bisect.pl [-l linux-tree][-b build-dir] good-config bad-config [good|bad] -l [optional] define location of linux-tree (default is current directory) -b [optional] define location to build (O=build-dir) (default is linux-tree) good-config the config that is considered good bad-config the config that does not work "good" add this if the last run produced a good config "bad" add this if the last run produced a bad config If "good" or "bad" is not specified, then it is the start of a new bisect Note, each run will create copy of good and bad configs with ".tmp" appended. EOF; exit(-1);} sub doprint { print @_;} sub dodie { doprint "CRITICAL FAILURE... ", @_, "\n"; die @_, "\n";} sub expand_path { my ($file) = @_; if ($file =~ m,^/,) { return $file; } return "$pwd/$file";} sub read_prompt { my ($cancel, $prompt) = @_; my $ans; for (;;) { if ($cancel) { print "$prompt [y/n/C] "; } else { print "$prompt [y/N] "; } $ans = <STDIN>; chomp $ans; if ($ans =~ /^\s*$/) { if ($cancel) { $ans = "c"; } else { $ans = "n"; } } last if ($ans =~ /^y$/i || $ans =~ /^n$/i); if ($cancel) { last if ($ans =~ /^c$/i); print "Please answer either 'y', 'n' or 'c'.\n"; } else { print "Please answer either 'y' or 'n'.\n"; } } if ($ans =~ /^c/i) { exit; } if ($ans !~ /^y$/i) { return 0; } return 1;} sub read_yn { my ($prompt) = @_; return read_prompt 0, $prompt;} sub read_ync { my ($prompt) = @_; return read_prompt 1, $prompt;} sub run_command { my ($command, $redirect) = @_; my $start_time; my $end_time; my $dord = 0; my $pid; $start_time = time; doprint("$command ... "); $pid = open(CMD, "$command 2>&1 |") or dodie "unable to exec $command"; if (defined($redirect)) { open (RD, ">$redirect") or dodie "failed to write to redirect $redirect"; $dord = 1; } while (<CMD>) { print RD if ($dord); } waitpid($pid, 0); my $failed = $?; close(CMD); close(RD) if ($dord); $end_time = time; my $delta = $end_time - $start_time; if ($delta == 1) { doprint "[1 second] "; } else { doprint "[$delta seconds] "; } if ($failed) { doprint "FAILED!\n"; } else { doprint "SUCCESS\n"; } return !$failed;} ###### CONFIG BISECT ###### # config_ignore holds the configs that were set (or unset) for# a good config and we will ignore these configs for the rest# of a config bisect. These configs stay as they were.my %config_ignore; # config_set holds what all configs were set as.my %config_set; # config_off holds the set of configs that the bad config had disabled.# We need to record them and set them in the .config when running# olddefconfig, because olddefconfig keeps the defaults.my %config_off; # config_off_tmp holds a set of configs to turn off for nowmy @config_off_tmp; # config_list is the set of configs that are being testedmy %config_list;my %null_config; my %dependency; my $make; sub make_oldconfig { if (!run_command "$make olddefconfig") { # Perhaps olddefconfig doesn't exist in this version of the kernel # try oldnoconfig doprint "olddefconfig failed, trying make oldnoconfig\n"; if (!run_command "$make oldnoconfig") { doprint "oldnoconfig failed, trying yes '' | make oldconfig\n"; # try a yes '' | oldconfig run_command "yes '' | $make oldconfig" or dodie "failed make config oldconfig"; } }} sub assign_configs { my ($hash, $config) = @_; doprint "Reading configs from $config\n"; open (IN, $config) or dodie "Failed to read $config"; while (<IN>) { chomp; if (/^((CONFIG\S*)=.*)/) { ${$hash}{$2} = $1; } elsif (/^(# (CONFIG\S*) is not set)/) { ${$hash}{$2} = $1; } } close(IN);} sub process_config_ignore { my ($config) = @_; assign_configs \%config_ignore, $config;} sub get_dependencies { my ($config) = @_; my $arr = $dependency{$config}; if (!defined($arr)) { return (); } my @deps = @{$arr}; foreach my $dep (@{$arr}) { print "ADD DEP $dep\n"; @deps = (@deps, get_dependencies $dep); } return @deps;} sub save_config { my ($pc, $file) = @_; my %configs = %{$pc}; doprint "Saving configs into $file\n"; open(OUT, ">$file") or dodie "Can not write to $file"; foreach my $config (keys %configs) { print OUT "$configs{$config}\n"; } close(OUT);} sub create_config { my ($name, $pc) = @_; doprint "Creating old config from $name configs\n"; save_config $pc, $output_config; make_oldconfig;} # compare two config hashes, and return configs with different vals.# It returns B's config values, but you can use A to see what A was.sub diff_config_vals { my ($pa, $pb) = @_; # crappy Perl way to pass in hashes. my %a = %{$pa}; my %b = %{$pb}; my %ret; foreach my $item (keys %a) { if (defined($b{$item}) && $b{$item} ne $a{$item}) { $ret{$item} = $b{$item}; } } return %ret;} # compare two config hashes and return the configs in B but not Asub diff_configs { my ($pa, $pb) = @_; my %ret; # crappy Perl way to pass in hashes. my %a = %{$pa}; my %b = %{$pb}; foreach my $item (keys %b) { if (!defined($a{$item})) { $ret{$item} = $b{$item}; } } return %ret;} # return if two configs are equal or not# 0 is equal +1 b has something a does not# +1 if a and b have a different item.# -1 if a has something b does notsub compare_configs { my ($pa, $pb) = @_; my %ret; # crappy Perl way to pass in hashes. my %a = %{$pa}; my %b = %{$pb}; foreach my $item (keys %b) { if (!defined($a{$item})) { return 1; } if ($a{$item} ne $b{$item}) { return 1; } } foreach my $item (keys %a) { if (!defined($b{$item})) { return -1; } } return 0;} sub process_failed { my ($config) = @_; doprint "\n\n***************************************\n"; doprint "Found bad config: $config\n"; doprint "***************************************\n\n";} sub process_new_config { my ($tc, $nc, $gc, $bc) = @_; my %tmp_config = %{$tc}; my %good_configs = %{$gc}; my %bad_configs = %{$bc}; my %new_configs; my $runtest = 1; my $ret; create_config "tmp_configs", \%tmp_config; assign_configs \%new_configs, $output_config; $ret = compare_configs \%new_configs, \%bad_configs; if (!$ret) { doprint "New config equals bad config, try next test\n"; $runtest = 0; } if ($runtest) { $ret = compare_configs \%new_configs, \%good_configs; if (!$ret) { doprint "New config equals good config, try next test\n"; $runtest = 0; } } %{$nc} = %new_configs; return $runtest;} sub convert_config { my ($config) = @_; if ($config =~ /^# (.*) is not set/) { $config = "$1=n"; } $config =~ s/^CONFIG_//; return $config;} sub print_config { my ($sym, $config) = @_; $config = convert_config $config; doprint "$sym$config\n";} sub print_config_compare { my ($good_config, $bad_config) = @_; $good_config = convert_config $good_config; $bad_config = convert_config $bad_config; my $good_value = $good_config; my $bad_value = $bad_config; $good_value =~ s/(.*)=//; my $config = $1; $bad_value =~ s/.*=//; doprint " $config $good_value -> $bad_value\n";} # Pass in:# $phalf: half of the configs names you want to add# $oconfigs: The orginial configs to start with# $sconfigs: The source to update $oconfigs with (from $phalf)# $which: The name of which half that is updating (top / bottom)# $type: The name of the source type (good / bad)sub make_half { my ($phalf, $oconfigs, $sconfigs, $which, $type) = @_; my @half = @{$phalf}; my %orig_configs = %{$oconfigs}; my %source_configs = %{$sconfigs}; my %tmp_config = %orig_configs; doprint "Settings bisect with $which half of $type configs:\n"; foreach my $item (@half) { doprint "Updating $item to $source_configs{$item}\n"; $tmp_config{$item} = $source_configs{$item}; } return %tmp_config;} sub run_config_bisect { my ($pgood, $pbad) = @_; my %good_configs = %{$pgood}; my %bad_configs = %{$pbad}; my %diff_configs = diff_config_vals \%good_configs, \%bad_configs; my %b_configs = diff_configs \%good_configs, \%bad_configs; my %g_configs = diff_configs \%bad_configs, \%good_configs; # diff_arr is what is in both good and bad but are different (y->n) my @diff_arr = keys %diff_configs; my $len_diff = $#diff_arr + 1; Lines 1–500 of 769. Load more
#!/usr/bin/perl -w # SPDX-License-Identifier: GPL-2.0-only # # Copyright 2015 - Steven Rostedt, Red Hat Inc. # Copyright 2017 - Steven Rostedt, VMware, Inc. # # usage: # config-bisect.pl [options] good-config bad-config [good|bad] # # Compares a good config to a bad config, then takes half of the diffs # and produces a config that is somewhere between the good config and # the bad config. That is, the resulting config will start with the # good config and will try to make half of the differences of between # the good and bad configs match the bad config. It tries because of # dependencies between the two configs it may not be able to change # exactly half of the configs that are different between the two config # files. # Here's a normal way to use it: # # $ cd /path/to/linux/kernel # $ config-bisect.pl /path/to/good/config /path/to/bad/config # This will now pull in good config (blowing away .config in that directory # so do not make that be one of the good or bad configs), and then # build the config with "make oldconfig" to make sure it matches the # current kernel. It will then store the configs in that result for # the good config. It does the same for the bad config as well. # The algorithm will run, merging half of the differences between # the two configs and building them with "make oldconfig" to make sure # the result changes (dependencies may reset changes the tool had made). # It then copies the result of its good config to /path/to/good/config.tmp # and the bad config to /path/to/bad/config.tmp (just appends ".tmp" to the # files passed in). And the ".config" that you should test will be in # directory # After the first run, determine if the result is good or bad then # run the same command appending the result # For good results: # $ config-bisect.pl /path/to/good/config /path/to/bad/config good # For bad results: # $ config-bisect.pl /path/to/good/config /path/to/bad/config bad # Do not change the good-config or bad-config, config-bisect.pl will # copy the good-config to a temp file with the same name as good-config # but with a ".tmp" after it. It will do the same with the bad-config. # If "good" or "bad" is not stated at the end, it will copy the good and # bad configs to the .tmp versions. If a .tmp version already exists, it will # warn before writing over them (-r will not warn, and just write over them). # If the last config is labeled "good", then it will copy it to the good .tmp # version. If the last config is labeled "bad", it will copy it to the bad # .tmp version. It will continue this until it can not merge the two any more # without the result being equal to either the good or bad .tmp configs. my $start = 0; my $val = ""; my $pwd = `pwd`; chomp $pwd; my $tree = $pwd; my $build; my $output_config; my $reset_bisect; sub usage { print << "EOF" usage: config-bisect.pl [-l linux-tree][-b build-dir] good-config bad-config [good|bad] -l [optional] define location of linux-tree (default is current directory) -b [optional] define location to build (O=build-dir) (default is linux-tree) good-config the config that is considered good bad-config the config that does not work "good" add this if the last run produced a good config "bad" add this if the last run produced a bad config If "good" or "bad" is not specified, then it is the start of a new bisect Note, each run will create copy of good and bad configs with ".tmp" appended. EOF ; exit(-1); } sub doprint { print @_; } sub dodie { doprint "CRITICAL FAILURE... ", @_, "\n"; die @_, "\n"; } sub expand_path { my ($file) = @_; if ($file =~ m,^/,) { return $file; } return "$pwd/$file"; } sub read_prompt { my ($cancel, $prompt) = @_; my $ans; for (;;) { if ($cancel) { print "$prompt [y/n/C] "; } else { print "$prompt [y/N] "; } $ans = <STDIN>; chomp $ans; if ($ans =~ /^\s*$/) { if ($cancel) { $ans = "c"; } else { $ans = "n"; } } last if ($ans =~ /^y$/i || $ans =~ /^n$/i); if ($cancel) { last if ($ans =~ /^c$/i); print "Please answer either 'y', 'n' or 'c'.\n"; } else { print "Please answer either 'y' or 'n'.\n"; } } if ($ans =~ /^c/i) { exit; } if ($ans !~ /^y$/i) { return 0; } return 1; } sub read_yn { my ($prompt) = @_; return read_prompt 0, $prompt; } sub read_ync { my ($prompt) = @_; return read_prompt 1, $prompt; } sub run_command { my ($command, $redirect) = @_; my $start_time; my $end_time; my $dord = 0; my $pid; $start_time = time; doprint("$command ... "); $pid = open(CMD, "$command 2>&1 |") or dodie "unable to exec $command"; if (defined($redirect)) { open (RD, ">$redirect") or dodie "failed to write to redirect $redirect"; $dord = 1; } while (<CMD>) { print RD if ($dord); } waitpid($pid, 0); my $failed = $?; close(CMD); close(RD) if ($dord); $end_time = time; my $delta = $end_time - $start_time; if ($delta == 1) { doprint "[1 second] "; } else { doprint "[$delta seconds] "; } if ($failed) { doprint "FAILED!\n"; } else { doprint "SUCCESS\n"; } return !$failed; } ###### CONFIG BISECT ###### # config_ignore holds the configs that were set (or unset) for # a good config and we will ignore these configs for the rest # of a config bisect. These configs stay as they were. my %config_ignore; # config_set holds what all configs were set as. my %config_set; # config_off holds the set of configs that the bad config had disabled. # We need to record them and set them in the .config when running # olddefconfig, because olddefconfig keeps the defaults. my %config_off; # config_off_tmp holds a set of configs to turn off for now my @config_off_tmp; # config_list is the set of configs that are being tested my %config_list; my %null_config; my %dependency; my $make; sub make_oldconfig { if (!run_command "$make olddefconfig") { # Perhaps olddefconfig doesn't exist in this version of the kernel # try oldnoconfig doprint "olddefconfig failed, trying make oldnoconfig\n"; if (!run_command "$make oldnoconfig") { doprint "oldnoconfig failed, trying yes '' | make oldconfig\n"; # try a yes '' | oldconfig run_command "yes '' | $make oldconfig" or dodie "failed make config oldconfig"; } } } sub assign_configs { my ($hash, $config) = @_; doprint "Reading configs from $config\n"; open (IN, $config) or dodie "Failed to read $config"; while (<IN>) { chomp; if (/^((CONFIG\S*)=.*)/) { ${$hash}{$2} = $1; } elsif (/^(# (CONFIG\S*) is not set)/) { ${$hash}{$2} = $1; } } close(IN); } sub process_config_ignore { my ($config) = @_; assign_configs \%config_ignore, $config; } sub get_dependencies { my ($config) = @_; my $arr = $dependency{$config}; if (!defined($arr)) { return (); } my @deps = @{$arr}; foreach my $dep (@{$arr}) { print "ADD DEP $dep\n"; @deps = (@deps, get_dependencies $dep); } return @deps; } sub save_config { my ($pc, $file) = @_; my %configs = %{$pc}; doprint "Saving configs into $file\n"; open(OUT, ">$file") or dodie "Can not write to $file"; foreach my $config (keys %configs) { print OUT "$configs{$config}\n"; } close(OUT); } sub create_config { my ($name, $pc) = @_; doprint "Creating old config from $name configs\n"; save_config $pc, $output_config; make_oldconfig; } # compare two config hashes, and return configs with different vals. # It returns B's config values, but you can use A to see what A was. sub diff_config_vals { my ($pa, $pb) = @_; # crappy Perl way to pass in hashes. my %a = %{$pa}; my %b = %{$pb}; my %ret; foreach my $item (keys %a) { if (defined($b{$item}) && $b{$item} ne $a{$item}) { $ret{$item} = $b{$item}; } } return %ret; } # compare two config hashes and return the configs in B but not A sub diff_configs { my ($pa, $pb) = @_; my %ret; # crappy Perl way to pass in hashes. my %a = %{$pa}; my %b = %{$pb}; foreach my $item (keys %b) { if (!defined($a{$item})) { $ret{$item} = $b{$item}; } } return %ret; } # return if two configs are equal or not # 0 is equal +1 b has something a does not # +1 if a and b have a different item. # -1 if a has something b does not sub compare_configs { my ($pa, $pb) = @_; my %ret; # crappy Perl way to pass in hashes. my %a = %{$pa}; my %b = %{$pb}; foreach my $item (keys %b) { if (!defined($a{$item})) { return 1; } if ($a{$item} ne $b{$item}) { return 1; } } foreach my $item (keys %a) { if (!defined($b{$item})) { return -1; } } return 0; } sub process_failed { my ($config) = @_; doprint "\n\n***************************************\n"; doprint "Found bad config: $config\n"; doprint "***************************************\n\n"; } sub process_new_config { my ($tc, $nc, $gc, $bc) = @_; my %tmp_config = %{$tc}; my %good_configs = %{$gc}; my %bad_configs = %{$bc}; my %new_configs; my $runtest = 1; my $ret; create_config "tmp_configs", \%tmp_config; assign_configs \%new_configs, $output_config; $ret = compare_configs \%new_configs, \%bad_configs; if (!$ret) { doprint "New config equals bad config, try next test\n"; $runtest = 0; } if ($runtest) { $ret = compare_configs \%new_configs, \%good_configs; if (!$ret) { doprint "New config equals good config, try next test\n"; $runtest = 0; } } %{$nc} = %new_configs; return $runtest; } sub convert_config { my ($config) = @_; if ($config =~ /^# (.*) is not set/) { $config = "$1=n"; } $config =~ s/^CONFIG_//; return $config; } sub print_config { my ($sym, $config) = @_; $config = convert_config $config; doprint "$sym$config\n"; } sub print_config_compare { my ($good_config, $bad_config) = @_; $good_config = convert_config $good_config; $bad_config = convert_config $bad_config; my $good_value = $good_config; my $bad_value = $bad_config; $good_value =~ s/(.*)=//; my $config = $1; $bad_value =~ s/.*=//; doprint " $config $good_value -> $bad_value\n"; } # Pass in: # $phalf: half of the configs names you want to add # $oconfigs: The orginial configs to start with # $sconfigs: The source to update $oconfigs with (from $phalf) # $which: The name of which half that is updating (top / bottom) # $type: The name of the source type (good / bad) sub make_half { my ($phalf, $oconfigs, $sconfigs, $which, $type) = @_; my @half = @{$phalf}; my %orig_configs = %{$oconfigs}; my %source_configs = %{$sconfigs}; my %tmp_config = %orig_configs; doprint "Settings bisect with $which half of $type configs:\n"; foreach my $item (@half) { doprint "Updating $item to $source_configs{$item}\n"; $tmp_config{$item} = $source_configs{$item}; } return %tmp_config; } sub run_config_bisect { my ($pgood, $pbad) = @_; my %good_configs = %{$pgood}; my %bad_configs = %{$pbad}; my %diff_configs = diff_config_vals \%good_configs, \%bad_configs; my %b_configs = diff_configs \%good_configs, \%bad_configs; my %g_configs = diff_configs \%bad_configs, \%good_configs; # diff_arr is what is in both good and bad but are different (y->n) my @diff_arr = keys %diff_configs; my $len_diff = $#diff_arr + 1;