cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

config-bisect.pl (18104B)


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