Home | History | Annotate | Download | only in bin
      1 #!/usr/bin/perl -w
      2 #
      3 #   Copyright (c) International Business Machines  Corp., 2002,2012
      4 #
      5 #   This program is free software;  you can redistribute it and/or modify
      6 #   it under the terms of the GNU General Public License as published by
      7 #   the Free Software Foundation; either version 2 of the License, or (at
      8 #   your option) any later version.
      9 #
     10 #   This program is distributed in the hope that it will be useful, but
     11 #   WITHOUT ANY WARRANTY;  without even the implied warranty of
     12 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13 #   General Public License for more details.                 
     14 #
     15 #   You should have received a copy of the GNU General Public License
     16 #   along with this program;  if not, write to the Free Software
     17 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
     18 #
     19 #
     20 # geninfo
     21 #
     22 #   This script generates .info files from data files as created by code
     23 #   instrumented with gcc's built-in profiling mechanism. Call it with
     24 #   --help and refer to the geninfo man page to get information on usage
     25 #   and available options.
     26 #
     27 #
     28 # Authors:
     29 #   2002-08-23 created by Peter Oberparleiter <Peter.Oberparleiter (at] de.ibm.com>
     30 #                         IBM Lab Boeblingen
     31 #        based on code by Manoj Iyer <manjo (at] mail.utexas.edu> and
     32 #                         Megan Bock <mbock (at] us.ibm.com>
     33 #                         IBM Austin
     34 #   2002-09-05 / Peter Oberparleiter: implemented option that allows file list
     35 #   2003-04-16 / Peter Oberparleiter: modified read_gcov so that it can also
     36 #                parse the new gcov format which is to be introduced in gcc 3.3
     37 #   2003-04-30 / Peter Oberparleiter: made info write to STDERR, not STDOUT
     38 #   2003-07-03 / Peter Oberparleiter: added line checksum support, added
     39 #                --no-checksum
     40 #   2003-09-18 / Nigel Hinds: capture branch coverage data from GCOV
     41 #   2003-12-11 / Laurent Deniel: added --follow option
     42 #                workaround gcov (<= 3.2.x) bug with empty .da files
     43 #   2004-01-03 / Laurent Deniel: Ignore empty .bb files
     44 #   2004-02-16 / Andreas Krebbel: Added support for .gcno/.gcda files and
     45 #                gcov versioning
     46 #   2004-08-09 / Peter Oberparleiter: added configuration file support
     47 #   2008-07-14 / Tom Zoerner: added --function-coverage command line option
     48 #   2008-08-13 / Peter Oberparleiter: modified function coverage
     49 #                implementation (now enabled per default)
     50 #
     51 
     52 use strict;
     53 use File::Basename; 
     54 use File::Spec::Functions qw /abs2rel catdir file_name_is_absolute splitdir
     55 			      splitpath catpath/;
     56 use Getopt::Long;
     57 use Digest::MD5 qw(md5_base64);
     58 if( $^O eq "msys" )
     59 {
     60 	require File::Spec::Win32;
     61 }
     62 
     63 # Constants
     64 our $lcov_version	= 'LCOV version 1.10';
     65 our $lcov_url		= "http://ltp.sourceforge.net/coverage/lcov.php";
     66 our $gcov_tool		= "gcov";
     67 our $tool_name		= basename($0);
     68 
     69 our $GCOV_VERSION_4_7_0	= 0x40700;
     70 our $GCOV_VERSION_3_4_0	= 0x30400;
     71 our $GCOV_VERSION_3_3_0	= 0x30300;
     72 our $GCNO_FUNCTION_TAG	= 0x01000000;
     73 our $GCNO_LINES_TAG	= 0x01450000;
     74 our $GCNO_FILE_MAGIC	= 0x67636e6f;
     75 our $BBG_FILE_MAGIC	= 0x67626267;
     76 
     77 # Error classes which users may specify to ignore during processing
     78 our $ERROR_GCOV		= 0;
     79 our $ERROR_SOURCE	= 1;
     80 our $ERROR_GRAPH	= 2;
     81 our %ERROR_ID = (
     82 	"gcov" => $ERROR_GCOV,
     83 	"source" => $ERROR_SOURCE,
     84 	"graph" => $ERROR_GRAPH,
     85 );
     86 
     87 our $EXCL_START = "LCOV_EXCL_START";
     88 our $EXCL_STOP = "LCOV_EXCL_STOP";
     89 our $EXCL_LINE = "LCOV_EXCL_LINE";
     90 
     91 # Compatibility mode values
     92 our $COMPAT_VALUE_OFF	= 0;
     93 our $COMPAT_VALUE_ON	= 1;
     94 our $COMPAT_VALUE_AUTO	= 2;
     95 
     96 # Compatibility mode value names
     97 our %COMPAT_NAME_TO_VALUE = (
     98 	"off"	=> $COMPAT_VALUE_OFF,
     99 	"on"	=> $COMPAT_VALUE_ON,
    100 	"auto"	=> $COMPAT_VALUE_AUTO,
    101 );
    102 
    103 # Compatiblity modes
    104 our $COMPAT_MODE_LIBTOOL	= 1 << 0;
    105 our $COMPAT_MODE_HAMMER		= 1 << 1;
    106 our $COMPAT_MODE_SPLIT_CRC	= 1 << 2;
    107 
    108 # Compatibility mode names
    109 our %COMPAT_NAME_TO_MODE = (
    110 	"libtool"	=> $COMPAT_MODE_LIBTOOL,
    111 	"hammer"	=> $COMPAT_MODE_HAMMER,
    112 	"split_crc"	=> $COMPAT_MODE_SPLIT_CRC,
    113 	"android_4_4_0"	=> $COMPAT_MODE_SPLIT_CRC,
    114 );
    115 
    116 # Map modes to names
    117 our %COMPAT_MODE_TO_NAME = (
    118 	$COMPAT_MODE_LIBTOOL	=> "libtool",
    119 	$COMPAT_MODE_HAMMER	=> "hammer",
    120 	$COMPAT_MODE_SPLIT_CRC	=> "split_crc",
    121 );
    122 
    123 # Compatibility mode default values
    124 our %COMPAT_MODE_DEFAULTS = (
    125 	$COMPAT_MODE_LIBTOOL	=> $COMPAT_VALUE_ON,
    126 	$COMPAT_MODE_HAMMER	=> $COMPAT_VALUE_AUTO,
    127 	$COMPAT_MODE_SPLIT_CRC	=> $COMPAT_VALUE_AUTO,
    128 );
    129 
    130 # Compatibility mode auto-detection routines
    131 sub compat_hammer_autodetect();
    132 our %COMPAT_MODE_AUTO = (
    133 	$COMPAT_MODE_HAMMER	=> \&compat_hammer_autodetect,
    134 	$COMPAT_MODE_SPLIT_CRC	=> 1,	# will be done later
    135 );
    136 
    137 our $BR_LINE		= 0;
    138 our $BR_BLOCK		= 1;
    139 our $BR_BRANCH		= 2;
    140 our $BR_TAKEN		= 3;
    141 our $BR_VEC_ENTRIES	= 4;
    142 our $BR_VEC_WIDTH	= 32;
    143 
    144 our $UNNAMED_BLOCK	= 9999;
    145 
    146 # Prototypes
    147 sub print_usage(*);
    148 sub gen_info($);
    149 sub process_dafile($$);
    150 sub match_filename($@);
    151 sub solve_ambiguous_match($$$);
    152 sub split_filename($);
    153 sub solve_relative_path($$);
    154 sub read_gcov_header($);
    155 sub read_gcov_file($);
    156 sub info(@);
    157 sub get_gcov_version();
    158 sub system_no_output($@);
    159 sub read_config($);
    160 sub apply_config($);
    161 sub get_exclusion_data($);
    162 sub apply_exclusion_data($$);
    163 sub process_graphfile($$);
    164 sub filter_fn_name($);
    165 sub warn_handler($);
    166 sub die_handler($);
    167 sub graph_error($$);
    168 sub graph_expect($);
    169 sub graph_read(*$;$$);
    170 sub graph_skip(*$;$);
    171 sub sort_uniq(@);
    172 sub sort_uniq_lex(@);
    173 sub graph_cleanup($);
    174 sub graph_find_base($);
    175 sub graph_from_bb($$$);
    176 sub graph_add_order($$$);
    177 sub read_bb_word(*;$);
    178 sub read_bb_value(*;$);
    179 sub read_bb_string(*$);
    180 sub read_bb($);
    181 sub read_bbg_word(*;$);
    182 sub read_bbg_value(*;$);
    183 sub read_bbg_string(*);
    184 sub read_bbg_lines_record(*$$$$$);
    185 sub read_bbg($);
    186 sub read_gcno_word(*;$$);
    187 sub read_gcno_value(*$;$$);
    188 sub read_gcno_string(*$);
    189 sub read_gcno_lines_record(*$$$$$$);
    190 sub determine_gcno_split_crc($$$);
    191 sub read_gcno_function_record(*$$$$);
    192 sub read_gcno($);
    193 sub get_gcov_capabilities();
    194 sub get_overall_line($$$$);
    195 sub print_overall_rate($$$$$$$$$);
    196 sub br_gvec_len($);
    197 sub br_gvec_get($$);
    198 sub debug($);
    199 sub int_handler();
    200 sub parse_ignore_errors(@);
    201 sub is_external($);
    202 sub compat_name($);
    203 sub parse_compat_modes($);
    204 sub is_compat($);
    205 sub is_compat_auto($);
    206 
    207 
    208 # Global variables
    209 our $gcov_version;
    210 our $gcov_version_string;
    211 our $graph_file_extension;
    212 our $data_file_extension;
    213 our @data_directory;
    214 our $test_name = "";
    215 our $quiet;
    216 our $help;
    217 our $output_filename;
    218 our $base_directory;
    219 our $version;
    220 our $follow;
    221 our $checksum;
    222 our $no_checksum;
    223 our $opt_compat_libtool;
    224 our $opt_no_compat_libtool;
    225 our $rc_adjust_src_path;# Regexp specifying parts to remove from source path
    226 our $adjust_src_pattern;
    227 our $adjust_src_replace;
    228 our $adjust_testname;
    229 our $config;		# Configuration file contents
    230 our @ignore_errors;	# List of errors to ignore (parameter)
    231 our @ignore;		# List of errors to ignore (array)
    232 our $initial;
    233 our $no_recursion = 0;
    234 our $maxdepth;
    235 our $no_markers = 0;
    236 our $opt_derive_func_data = 0;
    237 our $opt_external = 1;
    238 our $opt_no_external;
    239 our $debug = 0;
    240 our $gcov_caps;
    241 our @gcov_options;
    242 our @internal_dirs;
    243 our $opt_config_file;
    244 our $opt_gcov_all_blocks = 1;
    245 our $opt_compat;
    246 our %opt_rc;
    247 our %compat_value;
    248 our $gcno_split_crc;
    249 our $func_coverage = 1;
    250 our $br_coverage = 0;
    251 our $rc_auto_base = 1;
    252 
    253 our $cwd = `pwd`;
    254 chomp($cwd);
    255 
    256 
    257 #
    258 # Code entry point
    259 #
    260 
    261 # Register handler routine to be called when interrupted
    262 $SIG{"INT"} = \&int_handler;
    263 $SIG{__WARN__} = \&warn_handler;
    264 $SIG{__DIE__} = \&die_handler;
    265 
    266 # Prettify version string
    267 $lcov_version =~ s/\$\s*Revision\s*:?\s*(\S+)\s*\$/$1/;
    268 
    269 # Set LANG so that gcov output will be in a unified format
    270 $ENV{"LANG"} = "C";
    271 
    272 # Check command line for a configuration file name
    273 Getopt::Long::Configure("pass_through", "no_auto_abbrev");
    274 GetOptions("config-file=s" => \$opt_config_file,
    275 	   "rc=s%" => \%opt_rc);
    276 Getopt::Long::Configure("default");
    277 
    278 # Read configuration file if available
    279 if (defined($opt_config_file)) {
    280 	$config = read_config($opt_config_file);
    281 } elsif (defined($ENV{"HOME"}) && (-r $ENV{"HOME"}."/.lcovrc"))
    282 {
    283 	$config = read_config($ENV{"HOME"}."/.lcovrc");
    284 }
    285 elsif (-r "/etc/lcovrc")
    286 {
    287 	$config = read_config("/etc/lcovrc");
    288 }
    289 
    290 if ($config || %opt_rc)
    291 {
    292 	# Copy configuration file and --rc values to variables
    293 	apply_config({
    294 		"geninfo_gcov_tool"		=> \$gcov_tool,
    295 		"geninfo_adjust_testname"	=> \$adjust_testname,
    296 		"geninfo_checksum"		=> \$checksum,
    297 		"geninfo_no_checksum"		=> \$no_checksum, # deprecated
    298 		"geninfo_compat_libtool"	=> \$opt_compat_libtool,
    299 		"geninfo_external"		=> \$opt_external,
    300 		"geninfo_gcov_all_blocks"	=> \$opt_gcov_all_blocks,
    301 		"geninfo_compat"		=> \$opt_compat,
    302 		"geninfo_adjust_src_path"	=> \$rc_adjust_src_path,
    303 		"geninfo_auto_base"		=> \$rc_auto_base,
    304 		"lcov_function_coverage"	=> \$func_coverage,
    305 		"lcov_branch_coverage"		=> \$br_coverage,
    306 	});
    307 
    308 	# Merge options
    309 	if (defined($no_checksum))
    310 	{
    311 		$checksum = ($no_checksum ? 0 : 1);
    312 		$no_checksum = undef;
    313 	}
    314 
    315 	# Check regexp
    316 	if (defined($rc_adjust_src_path)) {
    317 		my ($pattern, $replace) = split(/\s*=>\s*/,
    318 						$rc_adjust_src_path);
    319 		local $SIG{__DIE__};
    320 		eval '$adjust_src_pattern = qr>'.$pattern.'>;';
    321 		if (!defined($adjust_src_pattern)) {
    322 			my $msg = $@;
    323 
    324 			chomp($msg);
    325 			$msg =~ s/at \(eval.*$//;
    326 			warn("WARNING: invalid pattern in ".
    327 			     "geninfo_adjust_src_path: $msg\n");
    328 		} elsif (!defined($replace)) {
    329 			# If no replacement is specified, simply remove pattern
    330 			$adjust_src_replace = "";
    331 		} else {
    332 			$adjust_src_replace = $replace;
    333 		}
    334 	}
    335 }
    336 
    337 # Parse command line options
    338 if (!GetOptions("test-name|t=s" => \$test_name,
    339 		"output-filename|o=s" => \$output_filename,
    340 		"checksum" => \$checksum,
    341 		"no-checksum" => \$no_checksum,
    342 		"base-directory|b=s" => \$base_directory,
    343 		"version|v" =>\$version,
    344 		"quiet|q" => \$quiet,
    345 		"help|h|?" => \$help,
    346 		"follow|f" => \$follow,
    347 		"compat-libtool" => \$opt_compat_libtool,
    348 		"no-compat-libtool" => \$opt_no_compat_libtool,
    349 		"gcov-tool=s" => \$gcov_tool,
    350 		"ignore-errors=s" => \@ignore_errors,
    351 		"initial|i" => \$initial,
    352 		"no-recursion" => \$no_recursion,
    353 		"no-markers" => \$no_markers,
    354 		"derive-func-data" => \$opt_derive_func_data,
    355 		"debug" => \$debug,
    356 		"external" => \$opt_external,
    357 		"no-external" => \$opt_no_external,
    358 		"compat=s" => \$opt_compat,
    359 		"config-file=s" => \$opt_config_file,
    360 		"rc=s%" => \%opt_rc,
    361 		))
    362 {
    363 	print(STDERR "Use $tool_name --help to get usage information\n");
    364 	exit(1);
    365 }
    366 else
    367 {
    368 	# Merge options
    369 	if (defined($no_checksum))
    370 	{
    371 		$checksum = ($no_checksum ? 0 : 1);
    372 		$no_checksum = undef;
    373 	}
    374 
    375 	if (defined($opt_no_compat_libtool))
    376 	{
    377 		$opt_compat_libtool = ($opt_no_compat_libtool ? 0 : 1);
    378 		$opt_no_compat_libtool = undef;
    379 	}
    380 
    381 	if (defined($opt_no_external)) {
    382 		$opt_external = 0;
    383 		$opt_no_external = undef;
    384 	}
    385 }
    386 
    387 @data_directory = @ARGV;
    388 
    389 # Check for help option
    390 if ($help)
    391 {
    392 	print_usage(*STDOUT);
    393 	exit(0);
    394 }
    395 
    396 # Check for version option
    397 if ($version)
    398 {
    399 	print("$tool_name: $lcov_version\n");
    400 	exit(0);
    401 }
    402 
    403 # Check gcov tool
    404 if (system_no_output(3, $gcov_tool, "--help") == -1)
    405 {
    406 	die("ERROR: need tool $gcov_tool!\n");
    407 }
    408 
    409 ($gcov_version, $gcov_version_string) = get_gcov_version();
    410 
    411 # Determine gcov options
    412 $gcov_caps = get_gcov_capabilities();
    413 push(@gcov_options, "-b") if ($gcov_caps->{'branch-probabilities'} &&
    414 			      ($br_coverage || $func_coverage));
    415 push(@gcov_options, "-c") if ($gcov_caps->{'branch-counts'} &&
    416 			      $br_coverage);
    417 push(@gcov_options, "-a") if ($gcov_caps->{'all-blocks'} &&
    418 			      $opt_gcov_all_blocks && $br_coverage);
    419 push(@gcov_options, "-p") if ($gcov_caps->{'preserve-paths'});
    420 
    421 # Determine compatibility modes
    422 parse_compat_modes($opt_compat);
    423 
    424 # Determine which errors the user wants us to ignore
    425 parse_ignore_errors(@ignore_errors);
    426 
    427 # Make sure test names only contain valid characters
    428 if ($test_name =~ s/\W/_/g)
    429 {
    430 	warn("WARNING: invalid characters removed from testname!\n");
    431 }
    432 
    433 # Adjust test name to include uname output if requested
    434 if ($adjust_testname)
    435 {
    436 	$test_name .= "__".`uname -a`;
    437 	$test_name =~ s/\W/_/g;
    438 }
    439 
    440 # Make sure base_directory contains an absolute path specification
    441 if ($base_directory)
    442 {
    443 	$base_directory = solve_relative_path($cwd, $base_directory);
    444 }
    445 
    446 # Check for follow option
    447 if ($follow)
    448 {
    449 	$follow = "-follow"
    450 }
    451 else
    452 {
    453 	$follow = "";
    454 }
    455 
    456 # Determine checksum mode
    457 if (defined($checksum))
    458 {
    459 	# Normalize to boolean
    460 	$checksum = ($checksum ? 1 : 0);
    461 }
    462 else
    463 {
    464 	# Default is off
    465 	$checksum = 0;
    466 }
    467 
    468 # Determine max depth for recursion
    469 if ($no_recursion)
    470 {
    471 	$maxdepth = "-maxdepth 1";
    472 }
    473 else
    474 {
    475 	$maxdepth = "";
    476 }
    477 
    478 # Check for directory name
    479 if (!@data_directory)
    480 {
    481 	die("No directory specified\n".
    482 	    "Use $tool_name --help to get usage information\n");
    483 }
    484 else
    485 {
    486 	foreach (@data_directory)
    487 	{
    488 		stat($_);
    489 		if (!-r _)
    490 		{
    491 			die("ERROR: cannot read $_!\n");
    492 		}
    493 	}
    494 }
    495 
    496 if ($gcov_version < $GCOV_VERSION_3_4_0)
    497 {
    498 	if (is_compat($COMPAT_MODE_HAMMER))
    499 	{
    500 		$data_file_extension = ".da";
    501 		$graph_file_extension = ".bbg";
    502 	}
    503 	else
    504 	{
    505 		$data_file_extension = ".da";
    506 		$graph_file_extension = ".bb";
    507 	}
    508 }
    509 else
    510 {
    511 	$data_file_extension = ".gcda";
    512 	$graph_file_extension = ".gcno";
    513 }	
    514 
    515 # Check output filename
    516 if (defined($output_filename) && ($output_filename ne "-"))
    517 {
    518 	# Initially create output filename, data is appended
    519 	# for each data file processed
    520 	local *DUMMY_HANDLE;
    521 	open(DUMMY_HANDLE, ">", $output_filename)
    522 		or die("ERROR: cannot create $output_filename!\n");
    523 	close(DUMMY_HANDLE);
    524 
    525 	# Make $output_filename an absolute path because we're going
    526 	# to change directories while processing files
    527 	if (!($output_filename =~ /^\/(.*)$/))
    528 	{
    529 		$output_filename = $cwd."/".$output_filename;
    530 	}
    531 }
    532 
    533 # Build list of directories to identify external files
    534 foreach my $entry(@data_directory, $base_directory) {
    535 	next if (!defined($entry));
    536 	push(@internal_dirs, solve_relative_path($cwd, $entry));
    537 }
    538 
    539 # Do something
    540 foreach my $entry (@data_directory) {
    541 	gen_info($entry);
    542 }
    543 
    544 if ($initial && $br_coverage) {
    545 	warn("Note: --initial does not generate branch coverage ".
    546 	     "data\n");
    547 }
    548 info("Finished .info-file creation\n");
    549 
    550 exit(0);
    551 
    552 
    553 
    554 #
    555 # print_usage(handle)
    556 #
    557 # Print usage information.
    558 #
    559 
    560 sub print_usage(*)
    561 {
    562 	local *HANDLE = $_[0];
    563 
    564 	print(HANDLE <<END_OF_USAGE);
    565 Usage: $tool_name [OPTIONS] DIRECTORY
    566 
    567 Traverse DIRECTORY and create a .info file for each data file found. Note
    568 that you may specify more than one directory, all of which are then processed
    569 sequentially.
    570 
    571   -h, --help                        Print this help, then exit
    572   -v, --version                     Print version number, then exit
    573   -q, --quiet                       Do not print progress messages
    574   -i, --initial                     Capture initial zero coverage data
    575   -t, --test-name NAME              Use test case name NAME for resulting data
    576   -o, --output-filename OUTFILE     Write data only to OUTFILE
    577   -f, --follow                      Follow links when searching .da/.gcda files
    578   -b, --base-directory DIR          Use DIR as base directory for relative paths
    579       --(no-)checksum               Enable (disable) line checksumming
    580       --(no-)compat-libtool         Enable (disable) libtool compatibility mode
    581       --gcov-tool TOOL              Specify gcov tool location
    582       --ignore-errors ERROR         Continue after ERROR (gcov, source, graph)
    583       --no-recursion                Exclude subdirectories from processing
    584       --no-markers                  Ignore exclusion markers in source code
    585       --derive-func-data            Generate function data from line data
    586       --(no-)external               Include (ignore) data for external files
    587       --config-file FILENAME        Specify configuration file location
    588       --rc SETTING=VALUE            Override configuration file setting
    589       --compat MODE=on|off|auto     Set compat MODE (libtool, hammer, split_crc)
    590 
    591 For more information see: $lcov_url
    592 END_OF_USAGE
    593 	;
    594 }
    595 
    596 #
    597 # get_common_prefix(min_dir, filenames)
    598 #
    599 # Return the longest path prefix shared by all filenames. MIN_DIR specifies
    600 # the minimum number of directories that a filename may have after removing
    601 # the prefix.
    602 #
    603 
    604 sub get_common_prefix($@)
    605 {
    606 	my ($min_dir, @files) = @_;
    607 	my $file;
    608 	my @prefix;
    609 	my $i;
    610 
    611 	foreach $file (@files) {
    612 		my ($v, $d, $f) = splitpath($file);
    613 		my @comp = splitdir($d);
    614 
    615 		if (!@prefix) {
    616 			@prefix = @comp;
    617 			next;
    618 		}
    619 		for ($i = 0; $i < scalar(@comp) && $i < scalar(@prefix); $i++) {
    620 			if ($comp[$i] ne $prefix[$i] ||
    621 			    ((scalar(@comp) - ($i + 1)) <= $min_dir)) {
    622 				delete(@prefix[$i..scalar(@prefix)]);
    623 				last;
    624 			}
    625 		}
    626 	}
    627 
    628 	return catdir(@prefix);
    629 }
    630 
    631 #
    632 # gen_info(directory)
    633 #
    634 # Traverse DIRECTORY and create a .info file for each data file found.
    635 # The .info file contains TEST_NAME in the following format:
    636 #
    637 #   TN:<test name>
    638 #
    639 # For each source file name referenced in the data file, there is a section
    640 # containing source code and coverage data:
    641 #
    642 #   SF:<absolute path to the source file>
    643 #   FN:<line number of function start>,<function name> for each function
    644 #   DA:<line number>,<execution count> for each instrumented line
    645 #   LH:<number of lines with an execution count> greater than 0
    646 #   LF:<number of instrumented lines>
    647 #
    648 # Sections are separated by:
    649 #
    650 #   end_of_record
    651 #
    652 # In addition to the main source code file there are sections for each
    653 # #included file containing executable code. Note that the absolute path
    654 # of a source file is generated by interpreting the contents of the respective
    655 # graph file. Relative filenames are prefixed with the directory in which the
    656 # graph file is found. Note also that symbolic links to the graph file will be
    657 # resolved so that the actual file path is used instead of the path to a link.
    658 # This approach is necessary for the mechanism to work with the /proc/gcov
    659 # files.
    660 #
    661 # Die on error.
    662 #
    663 
    664 sub gen_info($)
    665 {
    666 	my $directory = $_[0];
    667 	my @file_list;
    668 	my $file;
    669 	my $prefix;
    670 	my $type;
    671 	my $ext;
    672 
    673 	if ($initial) {
    674 		$type = "graph";
    675 		$ext = $graph_file_extension;
    676 	} else {
    677 		$type = "data";
    678 		$ext = $data_file_extension;
    679 	}
    680 
    681 	if (-d $directory)
    682 	{
    683 		info("Scanning $directory for $ext files ...\n");
    684 
    685 		@file_list = `find "$directory" $maxdepth $follow -name \\*$ext -type f 2>/dev/null`;
    686 		chomp(@file_list);
    687 		@file_list or
    688 			die("ERROR: no $ext files found in $directory!\n");
    689 		$prefix = get_common_prefix(1, @file_list);
    690 		info("Found %d %s files in %s\n", $#file_list+1, $type,
    691 		     $directory);
    692 	}
    693 	else
    694 	{
    695 		@file_list = ($directory);
    696 		$prefix = "";
    697 	}
    698 
    699 	# Process all files in list
    700 	foreach $file (@file_list) {
    701 		# Process file
    702 		if ($initial) {
    703 			process_graphfile($file, $prefix);
    704 		} else {
    705 			process_dafile($file, $prefix);
    706 		}
    707 	}
    708 }
    709 
    710 
    711 #
    712 # derive_data(contentdata, funcdata, bbdata)
    713 #
    714 # Calculate function coverage data by combining line coverage data and the
    715 # list of lines belonging to a function.
    716 #
    717 # contentdata: [ instr1, count1, source1, instr2, count2, source2, ... ]
    718 # instr<n>: Instrumentation flag for line n
    719 # count<n>: Execution count for line n
    720 # source<n>: Source code for line n
    721 #
    722 # funcdata: [ count1, func1, count2, func2, ... ]
    723 # count<n>: Execution count for function number n
    724 # func<n>: Function name for function number n
    725 #
    726 # bbdata: function_name -> [ line1, line2, ... ]
    727 # line<n>: Line number belonging to the corresponding function
    728 #
    729 
    730 sub derive_data($$$)
    731 {
    732 	my ($contentdata, $funcdata, $bbdata) = @_;
    733 	my @gcov_content = @{$contentdata};
    734 	my @gcov_functions = @{$funcdata};
    735 	my %fn_count;
    736 	my %ln_fn;
    737 	my $line;
    738 	my $maxline;
    739 	my %fn_name;
    740 	my $fn;
    741 	my $count;
    742 
    743 	if (!defined($bbdata)) {
    744 		return @gcov_functions;
    745 	}
    746 
    747 	# First add existing function data
    748 	while (@gcov_functions) {
    749 		$count = shift(@gcov_functions);
    750 		$fn = shift(@gcov_functions);
    751 
    752 		$fn_count{$fn} = $count;
    753 	}
    754 
    755 	# Convert line coverage data to function data
    756 	foreach $fn (keys(%{$bbdata})) {
    757 		my $line_data = $bbdata->{$fn};
    758 		my $line;
    759 		my $fninstr = 0;
    760 
    761 		if ($fn eq "") {
    762 			next;
    763 		}
    764 		# Find the lowest line count for this function
    765 		$count = 0;
    766 		foreach $line (@$line_data) {
    767 			my $linstr = $gcov_content[ ( $line - 1 ) * 3 + 0 ];
    768 			my $lcount = $gcov_content[ ( $line - 1 ) * 3 + 1 ];
    769 
    770 			next if (!$linstr);
    771 			$fninstr = 1;
    772 			if (($lcount > 0) &&
    773 			    (($count == 0) || ($lcount < $count))) {
    774 				$count = $lcount;
    775 			}
    776 		}
    777 		next if (!$fninstr);
    778 		$fn_count{$fn} = $count;
    779 	}
    780 
    781 
    782 	# Check if we got data for all functions
    783 	foreach $fn (keys(%fn_name)) {
    784 		if ($fn eq "") {
    785 			next;
    786 		}
    787 		if (defined($fn_count{$fn})) {
    788 			next;
    789 		}
    790 		warn("WARNING: no derived data found for function $fn\n");
    791 	}
    792 
    793 	# Convert hash to list in @gcov_functions format
    794 	foreach $fn (sort(keys(%fn_count))) {
    795 		push(@gcov_functions, $fn_count{$fn}, $fn);
    796 	}
    797 
    798 	return @gcov_functions;
    799 }
    800 
    801 #
    802 # get_filenames(directory, pattern)
    803 #
    804 # Return a list of filenames found in directory which match the specified
    805 # pattern.
    806 #
    807 # Die on error.
    808 #
    809 
    810 sub get_filenames($$)
    811 {
    812 	my ($dirname, $pattern) = @_;
    813 	my @result;
    814 	my $directory;
    815 	local *DIR;
    816 
    817 	opendir(DIR, $dirname) or
    818 		die("ERROR: cannot read directory $dirname\n");
    819 	while ($directory = readdir(DIR)) {
    820 		push(@result, $directory) if ($directory =~ /$pattern/);
    821 	}
    822 	closedir(DIR);
    823 
    824 	return @result;
    825 }
    826 
    827 #
    828 # process_dafile(da_filename, dir)
    829 #
    830 # Create a .info file for a single data file.
    831 #
    832 # Die on error.
    833 #
    834 
    835 sub process_dafile($$)
    836 {
    837 	my ($file, $dir) = @_;
    838 	my $da_filename;	# Name of data file to process
    839 	my $da_dir;		# Directory of data file
    840 	my $source_dir;		# Directory of source file
    841 	my $da_basename;	# data filename without ".da/.gcda" extension
    842 	my $bb_filename;	# Name of respective graph file
    843 	my $bb_basename;	# Basename of the original graph file
    844 	my $graph;		# Contents of graph file
    845 	my $instr;		# Contents of graph file part 2
    846 	my $gcov_error;		# Error code of gcov tool
    847 	my $object_dir;		# Directory containing all object files
    848 	my $source_filename;	# Name of a source code file
    849 	my $gcov_file;		# Name of a .gcov file
    850 	my @gcov_content;	# Content of a .gcov file
    851 	my $gcov_branches;	# Branch content of a .gcov file
    852 	my @gcov_functions;	# Function calls of a .gcov file
    853 	my @gcov_list;		# List of generated .gcov files
    854 	my $line_number;	# Line number count
    855 	my $lines_hit;		# Number of instrumented lines hit
    856 	my $lines_found;	# Number of instrumented lines found
    857 	my $funcs_hit;		# Number of instrumented functions hit
    858 	my $funcs_found;	# Number of instrumented functions found
    859 	my $br_hit;
    860 	my $br_found;
    861 	my $source;		# gcov source header information
    862 	my $object;		# gcov object header information
    863 	my @matches;		# List of absolute paths matching filename
    864 	my @unprocessed;	# List of unprocessed source code files
    865 	my $base_dir;		# Base directory for current file
    866 	my @tmp_links;		# Temporary links to be cleaned up
    867 	my @result;
    868 	my $index;
    869 	my $da_renamed;		# If data file is to be renamed
    870 	local *INFO_HANDLE;
    871 
    872 	info("Processing %s\n", abs2rel($file, $dir));
    873 	# Get path to data file in absolute and normalized form (begins with /,
    874 	# contains no more ../ or ./)
    875 	$da_filename = solve_relative_path($cwd, $file);
    876 
    877 	# Get directory and basename of data file
    878 	($da_dir, $da_basename) = split_filename($da_filename);
    879 
    880 	$source_dir = $da_dir;
    881 	if (is_compat($COMPAT_MODE_LIBTOOL)) {
    882 		# Avoid files from .libs dirs 	 
    883 		$source_dir =~ s/\.libs$//;
    884 	}
    885 
    886 	if (-z $da_filename)
    887 	{
    888 		$da_renamed = 1;
    889 	}
    890 	else
    891 	{
    892 		$da_renamed = 0;
    893 	}
    894 
    895 	# Construct base_dir for current file
    896 	if ($base_directory)
    897 	{
    898 		$base_dir = $base_directory;
    899 	}
    900 	else
    901 	{
    902 		$base_dir = $source_dir;
    903 	}
    904 
    905 	# Check for writable $base_dir (gcov will try to write files there)
    906 	stat($base_dir);
    907 	if (!-w _)
    908 	{
    909 		die("ERROR: cannot write to directory $base_dir!\n");
    910 	}
    911 
    912 	# Construct name of graph file
    913 	$bb_basename = $da_basename.$graph_file_extension;
    914 	$bb_filename = "$da_dir/$bb_basename";
    915 
    916 	# Find out the real location of graph file in case we're just looking at
    917 	# a link
    918 	while (readlink($bb_filename))
    919 	{
    920 		my $last_dir = dirname($bb_filename);
    921 
    922 		$bb_filename = readlink($bb_filename);
    923 		$bb_filename = solve_relative_path($last_dir, $bb_filename);
    924 	}
    925 
    926 	# Ignore empty graph file (e.g. source file with no statement)
    927 	if (-z $bb_filename)
    928 	{
    929 		warn("WARNING: empty $bb_filename (skipped)\n");
    930 		return;
    931 	}
    932 
    933 	# Read contents of graph file into hash. We need it later to find out
    934 	# the absolute path to each .gcov file created as well as for
    935 	# information about functions and their source code positions.
    936 	if ($gcov_version < $GCOV_VERSION_3_4_0)
    937 	{
    938 		if (is_compat($COMPAT_MODE_HAMMER))
    939 		{
    940 			($instr, $graph) = read_bbg($bb_filename);
    941 		}
    942 		else
    943 		{
    944 			($instr, $graph) = read_bb($bb_filename);
    945 		}
    946 	} 
    947 	else
    948 	{
    949 		($instr, $graph) = read_gcno($bb_filename);
    950 	} 
    951 
    952 	# Try to find base directory automatically if requested by user
    953 	if ($rc_auto_base) {
    954 		$base_dir = find_base_from_graph($base_dir, $instr, $graph);
    955 	}
    956 
    957 	($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph);
    958 
    959 	# Set $object_dir to real location of object files. This may differ
    960 	# from $da_dir if the graph file is just a link to the "real" object
    961 	# file location.
    962 	$object_dir = dirname($bb_filename);
    963 
    964 	# Is the data file in a different directory? (this happens e.g. with
    965 	# the gcov-kernel patch)
    966 	if ($object_dir ne $da_dir)
    967 	{
    968 		# Need to create link to data file in $object_dir
    969 		system("ln", "-s", $da_filename, 
    970 		       "$object_dir/$da_basename$data_file_extension")
    971 			and die ("ERROR: cannot create link $object_dir/".
    972 				 "$da_basename$data_file_extension!\n");
    973 		push(@tmp_links,
    974 		     "$object_dir/$da_basename$data_file_extension");
    975 		# Need to create link to graph file if basename of link
    976 		# and file are different (CONFIG_MODVERSION compat)
    977 		if ((basename($bb_filename) ne $bb_basename) &&
    978 		    (! -e "$object_dir/$bb_basename")) {
    979 			symlink($bb_filename, "$object_dir/$bb_basename") or
    980 				warn("WARNING: cannot create link ".
    981 				     "$object_dir/$bb_basename\n");
    982 			push(@tmp_links, "$object_dir/$bb_basename");
    983 		}
    984 	}
    985 
    986 	# Change to directory containing data files and apply GCOV
    987 	debug("chdir($base_dir)\n");
    988         chdir($base_dir);
    989 
    990 	if ($da_renamed)
    991 	{
    992 		# Need to rename empty data file to workaround
    993 	        # gcov <= 3.2.x bug (Abort)
    994 		system_no_output(3, "mv", "$da_filename", "$da_filename.ori")
    995 			and die ("ERROR: cannot rename $da_filename\n");
    996 	}
    997 
    998 	# Execute gcov command and suppress standard output
    999 	$gcov_error = system_no_output(1, $gcov_tool, $da_filename,
   1000 				       "-o", $object_dir, @gcov_options);
   1001 
   1002 	if ($da_renamed)
   1003 	{
   1004 		system_no_output(3, "mv", "$da_filename.ori", "$da_filename")
   1005 			and die ("ERROR: cannot rename $da_filename.ori");
   1006 	}
   1007 
   1008 	# Clean up temporary links
   1009 	foreach (@tmp_links) {
   1010 		unlink($_);
   1011 	}
   1012 
   1013 	if ($gcov_error)
   1014 	{
   1015 		if ($ignore[$ERROR_GCOV])
   1016 		{
   1017 			warn("WARNING: GCOV failed for $da_filename!\n");
   1018 			return;
   1019 		}
   1020 		die("ERROR: GCOV failed for $da_filename!\n");
   1021 	}
   1022 
   1023 	# Collect data from resulting .gcov files and create .info file
   1024 	@gcov_list = get_filenames('.', '\.gcov$');
   1025 
   1026 	# Check for files
   1027 	if (!@gcov_list)
   1028 	{
   1029 		warn("WARNING: gcov did not create any files for ".
   1030 		     "$da_filename!\n");
   1031 	}
   1032 
   1033 	# Check whether we're writing to a single file
   1034 	if ($output_filename)
   1035 	{
   1036 		if ($output_filename eq "-")
   1037 		{
   1038 			*INFO_HANDLE = *STDOUT;
   1039 		}
   1040 		else
   1041 		{
   1042 			# Append to output file
   1043 			open(INFO_HANDLE, ">>", $output_filename)
   1044 				or die("ERROR: cannot write to ".
   1045 				       "$output_filename!\n");
   1046 		}
   1047 	}
   1048 	else
   1049 	{
   1050 		# Open .info file for output
   1051 		open(INFO_HANDLE, ">", "$da_filename.info")
   1052 			or die("ERROR: cannot create $da_filename.info!\n");
   1053 	}
   1054 
   1055 	# Write test name
   1056 	printf(INFO_HANDLE "TN:%s\n", $test_name);
   1057 
   1058 	# Traverse the list of generated .gcov files and combine them into a
   1059 	# single .info file
   1060 	@unprocessed = keys(%{$instr});
   1061 	foreach $gcov_file (sort(@gcov_list))
   1062 	{
   1063 		my $i;
   1064 		my $num;
   1065 
   1066 		# Skip gcov file for gcc built-in code
   1067 		next if ($gcov_file eq "<built-in>.gcov");
   1068 
   1069 		($source, $object) = read_gcov_header($gcov_file);
   1070 
   1071 		if (!defined($source)) {
   1072 			# Derive source file name from gcov file name if
   1073 			# header format could not be parsed
   1074 			$source = $gcov_file;
   1075 			$source =~ s/\.gcov$//;
   1076 		}
   1077 
   1078 		$source = solve_relative_path($base_dir, $source);
   1079 
   1080 		if (defined($adjust_src_pattern)) {
   1081 			# Apply transformation as specified by user
   1082 			$source =~ s/$adjust_src_pattern/$adjust_src_replace/g;
   1083 		}
   1084 
   1085 		# gcov will happily create output even if there's no source code
   1086 		# available - this interferes with checksum creation so we need
   1087 		# to pull the emergency brake here.
   1088 		if (! -r $source && $checksum)
   1089 		{
   1090 			if ($ignore[$ERROR_SOURCE])
   1091 			{
   1092 				warn("WARNING: could not read source file ".
   1093 				     "$source\n");
   1094 				next;
   1095 			}
   1096 			die("ERROR: could not read source file $source\n");
   1097 		}
   1098 
   1099 		@matches = match_filename($source, keys(%{$instr}));
   1100 
   1101 		# Skip files that are not mentioned in the graph file
   1102 		if (!@matches)
   1103 		{
   1104 			warn("WARNING: cannot find an entry for ".$gcov_file.
   1105 			     " in $graph_file_extension file, skipping ".
   1106 			     "file!\n");
   1107 			unlink($gcov_file);
   1108 			next;
   1109 		}
   1110 
   1111 		# Read in contents of gcov file
   1112 		@result = read_gcov_file($gcov_file);
   1113 		if (!defined($result[0])) {
   1114 			warn("WARNING: skipping unreadable file ".
   1115 			     $gcov_file."\n");
   1116 			unlink($gcov_file);
   1117 			next;
   1118 		}
   1119 		@gcov_content = @{$result[0]};
   1120 		$gcov_branches = $result[1];
   1121 		@gcov_functions = @{$result[2]};
   1122 
   1123 		# Skip empty files
   1124 		if (!@gcov_content)
   1125 		{
   1126 			warn("WARNING: skipping empty file ".$gcov_file."\n");
   1127 			unlink($gcov_file);
   1128 			next;
   1129 		}
   1130 
   1131 		if (scalar(@matches) == 1)
   1132 		{
   1133 			# Just one match
   1134 			$source_filename = $matches[0];
   1135 		}
   1136 		else
   1137 		{
   1138 			# Try to solve the ambiguity
   1139 			$source_filename = solve_ambiguous_match($gcov_file,
   1140 						\@matches, \@gcov_content);
   1141 		}
   1142 
   1143 		# Remove processed file from list
   1144 		for ($index = scalar(@unprocessed) - 1; $index >= 0; $index--)
   1145 		{
   1146 			if ($unprocessed[$index] eq $source_filename)
   1147 			{
   1148 				splice(@unprocessed, $index, 1);
   1149 				last;
   1150 			}
   1151 		}
   1152 
   1153 		# Skip external files if requested
   1154 		if (!$opt_external) {
   1155 			if (is_external($source_filename)) {
   1156 				info("  ignoring data for external file ".
   1157 				     "$source_filename\n");
   1158 				unlink($gcov_file);
   1159 				next;
   1160 			}
   1161 		}
   1162 
   1163 		# Write absolute path of source file
   1164 		printf(INFO_HANDLE "SF:%s\n", $source_filename);
   1165 
   1166 		# If requested, derive function coverage data from
   1167 		# line coverage data of the first line of a function
   1168 		if ($opt_derive_func_data) {
   1169 			@gcov_functions =
   1170 				derive_data(\@gcov_content, \@gcov_functions,
   1171 					    $graph->{$source_filename});
   1172 		}
   1173 
   1174 		# Write function-related information
   1175 		if (defined($graph->{$source_filename}))
   1176 		{
   1177 			my $fn_data = $graph->{$source_filename};
   1178 			my $fn;
   1179 
   1180 			foreach $fn (sort
   1181 				{$fn_data->{$a}->[0] <=> $fn_data->{$b}->[0]}
   1182 				keys(%{$fn_data})) {
   1183 				my $ln_data = $fn_data->{$fn};
   1184 				my $line = $ln_data->[0];
   1185 
   1186 				# Skip empty function
   1187 				if ($fn eq "") {
   1188 					next;
   1189 				}
   1190 				# Remove excluded functions
   1191 				if (!$no_markers) {
   1192 					my $gfn;
   1193 					my $found = 0;
   1194 
   1195 					foreach $gfn (@gcov_functions) {
   1196 						if ($gfn eq $fn) {
   1197 							$found = 1;
   1198 							last;
   1199 						}
   1200 					}
   1201 					if (!$found) {
   1202 						next;
   1203 					}
   1204 				}
   1205 
   1206 				# Normalize function name
   1207 				$fn = filter_fn_name($fn);
   1208 
   1209 				print(INFO_HANDLE "FN:$line,$fn\n");
   1210 			}
   1211 		}
   1212 
   1213 		#--
   1214 		#-- FNDA: <call-count>, <function-name>
   1215 		#-- FNF: overall count of functions
   1216 		#-- FNH: overall count of functions with non-zero call count
   1217 		#--
   1218 		$funcs_found = 0;
   1219 		$funcs_hit = 0;
   1220 		while (@gcov_functions)
   1221 		{
   1222 			my $count = shift(@gcov_functions);
   1223 			my $fn = shift(@gcov_functions);
   1224 
   1225 			$fn = filter_fn_name($fn);
   1226 			printf(INFO_HANDLE "FNDA:$count,$fn\n");
   1227 			$funcs_found++;
   1228 			$funcs_hit++ if ($count > 0);
   1229 		}
   1230 		if ($funcs_found > 0) {
   1231 			printf(INFO_HANDLE "FNF:%s\n", $funcs_found);
   1232 			printf(INFO_HANDLE "FNH:%s\n", $funcs_hit);
   1233 		}
   1234 
   1235 		# Write coverage information for each instrumented branch:
   1236 		#
   1237 		#   BRDA:<line number>,<block number>,<branch number>,<taken>
   1238 		#
   1239 		# where 'taken' is the number of times the branch was taken
   1240 		# or '-' if the block to which the branch belongs was never
   1241 		# executed
   1242 		$br_found = 0;
   1243 		$br_hit = 0;
   1244 		$num = br_gvec_len($gcov_branches);
   1245 		for ($i = 0; $i < $num; $i++) {
   1246 			my ($line, $block, $branch, $taken) =
   1247 				br_gvec_get($gcov_branches, $i);
   1248 
   1249 			print(INFO_HANDLE "BRDA:$line,$block,$branch,$taken\n");
   1250 			$br_found++;
   1251 			$br_hit++ if ($taken ne '-' && $taken > 0);
   1252 		}
   1253 		if ($br_found > 0) {
   1254 			printf(INFO_HANDLE "BRF:%s\n", $br_found);
   1255 			printf(INFO_HANDLE "BRH:%s\n", $br_hit);
   1256 		}
   1257 
   1258 		# Reset line counters
   1259 		$line_number = 0;
   1260 		$lines_found = 0;
   1261 		$lines_hit = 0;
   1262 
   1263 		# Write coverage information for each instrumented line
   1264 		# Note: @gcov_content contains a list of (flag, count, source)
   1265 		# tuple for each source code line
   1266 		while (@gcov_content)
   1267 		{
   1268 			$line_number++;
   1269 
   1270 			# Check for instrumented line
   1271 			if ($gcov_content[0])
   1272 			{
   1273 				$lines_found++;
   1274 				printf(INFO_HANDLE "DA:".$line_number.",".
   1275 				       $gcov_content[1].($checksum ?
   1276 				       ",". md5_base64($gcov_content[2]) : "").
   1277 				       "\n");
   1278 
   1279 				# Increase $lines_hit in case of an execution
   1280 				# count>0
   1281 				if ($gcov_content[1] > 0) { $lines_hit++; }
   1282 			}
   1283 
   1284 			# Remove already processed data from array
   1285 			splice(@gcov_content,0,3);
   1286 		}
   1287 
   1288 		# Write line statistics and section separator
   1289 		printf(INFO_HANDLE "LF:%s\n", $lines_found);
   1290 		printf(INFO_HANDLE "LH:%s\n", $lines_hit);
   1291 		print(INFO_HANDLE "end_of_record\n");
   1292 
   1293 		# Remove .gcov file after processing
   1294 		unlink($gcov_file);
   1295 	}
   1296 
   1297 	# Check for files which show up in the graph file but were never
   1298 	# processed
   1299 	if (@unprocessed && @gcov_list)
   1300 	{
   1301 		foreach (@unprocessed)
   1302 		{
   1303 			warn("WARNING: no data found for $_\n");
   1304 		}
   1305 	}
   1306 
   1307 	if (!($output_filename && ($output_filename eq "-")))
   1308 	{
   1309 		close(INFO_HANDLE);
   1310 	}
   1311 
   1312 	# Change back to initial directory
   1313 	chdir($cwd);
   1314 }
   1315 
   1316 
   1317 #
   1318 # solve_relative_path(path, dir)
   1319 #
   1320 # Solve relative path components of DIR which, if not absolute, resides in PATH.
   1321 #
   1322 
   1323 sub solve_relative_path($$)
   1324 {
   1325 	my $path = $_[0];
   1326 	my $dir = $_[1];
   1327 	my $volume;
   1328 	my $directories;
   1329 	my $filename;
   1330 	my @dirs;			# holds path elements
   1331 	my $result;
   1332 
   1333 	# Convert from Windows path to msys path
   1334 	if( $^O eq "msys" )
   1335 	{
   1336 		# search for a windows drive letter at the beginning
   1337 		($volume, $directories, $filename) = File::Spec::Win32->splitpath( $dir );
   1338 		if( $volume ne '' )
   1339 		{
   1340 			my $uppercase_volume;
   1341 			# transform c/d\../e/f\g to Windows style c\d\..\e\f\g
   1342 			$dir = File::Spec::Win32->canonpath( $dir );
   1343 			# use Win32 module to retrieve path components
   1344 			# $uppercase_volume is not used any further
   1345 			( $uppercase_volume, $directories, $filename ) = File::Spec::Win32->splitpath( $dir );
   1346 			@dirs = File::Spec::Win32->splitdir( $directories );
   1347 			
   1348 			# prepend volume, since in msys C: is always mounted to /c
   1349 			$volume =~ s|^([a-zA-Z]+):|/\L$1\E|;
   1350 			unshift( @dirs, $volume );
   1351 			
   1352 			# transform to Unix style '/' path
   1353 			$directories = File::Spec->catdir( @dirs );
   1354 			$dir = File::Spec->catpath( '', $directories, $filename );
   1355 		} else {
   1356 			# eliminate '\' path separators
   1357 			$dir = File::Spec->canonpath( $dir );
   1358 		}
   1359 	}
   1360 
   1361 	$result = $dir;
   1362 	# Prepend path if not absolute
   1363 	if ($dir =~ /^[^\/]/)
   1364 	{
   1365 		$result = "$path/$result";
   1366 	}
   1367 
   1368 	# Remove //
   1369 	$result =~ s/\/\//\//g;
   1370 
   1371 	# Remove .
   1372 	$result =~ s/\/\.\//\//g;
   1373 	$result =~ s/\/\.$/\//g;
   1374 
   1375 	# Remove trailing /
   1376 	$result =~ s/\/$//g;
   1377 
   1378 	# Solve ..
   1379 	while ($result =~ s/\/[^\/]+\/\.\.\//\//)
   1380 	{
   1381 	}
   1382 
   1383 	# Remove preceding ..
   1384 	$result =~ s/^\/\.\.\//\//g;
   1385 
   1386 	return $result;
   1387 }
   1388 
   1389 
   1390 #
   1391 # match_filename(gcov_filename, list)
   1392 #
   1393 # Return a list of those entries of LIST which match the relative filename
   1394 # GCOV_FILENAME.
   1395 #
   1396 
   1397 sub match_filename($@)
   1398 {
   1399 	my ($filename, @list) = @_;
   1400 	my ($vol, $dir, $file) = splitpath($filename);
   1401 	my @comp = splitdir($dir);
   1402 	my $comps = scalar(@comp);
   1403 	my $entry;
   1404 	my @result;
   1405 
   1406 entry:
   1407 	foreach $entry (@list) {
   1408 		my ($evol, $edir, $efile) = splitpath($entry);
   1409 		my @ecomp;
   1410 		my $ecomps;
   1411 		my $i;
   1412 
   1413 		# Filename component must match
   1414 		if ($efile ne $file) {
   1415 			next;
   1416 		}
   1417 		# Check directory components last to first for match
   1418 		@ecomp = splitdir($edir);
   1419 		$ecomps = scalar(@ecomp);
   1420 		if ($ecomps < $comps) {
   1421 			next;
   1422 		}
   1423 		for ($i = 0; $i < $comps; $i++) {
   1424 			if ($comp[$comps - $i - 1] ne
   1425 			    $ecomp[$ecomps - $i - 1]) {
   1426 				next entry;
   1427 			}
   1428 		}
   1429 		push(@result, $entry),
   1430 	}
   1431 
   1432 	return @result;
   1433 }
   1434 
   1435 #
   1436 # solve_ambiguous_match(rel_filename, matches_ref, gcov_content_ref)
   1437 #
   1438 # Try to solve ambiguous matches of mapping (gcov file) -> (source code) file
   1439 # by comparing source code provided in the GCOV file with that of the files
   1440 # in MATCHES. REL_FILENAME identifies the relative filename of the gcov
   1441 # file.
   1442 # 
   1443 # Return the one real match or die if there is none.
   1444 #
   1445 
   1446 sub solve_ambiguous_match($$$)
   1447 {
   1448 	my $rel_name = $_[0];
   1449 	my $matches = $_[1];
   1450 	my $content = $_[2];
   1451 	my $filename;
   1452 	my $index;
   1453 	my $no_match;
   1454 	local *SOURCE;
   1455 
   1456 	# Check the list of matches
   1457 	foreach $filename (@$matches)
   1458 	{
   1459 
   1460 		# Compare file contents
   1461 		open(SOURCE, "<", $filename)
   1462 			or die("ERROR: cannot read $filename!\n");
   1463 
   1464 		$no_match = 0;
   1465 		for ($index = 2; <SOURCE>; $index += 3)
   1466 		{
   1467 			chomp;
   1468 
   1469 			# Also remove CR from line-end
   1470 			s/\015$//;
   1471 
   1472 			if ($_ ne @$content[$index])
   1473 			{
   1474 				$no_match = 1;
   1475 				last;
   1476 			}
   1477 		}
   1478 
   1479 		close(SOURCE);
   1480 
   1481 		if (!$no_match)
   1482 		{
   1483 			info("Solved source file ambiguity for $rel_name\n");
   1484 			return $filename;
   1485 		}
   1486 	}
   1487 
   1488 	die("ERROR: could not match gcov data for $rel_name!\n");
   1489 }
   1490 
   1491 
   1492 #
   1493 # split_filename(filename)
   1494 #
   1495 # Return (path, filename, extension) for a given FILENAME.
   1496 #
   1497 
   1498 sub split_filename($)
   1499 {
   1500 	my @path_components = split('/', $_[0]);
   1501 	my @file_components = split('\.', pop(@path_components));
   1502 	my $extension = pop(@file_components);
   1503 
   1504 	return (join("/",@path_components), join(".",@file_components),
   1505 		$extension);
   1506 }
   1507 
   1508 
   1509 #
   1510 # read_gcov_header(gcov_filename)
   1511 #
   1512 # Parse file GCOV_FILENAME and return a list containing the following
   1513 # information:
   1514 #
   1515 #   (source, object)
   1516 #
   1517 # where:
   1518 #
   1519 # source: complete relative path of the source code file (gcc >= 3.3 only)
   1520 # object: name of associated graph file
   1521 #
   1522 # Die on error.
   1523 #
   1524 
   1525 sub read_gcov_header($)
   1526 {
   1527 	my $source;
   1528 	my $object;
   1529 	local *INPUT;
   1530 
   1531 	if (!open(INPUT, "<", $_[0]))
   1532 	{
   1533 		if ($ignore_errors[$ERROR_GCOV])
   1534 		{
   1535 			warn("WARNING: cannot read $_[0]!\n");
   1536 			return (undef,undef);
   1537 		}
   1538 		die("ERROR: cannot read $_[0]!\n");
   1539 	}
   1540 
   1541 	while (<INPUT>)
   1542 	{
   1543 		chomp($_);
   1544 
   1545 		# Also remove CR from line-end
   1546 		s/\015$//;
   1547 
   1548 		if (/^\s+-:\s+0:Source:(.*)$/)
   1549 		{
   1550 			# Source: header entry
   1551 			$source = $1;
   1552 		}
   1553 		elsif (/^\s+-:\s+0:Object:(.*)$/)
   1554 		{
   1555 			# Object: header entry
   1556 			$object = $1;
   1557 		}
   1558 		else
   1559 		{
   1560 			last;
   1561 		}
   1562 	}
   1563 
   1564 	close(INPUT);
   1565 
   1566 	return ($source, $object);
   1567 }
   1568 
   1569 
   1570 #
   1571 # br_gvec_len(vector)
   1572 #
   1573 # Return the number of entries in the branch coverage vector.
   1574 #
   1575 
   1576 sub br_gvec_len($)
   1577 {
   1578 	my ($vec) = @_;
   1579 
   1580 	return 0 if (!defined($vec));
   1581 	return (length($vec) * 8 / $BR_VEC_WIDTH) / $BR_VEC_ENTRIES;
   1582 }
   1583 
   1584 
   1585 #
   1586 # br_gvec_get(vector, number)
   1587 #
   1588 # Return an entry from the branch coverage vector.
   1589 #
   1590 
   1591 sub br_gvec_get($$)
   1592 {
   1593 	my ($vec, $num) = @_;
   1594 	my $line;
   1595 	my $block;
   1596 	my $branch;
   1597 	my $taken;
   1598 	my $offset = $num * $BR_VEC_ENTRIES;
   1599 
   1600 	# Retrieve data from vector
   1601 	$line	= vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH);
   1602 	$block	= vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH);
   1603 	$branch = vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH);
   1604 	$taken	= vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH);
   1605 
   1606 	# Decode taken value from an integer
   1607 	if ($taken == 0) {
   1608 		$taken = "-";
   1609 	} else {
   1610 		$taken--;
   1611 	}
   1612 
   1613 	return ($line, $block, $branch, $taken);
   1614 }
   1615 
   1616 
   1617 #
   1618 # br_gvec_push(vector, line, block, branch, taken)
   1619 #
   1620 # Add an entry to the branch coverage vector.
   1621 #
   1622 
   1623 sub br_gvec_push($$$$$)
   1624 {
   1625 	my ($vec, $line, $block, $branch, $taken) = @_;
   1626 	my $offset;
   1627 
   1628 	$vec = "" if (!defined($vec));
   1629 	$offset = br_gvec_len($vec) * $BR_VEC_ENTRIES;
   1630 
   1631 	# Encode taken value into an integer
   1632 	if ($taken eq "-") {
   1633 		$taken = 0;
   1634 	} else {
   1635 		$taken++;
   1636 	}
   1637 
   1638 	# Add to vector
   1639 	vec($vec, $offset + $BR_LINE, $BR_VEC_WIDTH) = $line;
   1640 	vec($vec, $offset + $BR_BLOCK, $BR_VEC_WIDTH) = $block;
   1641 	vec($vec, $offset + $BR_BRANCH, $BR_VEC_WIDTH) = $branch;
   1642 	vec($vec, $offset + $BR_TAKEN, $BR_VEC_WIDTH) = $taken;
   1643 
   1644 	return $vec;
   1645 }
   1646 
   1647 
   1648 #
   1649 # read_gcov_file(gcov_filename)
   1650 #
   1651 # Parse file GCOV_FILENAME (.gcov file format) and return the list:
   1652 # (reference to gcov_content, reference to gcov_branch, reference to gcov_func)
   1653 #
   1654 # gcov_content is a list of 3 elements
   1655 # (flag, count, source) for each source code line:
   1656 #
   1657 # $result[($line_number-1)*3+0] = instrumentation flag for line $line_number
   1658 # $result[($line_number-1)*3+1] = execution count for line $line_number
   1659 # $result[($line_number-1)*3+2] = source code text for line $line_number
   1660 #
   1661 # gcov_branch is a vector of 4 4-byte long elements for each branch:
   1662 # line number, block number, branch number, count + 1 or 0
   1663 #
   1664 # gcov_func is a list of 2 elements
   1665 # (number of calls, function name) for each function
   1666 #
   1667 # Die on error.
   1668 #
   1669 
   1670 sub read_gcov_file($)
   1671 {
   1672 	my $filename = $_[0];
   1673 	my @result = ();
   1674 	my $branches = "";
   1675 	my @functions = ();
   1676 	my $number;
   1677 	my $exclude_flag = 0;
   1678 	my $exclude_line = 0;
   1679 	my $last_block = $UNNAMED_BLOCK;
   1680 	my $last_line = 0;
   1681 	local *INPUT;
   1682 
   1683 	if (!open(INPUT, "<", $filename)) {
   1684 		if ($ignore_errors[$ERROR_GCOV])
   1685 		{
   1686 			warn("WARNING: cannot read $filename!\n");
   1687 			return (undef, undef, undef);
   1688 		}
   1689 		die("ERROR: cannot read $filename!\n");
   1690 	}
   1691 
   1692 	if ($gcov_version < $GCOV_VERSION_3_3_0)
   1693 	{
   1694 		# Expect gcov format as used in gcc < 3.3
   1695 		while (<INPUT>)
   1696 		{
   1697 			chomp($_);
   1698 
   1699 			# Also remove CR from line-end
   1700 			s/\015$//;
   1701 
   1702 			if (/^branch\s+(\d+)\s+taken\s+=\s+(\d+)/) {
   1703 				next if (!$br_coverage);
   1704 				next if ($exclude_line);
   1705 				$branches = br_gvec_push($branches, $last_line,
   1706 						$last_block, $1, $2);
   1707 			} elsif (/^branch\s+(\d+)\s+never\s+executed/) {
   1708 				next if (!$br_coverage);
   1709 				next if ($exclude_line);
   1710 				$branches = br_gvec_push($branches, $last_line,
   1711 						$last_block, $1, '-');
   1712 			}
   1713 			elsif (/^call/ || /^function/)
   1714 			{
   1715 				# Function call return data
   1716 			}
   1717 			else
   1718 			{
   1719 				$last_line++;
   1720 				# Check for exclusion markers
   1721 				if (!$no_markers) {
   1722 					if (/$EXCL_STOP/) {
   1723 						$exclude_flag = 0;
   1724 					} elsif (/$EXCL_START/) {
   1725 						$exclude_flag = 1;
   1726 					}
   1727 					if (/$EXCL_LINE/ || $exclude_flag) {
   1728 						$exclude_line = 1;
   1729 					} else {
   1730 						$exclude_line = 0;
   1731 					}
   1732 				}
   1733 				# Source code execution data
   1734 				if (/^\t\t(.*)$/)
   1735 				{
   1736 					# Uninstrumented line
   1737 					push(@result, 0);
   1738 					push(@result, 0);
   1739 					push(@result, $1);
   1740 					next;
   1741 				}
   1742 				$number = (split(" ",substr($_, 0, 16)))[0];
   1743 
   1744 				# Check for zero count which is indicated
   1745 				# by ######
   1746 				if ($number eq "######") { $number = 0;	}
   1747 
   1748 				if ($exclude_line) {
   1749 					# Register uninstrumented line instead
   1750 					push(@result, 0);
   1751 					push(@result, 0);
   1752 				} else {
   1753 					push(@result, 1);
   1754 					push(@result, $number);
   1755 				}
   1756 				push(@result, substr($_, 16));
   1757 			}
   1758 		}
   1759 	}
   1760 	else
   1761 	{
   1762 		# Expect gcov format as used in gcc >= 3.3
   1763 		while (<INPUT>)
   1764 		{
   1765 			chomp($_);
   1766 
   1767 			# Also remove CR from line-end
   1768 			s/\015$//;
   1769 
   1770 			if (/^\s*(\d+|\$+):\s*(\d+)-block\s+(\d+)\s*$/) {
   1771 				# Block information - used to group related
   1772 				# branches
   1773 				$last_line = $2;
   1774 				$last_block = $3;
   1775 			} elsif (/^branch\s+(\d+)\s+taken\s+(\d+)/) {
   1776 				next if (!$br_coverage);
   1777 				next if ($exclude_line);
   1778 				$branches = br_gvec_push($branches, $last_line,
   1779 						$last_block, $1, $2);
   1780 			} elsif (/^branch\s+(\d+)\s+never\s+executed/) {
   1781 				next if (!$br_coverage);
   1782 				next if ($exclude_line);
   1783 				$branches = br_gvec_push($branches, $last_line,
   1784 						$last_block, $1, '-');
   1785 			}
   1786 			elsif (/^function\s+(.+)\s+called\s+(\d+)\s+/)
   1787 			{
   1788 				next if (!$func_coverage);
   1789 				if ($exclude_line) {
   1790 					next;
   1791 				}
   1792 				push(@functions, $2, $1);
   1793 			}
   1794 			elsif (/^call/)
   1795 			{
   1796 				# Function call return data
   1797 			}
   1798 			elsif (/^\s*([^:]+):\s*([^:]+):(.*)$/)
   1799 			{
   1800 				my ($count, $line, $code) = ($1, $2, $3);
   1801 
   1802 				$last_line = $line;
   1803 				$last_block = $UNNAMED_BLOCK;
   1804 				# Check for exclusion markers
   1805 				if (!$no_markers) {
   1806 					if (/$EXCL_STOP/) {
   1807 						$exclude_flag = 0;
   1808 					} elsif (/$EXCL_START/) {
   1809 						$exclude_flag = 1;
   1810 					}
   1811 					if (/$EXCL_LINE/ || $exclude_flag) {
   1812 						$exclude_line = 1;
   1813 					} else {
   1814 						$exclude_line = 0;
   1815 					}
   1816 				}
   1817 				# <exec count>:<line number>:<source code>
   1818 				if ($line eq "0")
   1819 				{
   1820 					# Extra data
   1821 				}
   1822 				elsif ($count eq "-")
   1823 				{
   1824 					# Uninstrumented line
   1825 					push(@result, 0);
   1826 					push(@result, 0);
   1827 					push(@result, $code);
   1828 				}
   1829 				else
   1830 				{
   1831 					if ($exclude_line) {
   1832 						push(@result, 0);
   1833 						push(@result, 0);
   1834 					} else {
   1835 						# Check for zero count
   1836 						if ($count eq "#####") {
   1837 							$count = 0;
   1838 						}
   1839 						push(@result, 1);
   1840 						push(@result, $count);
   1841 					}
   1842 					push(@result, $code);
   1843 				}
   1844 			}
   1845 		}
   1846 	}
   1847 
   1848 	close(INPUT);
   1849 	if ($exclude_flag) {
   1850 		warn("WARNING: unterminated exclusion section in $filename\n");
   1851 	}
   1852 	return(\@result, $branches, \@functions);
   1853 }
   1854 
   1855 
   1856 #
   1857 # Get the GCOV tool version. Return an integer number which represents the
   1858 # GCOV version. Version numbers can be compared using standard integer
   1859 # operations.
   1860 #
   1861 
   1862 sub get_gcov_version()
   1863 {
   1864 	local *HANDLE;
   1865 	my $version_string;
   1866 	my $result;
   1867 
   1868 	open(GCOV_PIPE, "-|", "$gcov_tool -v")
   1869 		or die("ERROR: cannot retrieve gcov version!\n");
   1870 	$version_string = <GCOV_PIPE>;
   1871 	close(GCOV_PIPE);
   1872 
   1873 	$result = 0;
   1874 	if ($version_string =~ /(\d+)\.(\d+)(\.(\d+))?/)
   1875 	{
   1876 		if (defined($4))
   1877 		{
   1878 			info("Found gcov version: $1.$2.$4\n");
   1879 			$result = $1 << 16 | $2 << 8 | $4;
   1880 		}
   1881 		else
   1882 		{
   1883 			info("Found gcov version: $1.$2\n");
   1884 			$result = $1 << 16 | $2 << 8;
   1885 		}
   1886 	}
   1887 	return ($result, $version_string);
   1888 }
   1889 
   1890 
   1891 #
   1892 # info(printf_parameter)
   1893 #
   1894 # Use printf to write PRINTF_PARAMETER to stdout only when the $quiet flag
   1895 # is not set.
   1896 #
   1897 
   1898 sub info(@)
   1899 {
   1900 	if (!$quiet)
   1901 	{
   1902 		# Print info string
   1903 		if (defined($output_filename) && ($output_filename eq "-"))
   1904 		{
   1905 			# Don't interfere with the .info output to STDOUT
   1906 			printf(STDERR @_);
   1907 		}
   1908 		else
   1909 		{
   1910 			printf(@_);
   1911 		}
   1912 	}
   1913 }
   1914 
   1915 
   1916 #
   1917 # int_handler()
   1918 #
   1919 # Called when the script was interrupted by an INT signal (e.g. CTRl-C)
   1920 #
   1921 
   1922 sub int_handler()
   1923 {
   1924 	if ($cwd) { chdir($cwd); }
   1925 	info("Aborted.\n");
   1926 	exit(1);
   1927 }
   1928 
   1929 
   1930 #
   1931 # system_no_output(mode, parameters)
   1932 #
   1933 # Call an external program using PARAMETERS while suppressing depending on
   1934 # the value of MODE:
   1935 #
   1936 #   MODE & 1: suppress STDOUT
   1937 #   MODE & 2: suppress STDERR
   1938 #
   1939 # Return 0 on success, non-zero otherwise.
   1940 #
   1941 
   1942 sub system_no_output($@)
   1943 {
   1944 	my $mode = shift;
   1945 	my $result;
   1946 	local *OLD_STDERR;
   1947 	local *OLD_STDOUT;
   1948 
   1949 	# Save old stdout and stderr handles
   1950 	($mode & 1) && open(OLD_STDOUT, ">>&", "STDOUT");
   1951 	($mode & 2) && open(OLD_STDERR, ">>&", "STDERR");
   1952 
   1953 	# Redirect to /dev/null
   1954 	($mode & 1) && open(STDOUT, ">", "/dev/null");
   1955 	($mode & 2) && open(STDERR, ">", "/dev/null");
   1956  
   1957 	debug("system(".join(' ', @_).")\n");
   1958 	system(@_);
   1959 	$result = $?;
   1960 
   1961 	# Close redirected handles
   1962 	($mode & 1) && close(STDOUT);
   1963 	($mode & 2) && close(STDERR);
   1964 
   1965 	# Restore old handles
   1966 	($mode & 1) && open(STDOUT, ">>&", "OLD_STDOUT");
   1967 	($mode & 2) && open(STDERR, ">>&", "OLD_STDERR");
   1968  
   1969 	return $result;
   1970 }
   1971 
   1972 
   1973 #
   1974 # read_config(filename)
   1975 #
   1976 # Read configuration file FILENAME and return a reference to a hash containing
   1977 # all valid key=value pairs found.
   1978 #
   1979 
   1980 sub read_config($)
   1981 {
   1982 	my $filename = $_[0];
   1983 	my %result;
   1984 	my $key;
   1985 	my $value;
   1986 	local *HANDLE;
   1987 
   1988 	if (!open(HANDLE, "<", $filename))
   1989 	{
   1990 		warn("WARNING: cannot read configuration file $filename\n");
   1991 		return undef;
   1992 	}
   1993 	while (<HANDLE>)
   1994 	{
   1995 		chomp;
   1996 		# Skip comments
   1997 		s/#.*//;
   1998 		# Remove leading blanks
   1999 		s/^\s+//;
   2000 		# Remove trailing blanks
   2001 		s/\s+$//;
   2002 		next unless length;
   2003 		($key, $value) = split(/\s*=\s*/, $_, 2);
   2004 		if (defined($key) && defined($value))
   2005 		{
   2006 			$result{$key} = $value;
   2007 		}
   2008 		else
   2009 		{
   2010 			warn("WARNING: malformed statement in line $. ".
   2011 			     "of configuration file $filename\n");
   2012 		}
   2013 	}
   2014 	close(HANDLE);
   2015 	return \%result;
   2016 }
   2017 
   2018 
   2019 #
   2020 # apply_config(REF)
   2021 #
   2022 # REF is a reference to a hash containing the following mapping:
   2023 #
   2024 #   key_string => var_ref
   2025 #
   2026 # where KEY_STRING is a keyword and VAR_REF is a reference to an associated
   2027 # variable. If the global configuration hashes CONFIG or OPT_RC contain a value
   2028 # for keyword KEY_STRING, VAR_REF will be assigned the value for that keyword. 
   2029 #
   2030 
   2031 sub apply_config($)
   2032 {
   2033 	my $ref = $_[0];
   2034 
   2035 	foreach (keys(%{$ref}))
   2036 	{
   2037 		if (defined($opt_rc{$_})) {
   2038 			${$ref->{$_}} = $opt_rc{$_};
   2039 		} elsif (defined($config->{$_})) {
   2040 			${$ref->{$_}} = $config->{$_};
   2041 		}
   2042 	}
   2043 }
   2044 
   2045 
   2046 #
   2047 # get_exclusion_data(filename)
   2048 #
   2049 # Scan specified source code file for exclusion markers and return
   2050 #   linenumber -> 1
   2051 # for all lines which should be excluded.
   2052 #
   2053 
   2054 sub get_exclusion_data($)
   2055 {
   2056 	my ($filename) = @_;
   2057 	my %list;
   2058 	my $flag = 0;
   2059 	local *HANDLE;
   2060 
   2061 	if (!open(HANDLE, "<", $filename)) {
   2062 		warn("WARNING: could not open $filename\n");
   2063 		return undef;
   2064 	}
   2065 	while (<HANDLE>) {
   2066 		if (/$EXCL_STOP/) {
   2067 			$flag = 0;
   2068 		} elsif (/$EXCL_START/) {
   2069 			$flag = 1;
   2070 		}
   2071 		if (/$EXCL_LINE/ || $flag) {
   2072 			$list{$.} = 1;
   2073 		}
   2074 	}
   2075 	close(HANDLE);
   2076 
   2077 	if ($flag) {
   2078 		warn("WARNING: unterminated exclusion section in $filename\n");
   2079 	}
   2080 
   2081 	return \%list;
   2082 }
   2083 
   2084 
   2085 #
   2086 # apply_exclusion_data(instr, graph)
   2087 #
   2088 # Remove lines from instr and graph data structures which are marked
   2089 # for exclusion in the source code file.
   2090 #
   2091 # Return adjusted (instr, graph).
   2092 #
   2093 # graph         : file name -> function data
   2094 # function data : function name -> line data
   2095 # line data     : [ line1, line2, ... ]
   2096 #
   2097 # instr     : filename -> line data
   2098 # line data : [ line1, line2, ... ]
   2099 #
   2100 
   2101 sub apply_exclusion_data($$)
   2102 {
   2103 	my ($instr, $graph) = @_;
   2104 	my $filename;
   2105 	my %excl_data;
   2106 	my $excl_read_failed = 0;
   2107 
   2108 	# Collect exclusion marker data
   2109 	foreach $filename (sort_uniq_lex(keys(%{$graph}), keys(%{$instr}))) {
   2110 		my $excl = get_exclusion_data($filename);
   2111 
   2112 		# Skip and note if file could not be read
   2113 		if (!defined($excl)) {
   2114 			$excl_read_failed = 1;
   2115 			next;
   2116 		}
   2117 
   2118 		# Add to collection if there are markers
   2119 		$excl_data{$filename} = $excl if (keys(%{$excl}) > 0);
   2120 	}
   2121 
   2122 	# Warn if not all source files could be read
   2123 	if ($excl_read_failed) {
   2124 		warn("WARNING: some exclusion markers may be ignored\n");
   2125 	}
   2126 
   2127 	# Skip if no markers were found
   2128 	return ($instr, $graph) if (keys(%excl_data) == 0);
   2129 
   2130 	# Apply exclusion marker data to graph
   2131 	foreach $filename (keys(%excl_data)) {
   2132 		my $function_data = $graph->{$filename};
   2133 		my $excl = $excl_data{$filename};
   2134 		my $function;
   2135 
   2136 		next if (!defined($function_data));
   2137 
   2138 		foreach $function (keys(%{$function_data})) {
   2139 			my $line_data = $function_data->{$function};
   2140 			my $line;
   2141 			my @new_data;
   2142 
   2143 			# To be consistent with exclusion parser in non-initial
   2144 			# case we need to remove a function if the first line
   2145 			# was excluded
   2146 			if ($excl->{$line_data->[0]}) {
   2147 				delete($function_data->{$function});
   2148 				next;
   2149 			}
   2150 			# Copy only lines which are not excluded
   2151 			foreach $line (@{$line_data}) {
   2152 				push(@new_data, $line) if (!$excl->{$line});
   2153 			}
   2154 
   2155 			# Store modified list
   2156 			if (scalar(@new_data) > 0) {
   2157 				$function_data->{$function} = \@new_data;
   2158 			} else {
   2159 				# All of this function was excluded
   2160 				delete($function_data->{$function});
   2161 			}
   2162 		}
   2163 
   2164 		# Check if all functions of this file were excluded
   2165 		if (keys(%{$function_data}) == 0) {
   2166 			delete($graph->{$filename});
   2167 		}
   2168 	}
   2169 
   2170 	# Apply exclusion marker data to instr
   2171 	foreach $filename (keys(%excl_data)) {
   2172 		my $line_data = $instr->{$filename};
   2173 		my $excl = $excl_data{$filename};
   2174 		my $line;
   2175 		my @new_data;
   2176 
   2177 		next if (!defined($line_data));
   2178 
   2179 		# Copy only lines which are not excluded
   2180 		foreach $line (@{$line_data}) {
   2181 			push(@new_data, $line) if (!$excl->{$line});
   2182 		}
   2183 
   2184 		# Store modified list
   2185 		$instr->{$filename} = \@new_data;
   2186 	}
   2187 
   2188 	return ($instr, $graph);
   2189 }
   2190 
   2191 
   2192 sub process_graphfile($$)
   2193 {
   2194 	my ($file, $dir) = @_;
   2195 	my $graph_filename = $file;
   2196 	my $graph_dir;
   2197 	my $graph_basename;
   2198 	my $source_dir;
   2199 	my $base_dir;
   2200 	my $graph;
   2201 	my $instr;
   2202 	my $filename;
   2203 	local *INFO_HANDLE;
   2204 
   2205 	info("Processing %s\n", abs2rel($file, $dir));
   2206 
   2207 	# Get path to data file in absolute and normalized form (begins with /,
   2208 	# contains no more ../ or ./)
   2209 	$graph_filename = solve_relative_path($cwd, $graph_filename);
   2210 
   2211 	# Get directory and basename of data file
   2212 	($graph_dir, $graph_basename) = split_filename($graph_filename);
   2213 
   2214 	$source_dir = $graph_dir;
   2215 	if (is_compat($COMPAT_MODE_LIBTOOL)) {
   2216 		# Avoid files from .libs dirs 	 
   2217 		$source_dir =~ s/\.libs$//;
   2218 	}
   2219 
   2220 	# Construct base_dir for current file
   2221 	if ($base_directory)
   2222 	{
   2223 		$base_dir = $base_directory;
   2224 	}
   2225 	else
   2226 	{
   2227 		$base_dir = $source_dir;
   2228 	}
   2229 
   2230 	if ($gcov_version < $GCOV_VERSION_3_4_0)
   2231 	{
   2232 		if (is_compat($COMPAT_MODE_HAMMER))
   2233 		{
   2234 			($instr, $graph) = read_bbg($graph_filename);
   2235 		}
   2236 		else
   2237 		{
   2238 			($instr, $graph) = read_bb($graph_filename);
   2239 		}
   2240 	} 
   2241 	else
   2242 	{
   2243 		($instr, $graph) = read_gcno($graph_filename);
   2244 	}
   2245 
   2246 	# Try to find base directory automatically if requested by user
   2247 	if ($rc_auto_base) {
   2248 		$base_dir = find_base_from_graph($base_dir, $instr, $graph);
   2249 	}
   2250 
   2251 	($instr, $graph) = adjust_graph_filenames($base_dir, $instr, $graph);
   2252 
   2253 	if (!$no_markers) {
   2254 		# Apply exclusion marker data to graph file data
   2255 		($instr, $graph) = apply_exclusion_data($instr, $graph);
   2256 	}
   2257 
   2258 	# Check whether we're writing to a single file
   2259 	if ($output_filename)
   2260 	{
   2261 		if ($output_filename eq "-")
   2262 		{
   2263 			*INFO_HANDLE = *STDOUT;
   2264 		}
   2265 		else
   2266 		{
   2267 			# Append to output file
   2268 			open(INFO_HANDLE, ">>", $output_filename)
   2269 				or die("ERROR: cannot write to ".
   2270 				       "$output_filename!\n");
   2271 		}
   2272 	}
   2273 	else
   2274 	{
   2275 		# Open .info file for output
   2276 		open(INFO_HANDLE, ">", "$graph_filename.info")
   2277 			or die("ERROR: cannot create $graph_filename.info!\n");
   2278 	}
   2279 
   2280 	# Write test name
   2281 	printf(INFO_HANDLE "TN:%s\n", $test_name);
   2282 	foreach $filename (sort(keys(%{$instr})))
   2283 	{
   2284 		my $funcdata = $graph->{$filename};
   2285 		my $line;
   2286 		my $linedata;
   2287 
   2288 		print(INFO_HANDLE "SF:$filename\n");
   2289 
   2290 		if (defined($funcdata) && $func_coverage) {
   2291 			my @functions = sort {$funcdata->{$a}->[0] <=>
   2292 					      $funcdata->{$b}->[0]}
   2293 					     keys(%{$funcdata});
   2294 			my $func;
   2295 
   2296 			# Gather list of instrumented lines and functions
   2297 			foreach $func (@functions) {
   2298 				$linedata = $funcdata->{$func};
   2299 
   2300 				# Print function name and starting line
   2301 				print(INFO_HANDLE "FN:".$linedata->[0].
   2302 				      ",".filter_fn_name($func)."\n");
   2303 			}
   2304 			# Print zero function coverage data
   2305 			foreach $func (@functions) {
   2306 				print(INFO_HANDLE "FNDA:0,".
   2307 				      filter_fn_name($func)."\n");
   2308 			}
   2309 			# Print function summary
   2310 			print(INFO_HANDLE "FNF:".scalar(@functions)."\n");
   2311 			print(INFO_HANDLE "FNH:0\n");
   2312 		}
   2313 		# Print zero line coverage data
   2314 		foreach $line (@{$instr->{$filename}}) {
   2315 			print(INFO_HANDLE "DA:$line,0\n");
   2316 		}
   2317 		# Print line summary
   2318 		print(INFO_HANDLE "LF:".scalar(@{$instr->{$filename}})."\n");
   2319 		print(INFO_HANDLE "LH:0\n");
   2320 
   2321 		print(INFO_HANDLE "end_of_record\n");
   2322 	}
   2323 	if (!($output_filename && ($output_filename eq "-")))
   2324 	{
   2325 		close(INFO_HANDLE);
   2326 	}
   2327 }
   2328 
   2329 sub filter_fn_name($)
   2330 {
   2331 	my ($fn) = @_;
   2332 
   2333 	# Remove characters used internally as function name delimiters
   2334 	$fn =~ s/[,=]/_/g;
   2335 
   2336 	return $fn;
   2337 }
   2338 
   2339 sub warn_handler($)
   2340 {
   2341 	my ($msg) = @_;
   2342 
   2343 	warn("$tool_name: $msg");
   2344 }
   2345 
   2346 sub die_handler($)
   2347 {
   2348 	my ($msg) = @_;
   2349 
   2350 	die("$tool_name: $msg");
   2351 }
   2352 
   2353 
   2354 #
   2355 # graph_error(filename, message)
   2356 #
   2357 # Print message about error in graph file. If ignore_graph_error is set, return.
   2358 # Otherwise abort.
   2359 #
   2360 
   2361 sub graph_error($$)
   2362 {
   2363 	my ($filename, $msg) = @_;
   2364 
   2365 	if ($ignore[$ERROR_GRAPH]) {
   2366 		warn("WARNING: $filename: $msg - skipping\n");
   2367 		return;
   2368 	}
   2369 	die("ERROR: $filename: $msg\n");
   2370 }
   2371 
   2372 #
   2373 # graph_expect(description)
   2374 #
   2375 # If debug is set to a non-zero value, print the specified description of what
   2376 # is expected to be read next from the graph file.
   2377 #
   2378 
   2379 sub graph_expect($)
   2380 {
   2381 	my ($msg) = @_;
   2382 
   2383 	if (!$debug || !defined($msg)) {
   2384 		return;
   2385 	}
   2386 
   2387 	print(STDERR "DEBUG: expecting $msg\n");
   2388 }
   2389 
   2390 #
   2391 # graph_read(handle, bytes[, description, peek])
   2392 #
   2393 # Read and return the specified number of bytes from handle. Return undef
   2394 # if the number of bytes could not be read. If PEEK is non-zero, reset
   2395 # file position after read.
   2396 #
   2397 
   2398 sub graph_read(*$;$$)
   2399 {
   2400 	my ($handle, $length, $desc, $peek) = @_;
   2401 	my $data;
   2402 	my $result;
   2403 	my $pos;
   2404 
   2405 	graph_expect($desc);
   2406 	if ($peek) {
   2407 		$pos = tell($handle);
   2408 		if ($pos == -1) {
   2409 			warn("Could not get current file position: $!\n");
   2410 			return undef;
   2411 		}
   2412 	}
   2413 	$result = read($handle, $data, $length);
   2414 	if ($debug) {
   2415 		my $op = $peek ? "peek" : "read";
   2416 		my $ascii = "";
   2417 		my $hex = "";
   2418 		my $i;
   2419 
   2420 		print(STDERR "DEBUG: $op($length)=$result: ");
   2421 		for ($i = 0; $i < length($data); $i++) {
   2422 			my $c = substr($data, $i, 1);;
   2423 			my $n = ord($c);
   2424 
   2425 			$hex .= sprintf("%02x ", $n);
   2426 			if ($n >= 32 && $n <= 127) {
   2427 				$ascii .= $c;
   2428 			} else {
   2429 				$ascii .= ".";
   2430 			}
   2431 		}
   2432 		print(STDERR "$hex |$ascii|");
   2433 		print(STDERR "\n");
   2434 	}
   2435 	if ($peek) {
   2436 		if (!seek($handle, $pos, 0)) {
   2437 			warn("Could not set file position: $!\n");
   2438 			return undef;
   2439 		}
   2440 	}
   2441 	if ($result != $length) {
   2442 		return undef;
   2443 	}
   2444 	return $data;
   2445 }
   2446 
   2447 #
   2448 # graph_skip(handle, bytes[, description])
   2449 #
   2450 # Read and discard the specified number of bytes from handle. Return non-zero
   2451 # if bytes could be read, zero otherwise.
   2452 #
   2453 
   2454 sub graph_skip(*$;$)
   2455 {
   2456 	my ($handle, $length, $desc) = @_;
   2457 
   2458 	if (defined(graph_read($handle, $length, $desc))) {
   2459 		return 1;
   2460 	}
   2461 	return 0;
   2462 }
   2463 
   2464 #
   2465 # sort_uniq(list)
   2466 #
   2467 # Return list in numerically ascending order and without duplicate entries.
   2468 #
   2469 
   2470 sub sort_uniq(@)
   2471 {
   2472 	my (@list) = @_;
   2473 	my %hash;
   2474 
   2475 	foreach (@list) {
   2476 		$hash{$_} = 1;
   2477 	}
   2478 	return sort { $a <=> $b } keys(%hash);
   2479 }
   2480 
   2481 #
   2482 # sort_uniq_lex(list)
   2483 #
   2484 # Return list in lexically ascending order and without duplicate entries.
   2485 #
   2486 
   2487 sub sort_uniq_lex(@)
   2488 {
   2489 	my (@list) = @_;
   2490 	my %hash;
   2491 
   2492 	foreach (@list) {
   2493 		$hash{$_} = 1;
   2494 	}
   2495 	return sort keys(%hash);
   2496 }
   2497 
   2498 #
   2499 # parent_dir(dir)
   2500 #
   2501 # Return parent directory for DIR. DIR must not contain relative path
   2502 # components.
   2503 #
   2504 
   2505 sub parent_dir($)
   2506 {
   2507 	my ($dir) = @_;
   2508 	my ($v, $d, $f) = splitpath($dir, 1);
   2509 	my @dirs = splitdir($d);
   2510 
   2511 	pop(@dirs);
   2512 
   2513 	return catpath($v, catdir(@dirs), $f);
   2514 }
   2515 
   2516 #
   2517 # find_base_from_graph(base_dir, instr, graph)
   2518 #
   2519 # Try to determine the base directory of the graph file specified by INSTR
   2520 # and GRAPH. The base directory is the base for all relative filenames in
   2521 # the graph file. It is defined by the current working directory at time
   2522 # of compiling the source file.
   2523 #
   2524 # This function implements a heuristic which relies on the following
   2525 # assumptions:
   2526 # - all files used for compilation are still present at their location
   2527 # - the base directory is either BASE_DIR or one of its parent directories
   2528 # - files by the same name are not present in multiple parent directories
   2529 #
   2530 
   2531 sub find_base_from_graph($$$)
   2532 {
   2533 	my ($base_dir, $instr, $graph) = @_;
   2534 	my $old_base;
   2535 	my $best_miss;
   2536 	my $best_base;
   2537 	my %rel_files;
   2538 
   2539 	# Determine list of relative paths
   2540 	foreach my $filename (keys(%{$instr}), keys(%{$graph})) {
   2541 		next if (file_name_is_absolute($filename));
   2542 
   2543 		$rel_files{$filename} = 1;
   2544 	}
   2545 
   2546 	# Early exit if there are no relative paths
   2547 	return $base_dir if (!%rel_files);
   2548 
   2549 	do {
   2550 		my $miss = 0;
   2551 
   2552 		foreach my $filename (keys(%rel_files)) {
   2553 			if (!-e solve_relative_path($base_dir, $filename)) {
   2554 				$miss++;
   2555 			}
   2556 		}
   2557 
   2558 		debug("base_dir=$base_dir miss=$miss\n");
   2559 
   2560 		# Exit if we find an exact match with no misses
   2561 		return $base_dir if ($miss == 0);
   2562 
   2563 		# No exact match, aim for the one with the least source file
   2564 		# misses
   2565 		if (!defined($best_base) || $miss < $best_miss) {
   2566 			$best_base = $base_dir;
   2567 			$best_miss = $miss;
   2568 		}
   2569 
   2570 		# Repeat until there's no more parent directory
   2571 		$old_base = $base_dir;
   2572 		$base_dir = parent_dir($base_dir);
   2573 	} while ($old_base ne $base_dir);
   2574 
   2575 	return $best_base;
   2576 }
   2577 
   2578 #
   2579 # adjust_graph_filenames(base_dir, instr, graph)
   2580 #
   2581 # Make relative paths in INSTR and GRAPH absolute and apply
   2582 # geninfo_adjust_src_path setting to graph file data.
   2583 #
   2584 
   2585 sub adjust_graph_filenames($$$)
   2586 {
   2587 	my ($base_dir, $instr, $graph) = @_;
   2588 
   2589 	foreach my $filename (keys(%{$instr})) {
   2590 		my $old_filename = $filename;
   2591 
   2592 		# Convert to absolute canonical form
   2593 		$filename = solve_relative_path($base_dir, $filename);
   2594 
   2595 		# Apply adjustment
   2596 		if (defined($adjust_src_pattern)) {
   2597 			$filename =~ s/$adjust_src_pattern/$adjust_src_replace/g;
   2598 		}
   2599 
   2600 		if ($filename ne $old_filename) {
   2601 			$instr->{$filename} = delete($instr->{$old_filename});
   2602 		}
   2603 	}
   2604 
   2605 	foreach my $filename (keys(%{$graph})) {
   2606 		my $old_filename = $filename;
   2607 
   2608 		# Make absolute
   2609 		# Convert to absolute canonical form
   2610 		$filename = solve_relative_path($base_dir, $filename);
   2611 
   2612 		# Apply adjustment
   2613 		if (defined($adjust_src_pattern)) {
   2614 			$filename =~ s/$adjust_src_pattern/$adjust_src_replace/g;
   2615 		}
   2616 
   2617 		if ($filename ne $old_filename) {
   2618 			$graph->{$filename} = delete($graph->{$old_filename});
   2619 		}
   2620 	}
   2621 
   2622 	return ($instr, $graph);
   2623 }
   2624 
   2625 #
   2626 # graph_cleanup(graph)
   2627 #
   2628 # Remove entries for functions with no lines. Remove duplicate line numbers.
   2629 # Sort list of line numbers numerically ascending.
   2630 #
   2631 
   2632 sub graph_cleanup($)
   2633 {
   2634 	my ($graph) = @_;
   2635 	my $filename;
   2636 
   2637 	foreach $filename (keys(%{$graph})) {
   2638 		my $per_file = $graph->{$filename};
   2639 		my $function;
   2640 
   2641 		foreach $function (keys(%{$per_file})) {
   2642 			my $lines = $per_file->{$function};
   2643 
   2644 			if (scalar(@$lines) == 0) {
   2645 				# Remove empty function
   2646 				delete($per_file->{$function});
   2647 				next;
   2648 			}
   2649 			# Normalize list
   2650 			$per_file->{$function} = [ sort_uniq(@$lines) ];
   2651 		}
   2652 		if (scalar(keys(%{$per_file})) == 0) {
   2653 			# Remove empty file
   2654 			delete($graph->{$filename});
   2655 		}
   2656 	}
   2657 }
   2658 
   2659 #
   2660 # graph_find_base(bb)
   2661 #
   2662 # Try to identify the filename which is the base source file for the
   2663 # specified bb data.
   2664 #
   2665 
   2666 sub graph_find_base($)
   2667 {
   2668 	my ($bb) = @_;
   2669 	my %file_count;
   2670 	my $basefile;
   2671 	my $file;
   2672 	my $func;
   2673 	my $filedata;
   2674 	my $count;
   2675 	my $num;
   2676 
   2677 	# Identify base name for this bb data.
   2678 	foreach $func (keys(%{$bb})) {
   2679 		$filedata = $bb->{$func};
   2680 
   2681 		foreach $file (keys(%{$filedata})) {
   2682 			$count = $file_count{$file};
   2683 
   2684 			# Count file occurrence
   2685 			$file_count{$file} = defined($count) ? $count + 1 : 1;
   2686 		}
   2687 	}
   2688 	$count = 0;
   2689 	$num = 0;
   2690 	foreach $file (keys(%file_count)) {
   2691 		if ($file_count{$file} > $count) {
   2692 			# The file that contains code for the most functions
   2693 			# is likely the base file
   2694 			$count = $file_count{$file};
   2695 			$num = 1;
   2696 			$basefile = $file;
   2697 		} elsif ($file_count{$file} == $count) {
   2698 			# If more than one file could be the basefile, we
   2699 			# don't have a basefile
   2700 			$basefile = undef;
   2701 		}
   2702 	}
   2703 
   2704 	return $basefile;
   2705 }
   2706 
   2707 #
   2708 # graph_from_bb(bb, fileorder, bb_filename)
   2709 #
   2710 # Convert data from bb to the graph format and list of instrumented lines.
   2711 # Returns (instr, graph).
   2712 #
   2713 # bb         : function name -> file data
   2714 #            : undef -> file order
   2715 # file data  : filename -> line data
   2716 # line data  : [ line1, line2, ... ]
   2717 #
   2718 # file order : function name -> [ filename1, filename2, ... ]
   2719 #
   2720 # graph         : file name -> function data
   2721 # function data : function name -> line data
   2722 # line data     : [ line1, line2, ... ]
   2723 #
   2724 # instr     : filename -> line data
   2725 # line data : [ line1, line2, ... ]
   2726 #
   2727 
   2728 sub graph_from_bb($$$)
   2729 {
   2730 	my ($bb, $fileorder, $bb_filename) = @_;
   2731 	my $graph = {};
   2732 	my $instr = {};
   2733 	my $basefile;
   2734 	my $file;
   2735 	my $func;
   2736 	my $filedata;
   2737 	my $linedata;
   2738 	my $order;
   2739 
   2740 	$basefile = graph_find_base($bb);
   2741 	# Create graph structure
   2742 	foreach $func (keys(%{$bb})) {
   2743 		$filedata = $bb->{$func};
   2744 		$order = $fileorder->{$func};
   2745 
   2746 		# Account for lines in functions
   2747 		if (defined($basefile) && defined($filedata->{$basefile})) {
   2748 			# If the basefile contributes to this function,
   2749 			# account this function to the basefile.
   2750 			$graph->{$basefile}->{$func} = $filedata->{$basefile};
   2751 		} else {
   2752 			# If the basefile does not contribute to this function,
   2753 			# account this function to the first file contributing
   2754 			# lines.
   2755 			$graph->{$order->[0]}->{$func} =
   2756 				$filedata->{$order->[0]};
   2757 		}
   2758 
   2759 		foreach $file (keys(%{$filedata})) {
   2760 			# Account for instrumented lines
   2761 			$linedata = $filedata->{$file};
   2762 			push(@{$instr->{$file}}, @$linedata);
   2763 		}
   2764 	}
   2765 	# Clean up array of instrumented lines
   2766 	foreach $file (keys(%{$instr})) {
   2767 		$instr->{$file} = [ sort_uniq(@{$instr->{$file}}) ];
   2768 	}
   2769 
   2770 	return ($instr, $graph);
   2771 }
   2772 
   2773 #
   2774 # graph_add_order(fileorder, function, filename)
   2775 #
   2776 # Add an entry for filename to the fileorder data set for function.
   2777 #
   2778 
   2779 sub graph_add_order($$$)
   2780 {
   2781 	my ($fileorder, $function, $filename) = @_;
   2782 	my $item;
   2783 	my $list;
   2784 
   2785 	$list = $fileorder->{$function};
   2786 	foreach $item (@$list) {
   2787 		if ($item eq $filename) {
   2788 			return;
   2789 		}
   2790 	}
   2791 	push(@$list, $filename);
   2792 	$fileorder->{$function} = $list;
   2793 }
   2794 
   2795 #
   2796 # read_bb_word(handle[, description])
   2797 #
   2798 # Read and return a word in .bb format from handle.
   2799 #
   2800 
   2801 sub read_bb_word(*;$)
   2802 {
   2803 	my ($handle, $desc) = @_;
   2804 
   2805 	return graph_read($handle, 4, $desc);
   2806 }
   2807 
   2808 #
   2809 # read_bb_value(handle[, description])
   2810 #
   2811 # Read a word in .bb format from handle and return the word and its integer
   2812 # value.
   2813 #
   2814 
   2815 sub read_bb_value(*;$)
   2816 {
   2817 	my ($handle, $desc) = @_;
   2818 	my $word;
   2819 
   2820 	$word = read_bb_word($handle, $desc);
   2821 	return undef if (!defined($word));
   2822 
   2823 	return ($word, unpack("V", $word));
   2824 }
   2825 
   2826 #
   2827 # read_bb_string(handle, delimiter)
   2828 #
   2829 # Read and return a string in .bb format from handle up to the specified
   2830 # delimiter value.
   2831 #
   2832 
   2833 sub read_bb_string(*$)
   2834 {
   2835 	my ($handle, $delimiter) = @_;
   2836 	my $word;
   2837 	my $value;
   2838 	my $string = "";
   2839 
   2840 	graph_expect("string");
   2841 	do {
   2842 		($word, $value) = read_bb_value($handle, "string or delimiter");
   2843 		return undef if (!defined($value));
   2844 		if ($value != $delimiter) {
   2845 			$string .= $word;
   2846 		}
   2847 	} while ($value != $delimiter);
   2848 	$string =~ s/\0//g;
   2849 
   2850 	return $string;
   2851 }
   2852 
   2853 #
   2854 # read_bb(filename)
   2855 #
   2856 # Read the contents of the specified .bb file and return (instr, graph), where:
   2857 #
   2858 #   instr     : filename -> line data
   2859 #   line data : [ line1, line2, ... ]
   2860 #
   2861 #   graph     :     filename -> file_data
   2862 #   file_data : function name -> line_data
   2863 #   line_data : [ line1, line2, ... ]
   2864 #
   2865 # See the gcov info pages of gcc 2.95 for a description of the .bb file format.
   2866 #
   2867 
   2868 sub read_bb($)
   2869 {
   2870 	my ($bb_filename) = @_;
   2871 	my $minus_one = 0x80000001;
   2872 	my $minus_two = 0x80000002;
   2873 	my $value;
   2874 	my $filename;
   2875 	my $function;
   2876 	my $bb = {};
   2877 	my $fileorder = {};
   2878 	my $instr;
   2879 	my $graph;
   2880 	local *HANDLE;
   2881 
   2882 	open(HANDLE, "<", $bb_filename) or goto open_error;
   2883 	binmode(HANDLE);
   2884 	while (!eof(HANDLE)) {
   2885 		$value = read_bb_value(*HANDLE, "data word");
   2886 		goto incomplete if (!defined($value));
   2887 		if ($value == $minus_one) {
   2888 			# Source file name
   2889 			graph_expect("filename");
   2890 			$filename = read_bb_string(*HANDLE, $minus_one);
   2891 			goto incomplete if (!defined($filename));
   2892 		} elsif ($value == $minus_two) {
   2893 			# Function name
   2894 			graph_expect("function name");
   2895 			$function = read_bb_string(*HANDLE, $minus_two);
   2896 			goto incomplete if (!defined($function));
   2897 		} elsif ($value > 0) {
   2898 			# Line number
   2899 			if (!defined($filename) || !defined($function)) {
   2900 				warn("WARNING: unassigned line number ".
   2901 				     "$value\n");
   2902 				next;
   2903 			}
   2904 			push(@{$bb->{$function}->{$filename}}, $value);
   2905 			graph_add_order($fileorder, $function, $filename);
   2906 		}
   2907 	}
   2908 	close(HANDLE);
   2909 	($instr, $graph) = graph_from_bb($bb, $fileorder, $bb_filename);
   2910 	graph_cleanup($graph);
   2911 
   2912 	return ($instr, $graph);
   2913 
   2914 open_error:
   2915 	graph_error($bb_filename, "could not open file");
   2916 	return undef;
   2917 incomplete:
   2918 	graph_error($bb_filename, "reached unexpected end of file");
   2919 	return undef;
   2920 }
   2921 
   2922 #
   2923 # read_bbg_word(handle[, description])
   2924 #
   2925 # Read and return a word in .bbg format.
   2926 #
   2927 
   2928 sub read_bbg_word(*;$)
   2929 {
   2930 	my ($handle, $desc) = @_;
   2931 
   2932 	return graph_read($handle, 4, $desc);
   2933 }
   2934 
   2935 #
   2936 # read_bbg_value(handle[, description])
   2937 #
   2938 # Read a word in .bbg format from handle and return its integer value.
   2939 #
   2940 
   2941 sub read_bbg_value(*;$)
   2942 {
   2943 	my ($handle, $desc) = @_;
   2944 	my $word;
   2945 
   2946 	$word = read_bbg_word($handle, $desc);
   2947 	return undef if (!defined($word));
   2948 
   2949 	return unpack("N", $word);
   2950 }
   2951 
   2952 #
   2953 # read_bbg_string(handle)
   2954 #
   2955 # Read and return a string in .bbg format.
   2956 #
   2957 
   2958 sub read_bbg_string(*)
   2959 {
   2960 	my ($handle, $desc) = @_;
   2961 	my $length;
   2962 	my $string;
   2963 
   2964 	graph_expect("string");
   2965 	# Read string length
   2966 	$length = read_bbg_value($handle, "string length");
   2967 	return undef if (!defined($length));
   2968 	if ($length == 0) {
   2969 		return "";
   2970 	}
   2971 	# Read string
   2972 	$string = graph_read($handle, $length, "string");
   2973 	return undef if (!defined($string));
   2974 	# Skip padding
   2975 	graph_skip($handle, 4 - $length % 4, "string padding") or return undef;
   2976 
   2977 	return $string;
   2978 }
   2979 
   2980 #
   2981 # read_bbg_lines_record(handle, bbg_filename, bb, fileorder, filename,
   2982 #                       function)
   2983 #
   2984 # Read a bbg format lines record from handle and add the relevant data to
   2985 # bb and fileorder. Return filename on success, undef on error.
   2986 #
   2987 
   2988 sub read_bbg_lines_record(*$$$$$)
   2989 {
   2990 	my ($handle, $bbg_filename, $bb, $fileorder, $filename, $function) = @_;
   2991 	my $string;
   2992 	my $lineno;
   2993 
   2994 	graph_expect("lines record");
   2995 	# Skip basic block index
   2996 	graph_skip($handle, 4, "basic block index") or return undef;
   2997 	while (1) {
   2998 		# Read line number
   2999 		$lineno = read_bbg_value($handle, "line number");
   3000 		return undef if (!defined($lineno));
   3001 		if ($lineno == 0) {
   3002 			# Got a marker for a new filename
   3003 			graph_expect("filename");
   3004 			$string = read_bbg_string($handle);
   3005 			return undef if (!defined($string));
   3006 			# Check for end of record
   3007 			if ($string eq "") {
   3008 				return $filename;
   3009 			}
   3010 			$filename = $string;
   3011 			if (!exists($bb->{$function}->{$filename})) {
   3012 				$bb->{$function}->{$filename} = [];
   3013 			}
   3014 			next;
   3015 		}
   3016 		# Got an actual line number
   3017 		if (!defined($filename)) {
   3018 			warn("WARNING: unassigned line number in ".
   3019 			     "$bbg_filename\n");
   3020 			next;
   3021 		}
   3022 		push(@{$bb->{$function}->{$filename}}, $lineno);
   3023 		graph_add_order($fileorder, $function, $filename);
   3024 	}
   3025 }
   3026 
   3027 #
   3028 # read_bbg(filename)
   3029 #
   3030 # Read the contents of the specified .bbg file and return the following mapping:
   3031 #   graph:     filename -> file_data
   3032 #   file_data: function name -> line_data
   3033 #   line_data: [ line1, line2, ... ]
   3034 #
   3035 # See the gcov-io.h file in the SLES 9 gcc 3.3.3 source code for a description
   3036 # of the .bbg format.
   3037 #
   3038 
   3039 sub read_bbg($)
   3040 {
   3041 	my ($bbg_filename) = @_;
   3042 	my $file_magic = 0x67626267;
   3043 	my $tag_function = 0x01000000;
   3044 	my $tag_lines = 0x01450000;
   3045 	my $word;
   3046 	my $tag;
   3047 	my $length;
   3048 	my $function;
   3049 	my $filename;
   3050 	my $bb = {};
   3051 	my $fileorder = {};
   3052 	my $instr;
   3053 	my $graph;
   3054 	local *HANDLE;
   3055 
   3056 	open(HANDLE, "<", $bbg_filename) or goto open_error;
   3057 	binmode(HANDLE);
   3058 	# Read magic
   3059 	$word = read_bbg_value(*HANDLE, "file magic");
   3060 	goto incomplete if (!defined($word));
   3061 	# Check magic
   3062 	if ($word != $file_magic) {
   3063 		goto magic_error;
   3064 	}
   3065 	# Skip version
   3066 	graph_skip(*HANDLE, 4, "version") or goto incomplete;
   3067 	while (!eof(HANDLE)) {
   3068 		# Read record tag
   3069 		$tag = read_bbg_value(*HANDLE, "record tag");
   3070 		goto incomplete if (!defined($tag));
   3071 		# Read record length
   3072 		$length = read_bbg_value(*HANDLE, "record length");
   3073 		goto incomplete if (!defined($tag));
   3074 		if ($tag == $tag_function) {
   3075 			graph_expect("function record");
   3076 			# Read function name
   3077 			graph_expect("function name");
   3078 			$function = read_bbg_string(*HANDLE);
   3079 			goto incomplete if (!defined($function));
   3080 			$filename = undef;
   3081 			# Skip function checksum
   3082 			graph_skip(*HANDLE, 4, "function checksum")
   3083 				or goto incomplete;
   3084 		} elsif ($tag == $tag_lines) {
   3085 			# Read lines record
   3086 			$filename = read_bbg_lines_record(HANDLE, $bbg_filename,
   3087 					  $bb, $fileorder, $filename,
   3088 					  $function);
   3089 			goto incomplete if (!defined($filename));
   3090 		} else {
   3091 			# Skip record contents
   3092 			graph_skip(*HANDLE, $length, "unhandled record")
   3093 				or goto incomplete;
   3094 		}
   3095 	}
   3096 	close(HANDLE);
   3097 	($instr, $graph) = graph_from_bb($bb, $fileorder, $bbg_filename);
   3098 	graph_cleanup($graph);
   3099 
   3100 	return ($instr, $graph);
   3101 
   3102 open_error:
   3103 	graph_error($bbg_filename, "could not open file");
   3104 	return undef;
   3105 incomplete:
   3106 	graph_error($bbg_filename, "reached unexpected end of file");
   3107 	return undef;
   3108 magic_error:
   3109 	graph_error($bbg_filename, "found unrecognized bbg file magic");
   3110 	return undef;
   3111 }
   3112 
   3113 #
   3114 # read_gcno_word(handle[, description, peek])
   3115 #
   3116 # Read and return a word in .gcno format.
   3117 #
   3118 
   3119 sub read_gcno_word(*;$$)
   3120 {
   3121 	my ($handle, $desc, $peek) = @_;
   3122 
   3123 	return graph_read($handle, 4, $desc, $peek);
   3124 }
   3125 
   3126 #
   3127 # read_gcno_value(handle, big_endian[, description, peek])
   3128 #
   3129 # Read a word in .gcno format from handle and return its integer value
   3130 # according to the specified endianness. If PEEK is non-zero, reset file
   3131 # position after read.
   3132 #
   3133 
   3134 sub read_gcno_value(*$;$$)
   3135 {
   3136 	my ($handle, $big_endian, $desc, $peek) = @_;
   3137 	my $word;
   3138 	my $pos;
   3139 
   3140 	$word = read_gcno_word($handle, $desc, $peek);
   3141 	return undef if (!defined($word));
   3142 	if ($big_endian) {
   3143 		return unpack("N", $word);
   3144 	} else {
   3145 		return unpack("V", $word);
   3146 	}
   3147 }
   3148 
   3149 #
   3150 # read_gcno_string(handle, big_endian)
   3151 #
   3152 # Read and return a string in .gcno format.
   3153 #
   3154 
   3155 sub read_gcno_string(*$)
   3156 {
   3157 	my ($handle, $big_endian) = @_;
   3158 	my $length;
   3159 	my $string;
   3160 
   3161 	graph_expect("string");
   3162 	# Read string length
   3163 	$length = read_gcno_value($handle, $big_endian, "string length");
   3164 	return undef if (!defined($length));
   3165 	if ($length == 0) {
   3166 		return "";
   3167 	}
   3168 	$length *= 4;
   3169 	# Read string
   3170 	$string = graph_read($handle, $length, "string and padding");
   3171 	return undef if (!defined($string));
   3172 	$string =~ s/\0//g;
   3173 
   3174 	return $string;
   3175 }
   3176 
   3177 #
   3178 # read_gcno_lines_record(handle, gcno_filename, bb, fileorder, filename,
   3179 #                        function, big_endian)
   3180 #
   3181 # Read a gcno format lines record from handle and add the relevant data to
   3182 # bb and fileorder. Return filename on success, undef on error.
   3183 #
   3184 
   3185 sub read_gcno_lines_record(*$$$$$$)
   3186 {
   3187 	my ($handle, $gcno_filename, $bb, $fileorder, $filename, $function,
   3188 	    $big_endian) = @_;
   3189 	my $string;
   3190 	my $lineno;
   3191 
   3192 	graph_expect("lines record");
   3193 	# Skip basic block index
   3194 	graph_skip($handle, 4, "basic block index") or return undef;
   3195 	while (1) {
   3196 		# Read line number
   3197 		$lineno = read_gcno_value($handle, $big_endian, "line number");
   3198 		return undef if (!defined($lineno));
   3199 		if ($lineno == 0) {
   3200 			# Got a marker for a new filename
   3201 			graph_expect("filename");
   3202 			$string = read_gcno_string($handle, $big_endian);
   3203 			return undef if (!defined($string));
   3204 			# Check for end of record
   3205 			if ($string eq "") {
   3206 				return $filename;
   3207 			}
   3208 			$filename = $string;
   3209 			if (!exists($bb->{$function}->{$filename})) {
   3210 				$bb->{$function}->{$filename} = [];
   3211 			}
   3212 			next;
   3213 		}
   3214 		# Got an actual line number
   3215 		if (!defined($filename)) {
   3216 			warn("WARNING: unassigned line number in ".
   3217 			     "$gcno_filename\n");
   3218 			next;
   3219 		}
   3220 		# Add to list
   3221 		push(@{$bb->{$function}->{$filename}}, $lineno);
   3222 		graph_add_order($fileorder, $function, $filename);
   3223 	}
   3224 }
   3225 
   3226 #
   3227 # determine_gcno_split_crc(handle, big_endian, rec_length)
   3228 #
   3229 # Determine if HANDLE refers to a .gcno file with a split checksum function
   3230 # record format. Return non-zero in case of split checksum format, zero
   3231 # otherwise, undef in case of read error.
   3232 #
   3233 
   3234 sub determine_gcno_split_crc($$$)
   3235 {
   3236 	my ($handle, $big_endian, $rec_length) = @_;
   3237 	my $strlen;
   3238 	my $overlong_string;
   3239 
   3240 	return 1 if ($gcov_version >= $GCOV_VERSION_4_7_0);
   3241 	return 1 if (is_compat($COMPAT_MODE_SPLIT_CRC));
   3242 
   3243 	# Heuristic:
   3244 	# Decide format based on contents of next word in record:
   3245 	# - pre-gcc 4.7
   3246 	#   This is the function name length / 4 which should be
   3247 	#   less than the remaining record length
   3248 	# - gcc 4.7
   3249 	#   This is a checksum, likely with high-order bits set,
   3250 	#   resulting in a large number
   3251 	$strlen = read_gcno_value($handle, $big_endian, undef, 1);
   3252 	return undef if (!defined($strlen));
   3253 	$overlong_string = 1 if ($strlen * 4 >= $rec_length - 12);
   3254 
   3255 	if ($overlong_string) {
   3256 		if (is_compat_auto($COMPAT_MODE_SPLIT_CRC)) {
   3257 			info("Auto-detected compatibility mode for split ".
   3258 			     "checksum .gcno file format\n");
   3259 
   3260 			return 1;
   3261 		} else {
   3262 			# Sanity check
   3263 			warn("Found overlong string in function record: ".
   3264 			     "try '--compat split_crc'\n");
   3265 		}
   3266 	}
   3267 
   3268 	return 0;
   3269 }
   3270 
   3271 #
   3272 # read_gcno_function_record(handle, graph, big_endian, rec_length)
   3273 #
   3274 # Read a gcno format function record from handle and add the relevant data
   3275 # to graph. Return (filename, function) on success, undef on error. 
   3276 #
   3277 
   3278 sub read_gcno_function_record(*$$$$)
   3279 {
   3280 	my ($handle, $bb, $fileorder, $big_endian, $rec_length) = @_;
   3281 	my $filename;
   3282 	my $function;
   3283 	my $lineno;
   3284 	my $lines;
   3285 
   3286 	graph_expect("function record");
   3287 	# Skip ident and checksum
   3288 	graph_skip($handle, 8, "function ident and checksum") or return undef;
   3289 	# Determine if this is a function record with split checksums
   3290 	if (!defined($gcno_split_crc)) {
   3291 		$gcno_split_crc = determine_gcno_split_crc($handle, $big_endian,
   3292 							   $rec_length);
   3293 		return undef if (!defined($gcno_split_crc));
   3294 	}
   3295 	# Skip cfg checksum word in case of split checksums
   3296 	graph_skip($handle, 4, "function cfg checksum") if ($gcno_split_crc);
   3297 	# Read function name
   3298 	graph_expect("function name");
   3299 	$function = read_gcno_string($handle, $big_endian);
   3300 	return undef if (!defined($function));
   3301 	# Read filename
   3302 	graph_expect("filename");
   3303 	$filename = read_gcno_string($handle, $big_endian);
   3304 	return undef if (!defined($filename));
   3305 	# Read first line number
   3306 	$lineno = read_gcno_value($handle, $big_endian, "initial line number");
   3307 	return undef if (!defined($lineno));
   3308 	# Add to list
   3309 	push(@{$bb->{$function}->{$filename}}, $lineno);
   3310 	graph_add_order($fileorder, $function, $filename);
   3311 
   3312 	return ($filename, $function);
   3313 }
   3314 
   3315 #
   3316 # read_gcno(filename)
   3317 #
   3318 # Read the contents of the specified .gcno file and return the following
   3319 # mapping:
   3320 #   graph:    filename -> file_data
   3321 #   file_data: function name -> line_data
   3322 #   line_data: [ line1, line2, ... ]
   3323 #
   3324 # See the gcov-io.h file in the gcc 3.3 source code for a description of
   3325 # the .gcno format.
   3326 #
   3327 
   3328 sub read_gcno($)
   3329 {
   3330 	my ($gcno_filename) = @_;
   3331 	my $file_magic = 0x67636e6f;
   3332 	my $tag_function = 0x01000000;
   3333 	my $tag_lines = 0x01450000;
   3334 	my $big_endian;
   3335 	my $word;
   3336 	my $tag;
   3337 	my $length;
   3338 	my $filename;
   3339 	my $function;
   3340 	my $bb = {};
   3341 	my $fileorder = {};
   3342 	my $instr;
   3343 	my $graph;
   3344 	local *HANDLE;
   3345 
   3346 	open(HANDLE, "<", $gcno_filename) or goto open_error;
   3347 	binmode(HANDLE);
   3348 	# Read magic
   3349 	$word = read_gcno_word(*HANDLE, "file magic");
   3350 	goto incomplete if (!defined($word));
   3351 	# Determine file endianness
   3352 	if (unpack("N", $word) == $file_magic) {
   3353 		$big_endian = 1;
   3354 	} elsif (unpack("V", $word) == $file_magic) {
   3355 		$big_endian = 0;
   3356 	} else {
   3357 		goto magic_error;
   3358 	}
   3359 	# Skip version and stamp
   3360 	graph_skip(*HANDLE, 8, "version and stamp") or goto incomplete;
   3361 	while (!eof(HANDLE)) {
   3362 		my $next_pos;
   3363 		my $curr_pos;
   3364 
   3365 		# Read record tag
   3366 		$tag = read_gcno_value(*HANDLE, $big_endian, "record tag");
   3367 		goto incomplete if (!defined($tag));
   3368 		# Read record length
   3369 		$length = read_gcno_value(*HANDLE, $big_endian,
   3370 					  "record length");
   3371 		goto incomplete if (!defined($length));
   3372 		# Convert length to bytes
   3373 		$length *= 4;
   3374 		# Calculate start of next record
   3375 		$next_pos = tell(HANDLE);
   3376 		goto tell_error if ($next_pos == -1);
   3377 		$next_pos += $length;
   3378 		# Process record
   3379 		if ($tag == $tag_function) {
   3380 			($filename, $function) = read_gcno_function_record(
   3381 				*HANDLE, $bb, $fileorder, $big_endian,
   3382 				$length);
   3383 			goto incomplete if (!defined($function));
   3384 		} elsif ($tag == $tag_lines) {
   3385 			# Read lines record
   3386 			$filename = read_gcno_lines_record(*HANDLE,
   3387 					$gcno_filename, $bb, $fileorder,
   3388 					$filename, $function,
   3389 					$big_endian);
   3390 			goto incomplete if (!defined($filename));
   3391 		} else {
   3392 			# Skip record contents
   3393 			graph_skip(*HANDLE, $length, "unhandled record")
   3394 				or goto incomplete;
   3395 		}
   3396 		# Ensure that we are at the start of the next record
   3397 		$curr_pos = tell(HANDLE);
   3398 		goto tell_error if ($curr_pos == -1);
   3399 		next if ($curr_pos == $next_pos);
   3400 		goto record_error if ($curr_pos > $next_pos);
   3401 		graph_skip(*HANDLE, $next_pos - $curr_pos,
   3402 			   "unhandled record content")
   3403 			or goto incomplete;
   3404 	}
   3405 	close(HANDLE);
   3406 	($instr, $graph) = graph_from_bb($bb, $fileorder, $gcno_filename);
   3407 	graph_cleanup($graph);
   3408 
   3409 	return ($instr, $graph);
   3410 
   3411 open_error:
   3412 	graph_error($gcno_filename, "could not open file");
   3413 	return undef;
   3414 incomplete:
   3415 	graph_error($gcno_filename, "reached unexpected end of file");
   3416 	return undef;
   3417 magic_error:
   3418 	graph_error($gcno_filename, "found unrecognized gcno file magic");
   3419 	return undef;
   3420 tell_error:
   3421 	graph_error($gcno_filename, "could not determine file position");
   3422 	return undef;
   3423 record_error:
   3424 	graph_error($gcno_filename, "found unrecognized record format");
   3425 	return undef;
   3426 }
   3427 
   3428 sub debug($)
   3429 {
   3430 	my ($msg) = @_;
   3431 
   3432 	return if (!$debug);
   3433 	print(STDERR "DEBUG: $msg");
   3434 }
   3435 
   3436 #
   3437 # get_gcov_capabilities
   3438 #
   3439 # Determine the list of available gcov options.
   3440 #
   3441 
   3442 sub get_gcov_capabilities()
   3443 {
   3444 	my $help = `$gcov_tool --help`;
   3445 	my %capabilities;
   3446 
   3447 	foreach (split(/\n/, $help)) {
   3448 		next if (!/--(\S+)/);
   3449 		next if ($1 eq 'help');
   3450 		next if ($1 eq 'version');
   3451 		next if ($1 eq 'object-directory');
   3452 
   3453 		$capabilities{$1} = 1;
   3454 		debug("gcov has capability '$1'\n");
   3455 	}
   3456 
   3457 	return \%capabilities;
   3458 }
   3459 
   3460 #
   3461 # parse_ignore_errors(@ignore_errors)
   3462 #
   3463 # Parse user input about which errors to ignore.
   3464 #
   3465 
   3466 sub parse_ignore_errors(@)
   3467 {
   3468 	my (@ignore_errors) = @_;
   3469 	my @items;
   3470 	my $item;
   3471 
   3472 	return if (!@ignore_errors);
   3473 
   3474 	foreach $item (@ignore_errors) {
   3475 		$item =~ s/\s//g;
   3476 		if ($item =~ /,/) {
   3477 			# Split and add comma-separated parameters
   3478 			push(@items, split(/,/, $item));
   3479 		} else {
   3480 			# Add single parameter
   3481 			push(@items, $item);
   3482 		}
   3483 	}
   3484 	foreach $item (@items) {
   3485 		my $item_id = $ERROR_ID{lc($item)};
   3486 
   3487 		if (!defined($item_id)) {
   3488 			die("ERROR: unknown argument for --ignore-errors: ".
   3489 			    "$item\n");
   3490 		}
   3491 		$ignore[$item_id] = 1;
   3492 	}
   3493 }
   3494 
   3495 #
   3496 # is_external(filename)
   3497 #
   3498 # Determine if a file is located outside of the specified data directories.
   3499 #
   3500 
   3501 sub is_external($)
   3502 {
   3503 	my ($filename) = @_;
   3504 	my $dir;
   3505 
   3506 	foreach $dir (@internal_dirs) {
   3507 		return 0 if ($filename =~ /^\Q$dir\/\E/);
   3508 	}
   3509 	return 1;
   3510 }
   3511 
   3512 #
   3513 # compat_name(mode)
   3514 #
   3515 # Return the name of compatibility mode MODE.
   3516 #
   3517 
   3518 sub compat_name($)
   3519 {
   3520 	my ($mode) = @_;
   3521 	my $name = $COMPAT_MODE_TO_NAME{$mode};
   3522 
   3523 	return $name if (defined($name));
   3524 
   3525 	return "<unknown>";
   3526 }
   3527 
   3528 #
   3529 # parse_compat_modes(opt)
   3530 #
   3531 # Determine compatibility mode settings.
   3532 #
   3533 
   3534 sub parse_compat_modes($)
   3535 {
   3536 	my ($opt) = @_;
   3537 	my @opt_list;
   3538 	my %specified;
   3539 
   3540 	# Initialize with defaults
   3541 	%compat_value = %COMPAT_MODE_DEFAULTS;
   3542 
   3543 	# Add old style specifications
   3544 	if (defined($opt_compat_libtool)) {
   3545 		$compat_value{$COMPAT_MODE_LIBTOOL} =
   3546 			$opt_compat_libtool ? $COMPAT_VALUE_ON
   3547 					    : $COMPAT_VALUE_OFF;
   3548 	}
   3549 
   3550 	# Parse settings
   3551 	if (defined($opt)) {
   3552 		@opt_list = split(/\s*,\s*/, $opt);
   3553 	}
   3554 	foreach my $directive (@opt_list) {
   3555 		my ($mode, $value);
   3556 
   3557 		# Either
   3558 		#   mode=off|on|auto or
   3559 		#   mode (implies on)
   3560 		if ($directive !~ /^(\w+)=(\w+)$/ &&
   3561 		    $directive !~ /^(\w+)$/) {
   3562 			die("ERROR: Unknown compatibility mode specification: ".
   3563 			    "$directive!\n");
   3564 		}
   3565 		# Determine mode
   3566 		$mode = $COMPAT_NAME_TO_MODE{lc($1)};
   3567 		if (!defined($mode)) {
   3568 			die("ERROR: Unknown compatibility mode '$1'!\n");
   3569 		}
   3570 		$specified{$mode} = 1;
   3571 		# Determine value
   3572 		if (defined($2)) {
   3573 			$value = $COMPAT_NAME_TO_VALUE{lc($2)};
   3574 			if (!defined($value)) {
   3575 				die("ERROR: Unknown compatibility mode ".
   3576 				    "value '$2'!\n");
   3577 			}
   3578 		} else {
   3579 			$value = $COMPAT_VALUE_ON;
   3580 		}
   3581 		$compat_value{$mode} = $value;
   3582 	}
   3583 	# Perform auto-detection
   3584 	foreach my $mode (sort(keys(%compat_value))) {
   3585 		my $value = $compat_value{$mode};
   3586 		my $is_autodetect = "";
   3587 		my $name = compat_name($mode);
   3588 
   3589 		if ($value == $COMPAT_VALUE_AUTO) {
   3590 			my $autodetect = $COMPAT_MODE_AUTO{$mode};
   3591 
   3592 			if (!defined($autodetect)) {
   3593 				die("ERROR: No auto-detection for ".
   3594 				    "mode '$name' available!\n");
   3595 			}
   3596 
   3597 			if (ref($autodetect) eq "CODE") {
   3598 				$value = &$autodetect();
   3599 				$compat_value{$mode} = $value;
   3600 				$is_autodetect = " (auto-detected)";
   3601 			}
   3602 		}
   3603 
   3604 		if ($specified{$mode}) {
   3605 			if ($value == $COMPAT_VALUE_ON) {
   3606 				info("Enabling compatibility mode ".
   3607 				     "'$name'$is_autodetect\n");
   3608 			} elsif ($value == $COMPAT_VALUE_OFF) {
   3609 				info("Disabling compatibility mode ".
   3610 				     "'$name'$is_autodetect\n");
   3611 			} else {
   3612 				info("Using delayed auto-detection for ".
   3613 				     "compatibility mode ".
   3614 				     "'$name'\n");
   3615 			}
   3616 		}
   3617 	}
   3618 }
   3619 
   3620 sub compat_hammer_autodetect()
   3621 {
   3622         if ($gcov_version_string =~ /suse/i && $gcov_version == 0x30303 ||
   3623             $gcov_version_string =~ /mandrake/i && $gcov_version == 0x30302)
   3624 	{
   3625 		info("Auto-detected compatibility mode for GCC 3.3 (hammer)\n");
   3626 		return $COMPAT_VALUE_ON;
   3627 	}
   3628 	return $COMPAT_VALUE_OFF;
   3629 }
   3630 
   3631 #
   3632 # is_compat(mode)
   3633 #
   3634 # Return non-zero if compatibility mode MODE is enabled.
   3635 #
   3636 
   3637 sub is_compat($)
   3638 {
   3639 	my ($mode) = @_;
   3640 
   3641 	return 1 if ($compat_value{$mode} == $COMPAT_VALUE_ON);
   3642 	return 0;
   3643 }
   3644 
   3645 #
   3646 # is_compat_auto(mode)
   3647 #
   3648 # Return non-zero if compatibility mode MODE is set to auto-detect.
   3649 #
   3650 
   3651 sub is_compat_auto($)
   3652 {
   3653 	my ($mode) = @_;
   3654 
   3655 	return 1 if ($compat_value{$mode} == $COMPAT_VALUE_AUTO);
   3656 	return 0;
   3657 }
   3658